feat(ui): apply branded headers and refresh splash/loading icon assets

- Use login background image in garage, collections, and profile headers
- Show app logo in About screen and add support mail action
- Configure native splash image assets in pubspec
- Regenerate flutter_native_splash and launcher icons for Android/iOS/Web
This commit is contained in:
Lukas Müllner 2026-03-04 17:01:45 +01:00
parent b67483db4e
commit ed541e046c
73 changed files with 220 additions and 68 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

View file

@ -3,4 +3,7 @@
<item> <item>
<bitmap android:gravity="fill" android:src="@drawable/background"/> <bitmap android:gravity="fill" android:src="@drawable/background"/>
</item> </item>
<item>
<bitmap android:gravity="center" android:src="@drawable/splash"/>
</item>
</layer-list> </layer-list>

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 276 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 276 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 458 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 458 KiB

View file

@ -3,4 +3,7 @@
<item> <item>
<bitmap android:gravity="fill" android:src="@drawable/background"/> <bitmap android:gravity="fill" android:src="@drawable/background"/>
</item> </item>
<item>
<bitmap android:gravity="center" android:src="@drawable/splash"/>
</item>
</layer-list> </layer-list>

View file

@ -3,4 +3,7 @@
<item> <item>
<bitmap android:gravity="fill" android:src="@drawable/background"/> <bitmap android:gravity="fill" android:src="@drawable/background"/>
</item> </item>
<item>
<bitmap android:gravity="center" android:src="@drawable/splash"/>
</item>
</layer-list> </layer-list>

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 276 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 97 KiB

After

Width:  |  Height:  |  Size: 214 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 276 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 458 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 163 KiB

After

Width:  |  Height:  |  Size: 354 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 458 KiB

View file

@ -3,4 +3,7 @@
<item> <item>
<bitmap android:gravity="fill" android:src="@drawable/background"/> <bitmap android:gravity="fill" android:src="@drawable/background"/>
</item> </item>
<item>
<bitmap android:gravity="center" android:src="@drawable/splash"/>
</item>
</layer-list> </layer-list>

View file

@ -1,5 +1,9 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/> <background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/> <foreground>
<inset
android:drawable="@drawable/ic_launcher_foreground"
android:inset="16%" />
</foreground>
</adaptive-icon> </adaptive-icon>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 KiB

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

After

Width:  |  Height:  |  Size: 84 KiB

View file

@ -7,6 +7,7 @@
<item name="android:windowDrawsSystemBarBackgrounds">false</item> <item name="android:windowDrawsSystemBarBackgrounds">false</item>
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item> <item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
<item name="android:windowSplashScreenBackground">#1a1a2e</item> <item name="android:windowSplashScreenBackground">#1a1a2e</item>
<item name="android:windowSplashScreenAnimatedIcon">@drawable/android12splash</item>
</style> </style>
<!-- Theme applied to the Android Window as soon as the process has started. <!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your This theme determines the color of the Android Window while your

View file

@ -7,6 +7,7 @@
<item name="android:windowDrawsSystemBarBackgrounds">false</item> <item name="android:windowDrawsSystemBarBackgrounds">false</item>
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item> <item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
<item name="android:windowSplashScreenBackground">#f9a11b</item> <item name="android:windowSplashScreenBackground">#f9a11b</item>
<item name="android:windowSplashScreenAnimatedIcon">@drawable/android12splash</item>
</style> </style>
<!-- Theme applied to the Android Window as soon as the process has started. <!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your This theme determines the color of the Android Window while your

View file

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<color name="ic_launcher_background">#00000000</color> <color name="ic_launcher_background">#f9a11b</color>
</resources> </resources>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.8 KiB

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.3 KiB

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.1 KiB

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 63 KiB

View file

@ -5,15 +5,48 @@
"idiom" : "universal", "idiom" : "universal",
"scale" : "1x" "scale" : "1x"
}, },
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "LaunchImageDark.png",
"idiom" : "universal",
"scale" : "1x"
},
{ {
"filename" : "LaunchImage@2x.png", "filename" : "LaunchImage@2x.png",
"idiom" : "universal", "idiom" : "universal",
"scale" : "2x" "scale" : "2x"
}, },
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "LaunchImageDark@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{ {
"filename" : "LaunchImage@3x.png", "filename" : "LaunchImage@3x.png",
"idiom" : "universal", "idiom" : "universal",
"scale" : "3x" "scale" : "3x"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "LaunchImageDark@3x.png",
"idiom" : "universal",
"scale" : "3x"
} }
], ],
"info" : { "info" : {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 B

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 B

After

Width:  |  Height:  |  Size: 135 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 B

After

Width:  |  Height:  |  Size: 276 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 276 KiB

View file

@ -1,5 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:package_info_plus/package_info_plus.dart'; import 'package:package_info_plus/package_info_plus.dart';
import 'package:url_launcher/url_launcher.dart';
import '../theme/app_colors.dart'; import '../theme/app_colors.dart';
/// Full-page About screen with app info, version, and links. /// Full-page About screen with app info, version, and links.
@ -11,6 +12,8 @@ class AboutScreen extends StatefulWidget {
} }
class _AboutScreenState extends State<AboutScreen> { class _AboutScreenState extends State<AboutScreen> {
static const _supportEmail = 'support@car64.app';
PackageInfo? _packageInfo; PackageInfo? _packageInfo;
@override @override
@ -24,6 +27,25 @@ class _AboutScreenState extends State<AboutScreen> {
if (mounted) setState(() => _packageInfo = info); if (mounted) setState(() => _packageInfo = info);
} }
Future<void> _contactSupport() async {
final uri = Uri(
scheme: 'mailto',
path: _supportEmail,
queryParameters: {
'subject': 'car64 Support Request',
},
);
final launched = await launchUrl(uri, mode: LaunchMode.externalApplication);
if (!launched && mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Could not open mail app. Please email support@car64.app.'),
),
);
}
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final theme = Theme.of(context); final theme = Theme.of(context);
@ -49,10 +71,17 @@ class _AboutScreenState extends State<AboutScreen> {
gradient: AppColors.brandGradient, gradient: AppColors.brandGradient,
shape: BoxShape.circle, shape: BoxShape.circle,
), ),
child: const Icon( child: Padding(
Icons.directions_car_filled, padding: const EdgeInsets.all(16),
color: Colors.white, child: Image.asset(
size: 52, 'assets/img/icon_bg_removed.png',
fit: BoxFit.contain,
errorBuilder: (context, error, stackTrace) => const Icon(
Icons.directions_car_filled,
color: Colors.white,
size: 52,
),
),
), ),
), ),
), ),
@ -109,6 +138,16 @@ class _AboutScreenState extends State<AboutScreen> {
value: _packageInfo?.packageName ?? '', value: _packageInfo?.packageName ?? '',
), ),
const SizedBox(height: 20),
SizedBox(
width: double.infinity,
child: OutlinedButton.icon(
onPressed: _contactSupport,
icon: const Icon(Icons.support_agent),
label: const Text('Contact Support'),
),
),
const SizedBox(height: 32), const SizedBox(height: 32),
// Powered by // Powered by

View file

@ -202,17 +202,33 @@ class CollectionsScreenState extends State<CollectionsScreen> {
), ),
), ),
background: Container( background: Container(
decoration: const BoxDecoration( decoration: BoxDecoration(
gradient: AppColors.brandGradient, image: const DecorationImage(
image: AssetImage('assets/img/login_bg.jpg'),
fit: BoxFit.cover,
),
color: Colors.black.withValues(alpha: 0.15),
), ),
child: Align( child: Container(
alignment: Alignment.centerRight, decoration: BoxDecoration(
child: Padding( gradient: LinearGradient(
padding: const EdgeInsets.only(right: 24), begin: Alignment.topCenter,
child: Icon( end: Alignment.bottomCenter,
Icons.collections_bookmark, colors: [
size: 72, Colors.black.withValues(alpha: 0.3),
color: Colors.white.withValues(alpha: 0.15), Colors.black.withValues(alpha: 0.55),
],
),
),
child: Align(
alignment: Alignment.centerRight,
child: Padding(
padding: const EdgeInsets.only(right: 24),
child: Icon(
Icons.collections_bookmark,
size: 72,
color: Colors.white.withValues(alpha: 0.18),
),
), ),
), ),
), ),

View file

@ -184,15 +184,33 @@ class GarageScreenState extends State<GarageScreen> {
), ),
), ),
background: Container( background: Container(
decoration: const BoxDecoration(gradient: AppColors.brandGradient), decoration: BoxDecoration(
child: Align( image: const DecorationImage(
alignment: Alignment.centerRight, image: AssetImage('assets/img/login_bg.jpg'),
child: Padding( fit: BoxFit.cover,
padding: const EdgeInsets.only(right: 24), ),
child: Icon( color: Colors.black.withValues(alpha: 0.15),
Icons.directions_car_filled, ),
size: 72, child: Container(
color: Colors.white.withValues(alpha: 0.15), decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Colors.black.withValues(alpha: 0.3),
Colors.black.withValues(alpha: 0.55),
],
),
),
child: Align(
alignment: Alignment.centerRight,
child: Padding(
padding: const EdgeInsets.only(right: 24),
child: Icon(
Icons.directions_car_filled,
size: 72,
color: Colors.white.withValues(alpha: 0.18),
),
), ),
), ),
), ),

View file

@ -23,52 +23,68 @@ class ProfileScreen extends StatelessWidget {
expandedHeight: 200, expandedHeight: 200,
pinned: true, pinned: true,
flexibleSpace: FlexibleSpaceBar( flexibleSpace: FlexibleSpaceBar(
background: Container( background: Stack(
decoration: const BoxDecoration( fit: StackFit.expand,
gradient: AppColors.brandGradient, children: [
), Image.asset(
child: SafeArea( 'assets/img/login_bg.jpg',
child: Column( fit: BoxFit.cover,
mainAxisAlignment: MainAxisAlignment.center, ),
children: [ Container(
const SizedBox(height: 16), decoration: BoxDecoration(
// Avatar gradient: LinearGradient(
Container( begin: Alignment.topCenter,
width: 80, end: Alignment.bottomCenter,
height: 80, colors: [
decoration: BoxDecoration( Colors.black.withValues(alpha: 0.3),
shape: BoxShape.circle, Colors.black.withValues(alpha: 0.55),
border: Border.all(color: Colors.white, width: 3), ],
color: Colors.white.withValues(alpha: 0.2),
),
child: const Icon(
Icons.person,
size: 44,
color: Colors.white,
),
), ),
const SizedBox(height: 12), ),
Text( ),
email, SafeArea(
style: TextStyle( child: Column(
fontSize: 16, mainAxisAlignment: MainAxisAlignment.center,
fontWeight: FontWeight.w500, children: [
color: Colors.white.withValues(alpha: 0.95), const SizedBox(height: 16),
), // Avatar
), Container(
if (createdAt != null) ...[ width: 80,
const SizedBox(height: 4), height: 80,
Text( decoration: BoxDecoration(
'Member since ${_formatDate(createdAt)}', shape: BoxShape.circle,
style: TextStyle( border: Border.all(color: Colors.white, width: 3),
fontSize: 12, color: Colors.white.withValues(alpha: 0.2),
color: Colors.white.withValues(alpha: 0.7), ),
child: const Icon(
Icons.person,
size: 44,
color: Colors.white,
), ),
), ),
const SizedBox(height: 12),
Text(
email,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
color: Colors.white.withValues(alpha: 0.95),
),
),
if (createdAt != null) ...[
const SizedBox(height: 4),
Text(
'Member since ${_formatDate(createdAt)}',
style: TextStyle(
fontSize: 12,
color: Colors.white.withValues(alpha: 0.75),
),
),
],
], ],
], ),
), ),
), ],
), ),
), ),
), ),

View file

@ -1046,7 +1046,7 @@ packages:
source: hosted source: hosted
version: "2.3.1" version: "2.3.1"
url_launcher: url_launcher:
dependency: transitive dependency: "direct main"
description: description:
name: url_launcher name: url_launcher
sha256: f6a7e5c4835bb4e3026a04793a4199ca2d14c739ec378fdfe23fc8075d0439f8 sha256: f6a7e5c4835bb4e3026a04793a4199ca2d14c739ec378fdfe23fc8075d0439f8

View file

@ -46,6 +46,7 @@ dependencies:
image: ^4.2.0 image: ^4.2.0
package_info_plus: ^8.1.3 package_info_plus: ^8.1.3
shared_preferences: ^2.5.3 shared_preferences: ^2.5.3
url_launcher: ^6.3.1
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
@ -89,9 +90,13 @@ flutter:
flutter_native_splash: flutter_native_splash:
color: "#f9a11b" color: "#f9a11b"
color_dark: "#1a1a2e" color_dark: "#1a1a2e"
image: assets/icon/app_icon.png
image_dark: assets/icon/app_icon.png
android_12: android_12:
color: "#f9a11b" color: "#f9a11b"
color_dark: "#1a1a2e" color_dark: "#1a1a2e"
image: assets/icon/app_icon_foreground.png
image_dark: assets/icon/app_icon_foreground.png
# ── Launcher Icon config ── # ── Launcher Icon config ──
flutter_launcher_icons: flutter_launcher_icons:

View file

@ -29,6 +29,9 @@
<title>car64</title> <title>car64</title>
<link rel="manifest" href="manifest.json"> <link rel="manifest" href="manifest.json">
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport">
<style id="splash-screen-style"> <style id="splash-screen-style">
html { html {
height: 100% height: 100%
@ -100,9 +103,13 @@
document.body.style.background = "transparent"; document.body.style.background = "transparent";
} }
</script> </script>
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport">
</head> </head>
<body> <body>
<picture id="splash">
<source srcset="splash/img/light-1x.png 1x, splash/img/light-2x.png 2x, splash/img/light-3x.png 3x, splash/img/light-4x.png 4x" media="(prefers-color-scheme: light)">
<source srcset="splash/img/dark-1x.png 1x, splash/img/dark-2x.png 2x, splash/img/dark-3x.png 3x, splash/img/dark-4x.png 4x" media="(prefers-color-scheme: dark)">
<img class="center" aria-hidden="true" src="splash/img/light-1x.png" alt="">
</picture>
<!-- <!--
You can customize the "flutter_bootstrap.js" script. You can customize the "flutter_bootstrap.js" script.
This is useful to provide a custom configuration to the Flutter loader This is useful to provide a custom configuration to the Flutter loader