diff --git a/android/app/src/main/res/drawable-hdpi/ic_launcher_background.png b/android/app/src/main/res/drawable-hdpi/ic_launcher_background.png new file mode 100644 index 0000000..9f5cf02 Binary files /dev/null and b/android/app/src/main/res/drawable-hdpi/ic_launcher_background.png differ diff --git a/android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png b/android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..4875d9b Binary files /dev/null and b/android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png differ diff --git a/android/app/src/main/res/drawable-mdpi/ic_launcher_background.png b/android/app/src/main/res/drawable-mdpi/ic_launcher_background.png new file mode 100644 index 0000000..ad3a963 Binary files /dev/null and b/android/app/src/main/res/drawable-mdpi/ic_launcher_background.png differ diff --git a/android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png b/android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..989da5b Binary files /dev/null and b/android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png differ diff --git a/android/app/src/main/res/drawable-v21/launch_background.xml b/android/app/src/main/res/drawable-v21/launch_background.xml index f74085f..d3f4435 100644 --- a/android/app/src/main/res/drawable-v21/launch_background.xml +++ b/android/app/src/main/res/drawable-v21/launch_background.xml @@ -1,7 +1,7 @@ - + - + + #1A1A1A + \ No newline at end of file diff --git a/app-store-description.md b/app-store-description.md new file mode 100644 index 0000000..e518d99 --- /dev/null +++ b/app-store-description.md @@ -0,0 +1,116 @@ +# Timbre — App Store Listing + +## Promotional Text +*(170-char limit)* + +Your music, your server. Stream and download from any Subsonic or Navidrome library, control playback across your devices, and take it all offline. + +--- + +## Description +*(4,000-char limit)* + +**Timbre is a fast, beautiful music player for your own Subsonic or Navidrome server.** Connect to the library you host, and your entire collection comes with you — streamed on Wi‑Fi and cellular, or saved for offline listening wherever you go. + +Timbre pairs a distinctive, tactile design — an animated cassette on the Now Playing screen and accent colors drawn from each album's artwork — with the everyday features you actually reach for. + +**STREAM YOUR LIBRARY** +• Works with any Subsonic-compatible server, including Navidrome +• Browse albums, artists, tracks, and playlists +• Save multiple servers and switch between them in a tap +• Home screen with Resume, Recently Played, and fresh picks + +**FIND IT FAST** +• Search across your whole collection +• Filter and sort albums and tracks by genre, release year, and date added +• Mark favorites and organize with custom tags + +**LISTEN OFFLINE** +• Download individual tracks, albums, or whole playlists +• "Download all" for one-tap saving +• Set how many tracks download at once to match your connection +• Keeps playing when you lose signal + +**PLAYLISTS & TAGS** +• Create, rename, and reorder playlists +• Drag and drop to reorder your play queue +• Share playlists with other users on the same server +• Tag songs to group them your way + +**CONTROL ACROSS DEVICES** +• Use one device as a remote for another on the same Wi‑Fi +• Mirror the queue and browse, enqueue, and reorder from either device +• Secure, automatic pairing — no codes to type + +**BUILT FOR EVERYDAY PLAY** +• Background playback with lock screen and Control Center controls +• Gapless, high-quality audio +• Resume right where you left off + +Timbre never locks you in. There's no subscription and no catalog to rent — just a clean, thoughtful player for the music you already own and host. + +**Requires a Subsonic-compatible server (such as Navidrome). Timbre is a client only; you'll sign in with your own server details.** + +--- + +## Keywords +*(100-char limit, comma-separated, no spaces)* + +subsonic,navidrome,music,player,streaming,offline,download,playlist,FLAC,audio,server,remote,gapless + +--- + +## App Review Information + +### Sign-In Required +Yes — Timbre is a client for a self-hosted music server and requires server +credentials to sign in. + +**Demo server (fill in before submitting):** +- Server URL: `https://` +- Username: `` +- Password: `` + +### Contact Information +- First name: `` +- Last name: `` +- Phone: `` +- Email: `` + +### Notes for the Reviewer + +Thank you for reviewing Timbre. + +WHAT THIS APP IS +Timbre is a client for self-hosted, Subsonic-compatible music servers (such as +Navidrome). It does not host any music itself — it plays back a library that the +user runs on their own server. We have provided demo server credentials above so +you can review full functionality. + +HOW TO GET STARTED +1. Launch the app. On first run you will see the "Add server" / connect screen. +2. Enter the Server URL, Username, and Password from the demo credentials above. +3. Tap connect. The library (albums, artists, playlists) will load. + +WHAT TO TRY +- Browse the Home, Library, Playlists, and Favorites tabs. +- Tap any album or track to start playback; open Now Playing. +- Search the library and apply genre / year / date-added filters. +- Download a track for offline playback (Downloads tab). +- Create a playlist or tag a song. + +ABOUT PERMISSIONS / LOCAL NETWORK +Timbre includes an optional cross-device control feature: with two devices signed +into the same server on the same Wi-Fi network, one device can act as a remote for +the other. This is why the app requests Local Network access. The feature is +entirely optional — the app is fully functional on a single device without it, and +you may decline the Local Network prompt. + +PRIVACY / DATA +Timbre collects no analytics and has no third-party accounts. The password is used +only to authenticate to the user's own server (via Subsonic's standard token-based +auth) and is stored securely on-device. There are no in-app purchases or ads in +this version. + +Please contact us at the email above if you have any trouble connecting to the +demo server or need additional information. diff --git a/assets/icon/icon_full.png b/assets/icon/icon_full.png new file mode 100644 index 0000000..3243ce0 Binary files /dev/null and b/assets/icon/icon_full.png differ diff --git a/assets/icon/icon_transparent.png b/assets/icon/icon_transparent.png new file mode 100644 index 0000000..ed27b7d Binary files /dev/null and b/assets/icon/icon_transparent.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png index e242cfa..e906f68 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png index 5636f24..4274638 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png index 7ba8c40..da1f874 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png index 848b873..0805e57 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png index 9244754..4c81858 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png index 8b67ed5..db236df 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png index 5fdaa67..2a833ef 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png index 7ba8c40..da1f874 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png index 0eb0948..43fd827 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png index 235b4a4..6152024 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png index 7af6f76..f286a06 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png index c38bee4..39b4f22 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png index 0c1f833..ee5b187 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png index d9662ac..63fbac9 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png index 235b4a4..6152024 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png index c4af51e..df3286a 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png index 5c15681..eab6508 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png index d4d5d74..70f4fb6 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png index 8a2fdf9..c5ba434 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png index 89c2057..4669a61 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png index c4182e3..c94db60 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/ios/Runner/Base.lproj/LaunchScreen.storyboard b/ios/Runner/Base.lproj/LaunchScreen.storyboard index f2e259c..6ad3f10 100644 --- a/ios/Runner/Base.lproj/LaunchScreen.storyboard +++ b/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -19,7 +19,7 @@ - + diff --git a/lib/main.dart b/lib/main.dart index 5b328b7..3798281 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -10,6 +10,7 @@ import 'shell/app_shell.dart'; import 'state/providers.dart'; import 'theme/accent.dart'; import 'theme/app_theme.dart'; +import 'widgets/splash_screen.dart'; Future main() async { WidgetsFlutterBinding.ensureInitialized(); @@ -50,7 +51,9 @@ class TimbreApp extends ConsumerWidget { title: 'Timbre', debugShowCheckedModeBanner: false, theme: buildTimbreTheme(accent), - home: const AppShell(), + // AppShell mounts under the splash and boots behind it; the splash fades + // out after a couple of seconds. + home: const SplashGate(child: AppShell()), ); } } diff --git a/lib/widgets/splash_screen.dart b/lib/widgets/splash_screen.dart new file mode 100644 index 0000000..87ccc4f --- /dev/null +++ b/lib/widgets/splash_screen.dart @@ -0,0 +1,223 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; + +import '../theme/tokens.dart'; + +/// How long the splash stays up before it fades away. The app mounts *behind* +/// the splash (see [SplashGate]), so this window also masks real cold-start +/// work (settings load, discovery, reconnect). +const _splashHold = Duration(seconds: 2); +const _splashFade = Duration(milliseconds: 350); + +/// Holds the [child] app under a cassette splash overlay, then fades the +/// overlay out once [_splashHold] elapses. The child is live the whole time, +/// so it finishes booting behind the splash rather than after it. +class SplashGate extends StatefulWidget { + const SplashGate({super.key, required this.child}); + + final Widget child; + + @override + State createState() => _SplashGateState(); +} + +class _SplashGateState extends State { + Timer? _timer; + bool _fading = false; // began fading out + bool _gone = false; // fully removed from the tree + + @override + void initState() { + super.initState(); + _timer = Timer(_splashHold, () { + if (mounted) setState(() => _fading = true); + }); + } + + @override + void dispose() { + _timer?.cancel(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Stack( + children: [ + widget.child, + if (!_gone) + // Absorb taps while opaque; let them through once we start fading. + IgnorePointer( + ignoring: _fading, + child: AnimatedOpacity( + opacity: _fading ? 0 : 1, + duration: _splashFade, + curve: Curves.easeOut, + onEnd: () { + if (mounted && _fading) setState(() => _gone = true); + }, + child: const SplashScreen(), + ), + ), + ], + ); + } +} + +/// The full-screen cassette splash: a spinning cassette over the wordmark, on +/// Timbre's near-black canvas. Rebuilds nothing per frame beyond the two +/// spindle transforms. +class SplashScreen extends StatefulWidget { + const SplashScreen({super.key}); + + @override + State createState() => _SplashScreenState(); +} + +class _SplashScreenState extends State + with SingleTickerProviderStateMixin { + // One full turn every 3s; both reels spin clockwise like a real cassette. + late final AnimationController _spin = AnimationController( + vsync: this, + duration: const Duration(seconds: 3), + )..repeat(); + + @override + void dispose() { + _spin.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return ColoredBox( + color: TimbreColors.background, + child: Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox( + width: 240, + child: _SplashCassette(spin: _spin), + ), + const SizedBox(height: TimbreSpacing.xl), + Text( + 'TIMBRE', + style: Theme.of(context).textTheme.titleLarge?.copyWith( + color: TimbreColors.foreground, + fontWeight: FontWeight.w600, + letterSpacing: 8, + ), + ), + const SizedBox(height: TimbreSpacing.md), + // Accent hairline — the one lively color, kept small. + Container( + width: 32, + height: 2, + color: TimbreColors.accentDefault, + ), + const SizedBox(height: TimbreSpacing.md), + Text( + 'loading…', + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: TimbreColors.dimmed, + letterSpacing: 2, + ), + ), + ], + ), + ), + ); + } +} + +/// The cassette itself: static reels + shell (from the shared assets) with two +/// spinning spindles. Mirrors [CassetteView]'s layering and geometry but drops +/// the playback model — the reels sit at a fixed half-wound radius. +class _SplashCassette extends StatelessWidget { + const _SplashCassette({required this.spin}); + + final Animation spin; + + // Geometry measured off casette_shell.svg (viewBox 469×298), matching + // CassetteView's _CassetteConfig. + static const _viewBox = Size(469, 298); + static const _leftReel = Offset(135, 135); + static const _rightReel = Offset(334, 135); + static const _spindleSize = 36.0; + + @override + Widget build(BuildContext context) { + return AspectRatio( + aspectRatio: _viewBox.width / _viewBox.height, + child: FittedBox( + fit: BoxFit.contain, + child: SizedBox( + width: _viewBox.width, + height: _viewBox.height, + child: Stack( + children: [ + // 1. Static tape discs behind the reel holes. + Positioned.fill( + child: CustomPaint(painter: _ReelPainter()), + ), + // 2. Spinning cogs. + _spindle(_leftReel), + _spindle(_rightReel), + // 3. Static shell on top, framing the reels through its holes. + Positioned.fill( + child: SvgPicture.asset( + 'assets/casette/casette_shell.svg', + fit: BoxFit.fill, + ), + ), + ], + ), + ), + ), + ); + } + + Widget _spindle(Offset center) { + final cog = SvgPicture.asset( + 'assets/casette/spindle.svg', + width: _spindleSize, + height: _spindleSize, + ); + return Positioned( + left: center.dx - _spindleSize / 2, + top: center.dy - _spindleSize / 2, + width: _spindleSize, + height: _spindleSize, + child: RotationTransition(turns: spin, child: cog), + ); + } +} + +/// Paints the two wound-tape discs at a fixed half-wound radius, clipped to the +/// window band so they never touch the label. The shell (on top) masks them to +/// the reel holes + centre window. +class _ReelPainter extends CustomPainter { + static const _windowRect = Rect.fromLTRB(62.5, 98.5, 405.5, 172); + static const _leftReel = Offset(135, 135); + static const _rightReel = Offset(334, 135); + static const _radius = 62.0; // fixed ~half-wound (see CassetteView physics) + static const _tapeColor = Color(0xFF121212); + static const _padColor = Color(0xFF1A1A1A); + + @override + void paint(Canvas canvas, Size size) { + canvas.save(); + canvas.clipRect(_windowRect); + canvas.drawRect(_windowRect, Paint()..color = _padColor); + final tape = Paint()..color = _tapeColor; + canvas.drawCircle(_leftReel, _radius, tape); + canvas.drawCircle(_rightReel, _radius, tape); + canvas.restore(); + } + + @override + bool shouldRepaint(_ReelPainter oldDelegate) => false; +} diff --git a/pubspec.yaml b/pubspec.yaml index fd936f6..e643068 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -60,13 +60,19 @@ dev_dependencies: flutter_launcher_icons: ^0.14.4 flutter_launcher_icons: - image_path: "assets/icon/icon.png" + # Full-bleed square used for iOS and the Android legacy (pre-adaptive) icon. + image_path_ios: "assets/icon/icon_full.png" + image_path_android: "assets/icon/icon_full.png" android: true ios: true remove_alpha_ios: true - # Optional adaptive icon for Android (uncomment and provide a foreground PNG): - # adaptive_icon_background: "#FFFFFF" - # adaptive_icon_foreground: "assets/icon/icon_foreground.png" + min_sdk_android: 21 + # Android adaptive icon: the full-bleed artwork goes in the BACKGROUND layer, + # which fills the whole canvas and is only clipped by the launcher's mask shape + # (no inset). The foreground is transparent. This gives edge-to-edge coverage; + # round/squircle masks will clip the cassette's rotated corners (accepted). + adaptive_icon_background: "assets/icon/icon_full.png" + adaptive_icon_foreground: "assets/icon/icon_transparent.png" # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec