diff --git a/README.md b/README.md index 6bfb9b4..b93ddc8 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# ratune_mobile +# Timbre -A new Flutter project. +A Subsonic music client, built with Flutter. ## Getting Started diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index 3bf4a0f..73a9892 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -5,7 +5,7 @@ plugins { } android { - namespace = "com.conversionpath.ratune_mobile" + namespace = "com.laforrestchurch.timbre" compileSdk = flutter.compileSdkVersion ndkVersion = flutter.ndkVersion @@ -16,7 +16,7 @@ android { defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). - applicationId = "com.conversionpath.ratune_mobile" + applicationId = "com.laforrestchurch.timbre" // You can update the following values to match your application needs. // For more information, see: https://flutter.dev/to/review-gradle-config. minSdk = flutter.minSdkVersion diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 95d4f0d..5c70417 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -5,7 +5,7 @@ /dev/null || echo 0) + flutter build ipa --release \ + --build-number=$(( BUILD_NUMBER + 1 )) \ + --export-options-plist=/Users/builder/export_options.plist artifacts: - - build/ios/iphoneos/*.app - - flutter_drive.log - - # --- To produce a signed, uploadable IPA instead: configure App Store - # --- Connect + signing in Codemagic (Teams → Integrations → App Store - # --- Connect), then add `ios_signing` to `environment` and replace the - # --- build step with: - # - name: Set up provisioning profiles - # script: xcode-project use-profiles - # - name: Build IPA - # script: flutter build ipa --release --export-options-plist=/Users/builder/export_options.plist - # --- with artifacts: build/ios/ipa/*.ipa and - # --- publishing.app_store_connect.submit_to_testflight: true + - build/ios/ipa/*.ipa + publishing: + app_store_connect: + auth: integration + submit_to_testflight: true + # Internal testers automatically receive every build once Apple finishes + # processing (no Beta App Review). To also push to a named group, + # uncomment and match the group name from App Store Connect → TestFlight: + # beta_groups: + # - Internal Testers diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 206b28d..52fbdb1 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -382,7 +382,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.conversionpath.ratuneMobile; + PRODUCT_BUNDLE_IDENTIFIER = com.laforrestchurch.timbre; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; @@ -398,7 +398,7 @@ CURRENT_PROJECT_VERSION = 1; GENERATE_INFOPLIST_FILE = YES; MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.conversionpath.ratuneMobile.RunnerTests; + PRODUCT_BUNDLE_IDENTIFIER = com.laforrestchurch.timbre.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -415,7 +415,7 @@ CURRENT_PROJECT_VERSION = 1; GENERATE_INFOPLIST_FILE = YES; MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.conversionpath.ratuneMobile.RunnerTests; + PRODUCT_BUNDLE_IDENTIFIER = com.laforrestchurch.timbre.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; @@ -430,7 +430,7 @@ CURRENT_PROJECT_VERSION = 1; GENERATE_INFOPLIST_FILE = YES; MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.conversionpath.ratuneMobile.RunnerTests; + PRODUCT_BUNDLE_IDENTIFIER = com.laforrestchurch.timbre.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; @@ -561,7 +561,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.conversionpath.ratuneMobile; + PRODUCT_BUNDLE_IDENTIFIER = com.laforrestchurch.timbre; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -583,7 +583,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.conversionpath.ratuneMobile; + PRODUCT_BUNDLE_IDENTIFIER = com.laforrestchurch.timbre; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index 3df94db..0e2efad 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -11,7 +11,7 @@ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleDisplayName - Ratune Mobile + Timbre CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier @@ -19,7 +19,7 @@ CFBundleInfoDictionaryVersion 6.0 CFBundleName - ratune_mobile + timbre CFBundlePackageType APPL CFBundleShortVersionString diff --git a/lib/downloads/download_manager.dart b/lib/downloads/download_manager.dart index d7b65a2..9f49255 100644 --- a/lib/downloads/download_manager.dart +++ b/lib/downloads/download_manager.dart @@ -129,7 +129,7 @@ class DownloadController extends StateNotifier { final Dio _dio = Dio(BaseOptions( receiveTimeout: const Duration(minutes: 5), - headers: {'User-Agent': 'ratune-mobile'}, + headers: {'User-Agent': 'timbre'}, )); static const int _maxConcurrent = 3; diff --git a/lib/main.dart b/lib/main.dart index 9055442..03de738 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -19,28 +19,28 @@ Future main() async { // hang we still start the app (playback just loses lock-screen controls). try { await JustAudioBackground.init( - androidNotificationChannelId: 'com.conversionpath.ratune_mobile.audio', - androidNotificationChannelName: 'Ratune playback', + androidNotificationChannelId: 'com.laforrestchurch.timbre.audio', + androidNotificationChannelName: 'Timbre playback', androidNotificationOngoing: true, ).timeout(const Duration(seconds: 8)); } catch (e, st) { debugPrint('JustAudioBackground.init failed: $e\n$st'); } } - runApp(const ProviderScope(child: RatuneApp())); + runApp(const ProviderScope(child: TimbreApp())); } -class RatuneApp extends ConsumerWidget { - const RatuneApp({super.key}); +class TimbreApp extends ConsumerWidget { + const TimbreApp({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { // The theme rebuilds whenever the album-art accent changes. final accent = ref.watch(accentProvider); return MaterialApp( - title: 'Ratune', + title: 'Timbre', debugShowCheckedModeBanner: false, - theme: buildRatuneTheme(accent), + theme: buildTimbreTheme(accent), home: const AppShell(), ); } diff --git a/lib/screens/add_to_playlist_sheet.dart b/lib/screens/add_to_playlist_sheet.dart index bd593b3..95a8c08 100644 --- a/lib/screens/add_to_playlist_sheet.dart +++ b/lib/screens/add_to_playlist_sheet.dart @@ -13,7 +13,7 @@ Future showAddToPlaylistSheet( }) { return showModalBottomSheet( context: context, - backgroundColor: RatuneColors.background, + backgroundColor: TimbreColors.background, isScrollControlled: true, builder: (_) => _AddToPlaylistSheet(songs: songs), ); @@ -32,24 +32,24 @@ class _AddToPlaylistSheet extends ConsumerWidget { return SafeArea( child: Padding( - padding: const EdgeInsets.symmetric(vertical: RatuneSpacing.lg), + padding: const EdgeInsets.symmetric(vertical: TimbreSpacing.lg), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Padding( padding: - const EdgeInsets.symmetric(horizontal: RatuneSpacing.xl), + const EdgeInsets.symmetric(horizontal: TimbreSpacing.xl), child: Text('Add to playlist', style: TextStyle(color: accent, fontWeight: FontWeight.w700)), ), - const SizedBox(height: RatuneSpacing.md), + const SizedBox(height: TimbreSpacing.md), if (!connected) const Padding( - padding: EdgeInsets.all(RatuneSpacing.xl), + padding: EdgeInsets.all(TimbreSpacing.xl), child: Text('Connect to a server to manage playlists.', - style: TextStyle(color: RatuneColors.dimmed)), + style: TextStyle(color: TimbreColors.dimmed)), ) else ...[ _Tile( @@ -123,21 +123,21 @@ class _Tile extends StatelessWidget { onTap: onTap, child: Container( constraints: - const BoxConstraints(minHeight: RatuneSpacing.minTouchTarget), - padding: const EdgeInsets.symmetric(horizontal: RatuneSpacing.xl), + const BoxConstraints(minHeight: TimbreSpacing.minTouchTarget), + padding: const EdgeInsets.symmetric(horizontal: TimbreSpacing.xl), child: Row( children: [ - Icon(icon, size: 20, color: accent ?? RatuneColors.dimmed), - const SizedBox(width: RatuneSpacing.lg), + Icon(icon, size: 20, color: accent ?? TimbreColors.dimmed), + const SizedBox(width: TimbreSpacing.lg), Expanded( child: Text(label, maxLines: 1, overflow: TextOverflow.ellipsis, - style: TextStyle(color: accent ?? RatuneColors.foreground)), + style: TextStyle(color: accent ?? TimbreColors.foreground)), ), if (trailing != null) Text(trailing!, - style: const TextStyle(color: RatuneColors.dimmed)), + style: const TextStyle(color: TimbreColors.dimmed)), ], ), ), @@ -158,15 +158,15 @@ Future promptPlaylistName( builder: (ctx) { final accent = Theme.of(ctx).colorScheme.primary; return AlertDialog( - backgroundColor: RatuneColors.surface, + backgroundColor: TimbreColors.surface, title: Text(title), content: TextField( controller: controller, autofocus: true, - style: const TextStyle(color: RatuneColors.foreground), + style: const TextStyle(color: TimbreColors.foreground), decoration: const InputDecoration( hintText: 'Playlist name', - hintStyle: TextStyle(color: RatuneColors.dimmed), + hintStyle: TextStyle(color: TimbreColors.dimmed), ), onSubmitted: (v) => Navigator.pop(ctx, v.trim()), ), diff --git a/lib/screens/browser_screen.dart b/lib/screens/browser_screen.dart index fe13fd1..fb2eea1 100644 --- a/lib/screens/browser_screen.dart +++ b/lib/screens/browser_screen.dart @@ -25,17 +25,17 @@ class BrowserScreen extends ConsumerWidget { return Padding( padding: const EdgeInsets.fromLTRB( - RatuneSpacing.lg, - RatuneSpacing.xl, - RatuneSpacing.lg, - RatuneSpacing.lg, + TimbreSpacing.lg, + TimbreSpacing.xl, + TimbreSpacing.lg, + TimbreSpacing.lg, ), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Wrap( - spacing: RatuneSpacing.xl, - runSpacing: RatuneSpacing.xs, + spacing: TimbreSpacing.xl, + runSpacing: TimbreSpacing.xs, children: [ _Action( icon: Icons.search, @@ -67,9 +67,9 @@ class BrowserScreen extends ConsumerWidget { ), ], ), - const SizedBox(height: RatuneSpacing.md), + const SizedBox(height: TimbreSpacing.md), _ModeSelector(mode: mode), - const SizedBox(height: RatuneSpacing.lg), + const SizedBox(height: TimbreSpacing.lg), Expanded( child: client == null ? const HairlinePanel( @@ -105,13 +105,13 @@ class _ModeSelector extends ConsumerWidget { onTap: () => ref.read(browseModeProvider.notifier).state = m, child: Container( constraints: - const BoxConstraints(minHeight: RatuneSpacing.minTouchTarget), - padding: const EdgeInsets.symmetric(horizontal: RatuneSpacing.md), + const BoxConstraints(minHeight: TimbreSpacing.minTouchTarget), + padding: const EdgeInsets.symmetric(horizontal: TimbreSpacing.md), alignment: Alignment.center, child: Text( text, style: TextStyle( - color: active ? RatuneColors.foreground : RatuneColors.dimmed, + color: active ? TimbreColors.foreground : TimbreColors.dimmed, fontWeight: active ? FontWeight.w700 : FontWeight.w400, decoration: active ? TextDecoration.underline : TextDecoration.none, @@ -127,9 +127,9 @@ class _ModeSelector extends ConsumerWidget { mainAxisAlignment: MainAxisAlignment.center, children: [ label('Artists', BrowseMode.artists), - const Text('|', style: TextStyle(color: RatuneColors.border)), + const Text('|', style: TextStyle(color: TimbreColors.border)), label('Albums', BrowseMode.albums), - const Text('|', style: TextStyle(color: RatuneColors.border)), + const Text('|', style: TextStyle(color: TimbreColors.border)), label('Tracks', BrowseMode.tracks), ], ); @@ -148,7 +148,7 @@ class _ArtistsPanel extends ConsumerWidget { trailing: artists.hasValue && artists.value!.isNotEmpty ? '(${artists.value!.length})' : null, - padding: const EdgeInsets.symmetric(vertical: RatuneSpacing.md), + padding: const EdgeInsets.symmetric(vertical: TimbreSpacing.md), child: artists.when( loading: () => const _Centered(child: _Loading()), error: (e, _) => _Centered(child: _ErrorText('$e')), @@ -186,7 +186,7 @@ class _AlbumsPanel extends ConsumerWidget { trailing: albums.hasValue && albums.value!.isNotEmpty ? '(${albums.value!.length})' : null, - padding: const EdgeInsets.all(RatuneSpacing.md), + padding: const EdgeInsets.all(TimbreSpacing.md), child: albums.when( loading: () => const _Centered(child: _Loading()), error: (e, _) => _Centered(child: _ErrorText('$e')), @@ -200,8 +200,8 @@ class _AlbumsPanel extends ConsumerWidget { gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: cols, - mainAxisSpacing: RatuneSpacing.md, - crossAxisSpacing: RatuneSpacing.md, + mainAxisSpacing: TimbreSpacing.md, + crossAxisSpacing: TimbreSpacing.md, // Square art + two caption lines; extra vertical slack so // the tile never sub-pixel-overflows. childAspectRatio: 0.68, @@ -241,7 +241,7 @@ class _AlbumTile extends StatelessWidget { AspectRatio( aspectRatio: 1, child: ColoredBox( - color: RatuneColors.surface, + color: TimbreColors.surface, child: artUri != null ? Image.network( artUri!, @@ -253,12 +253,12 @@ class _AlbumTile extends StatelessWidget { : const _AlbumArtFallback(), ), ), - const SizedBox(height: RatuneSpacing.xs), + const SizedBox(height: TimbreSpacing.xs), Text( album.name ?? 'Unknown album', maxLines: 1, overflow: TextOverflow.ellipsis, - style: const TextStyle(color: RatuneColors.foreground), + style: const TextStyle(color: TimbreColors.foreground), ), if (album.artist != null) Text( @@ -266,7 +266,7 @@ class _AlbumTile extends StatelessWidget { maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle( - color: RatuneColors.dimmed, fontSize: 12), + color: TimbreColors.dimmed, fontSize: 12), ), ], ), @@ -279,7 +279,7 @@ class _AlbumArtFallback extends StatelessWidget { @override Widget build(BuildContext context) => const Center( child: Icon(Icons.album_outlined, - color: RatuneColors.dimmed, size: 32), + color: TimbreColors.dimmed, size: 32), ); } @@ -317,8 +317,8 @@ class _TracksPanelState extends ConsumerState<_TracksPanel> { mainAxisSize: MainAxisSize.min, children: [ const _Loading(), - const SizedBox(height: RatuneSpacing.md), - Text(label, style: const TextStyle(color: RatuneColors.dimmed)), + const SizedBox(height: TimbreSpacing.md), + Text(label, style: const TextStyle(color: TimbreColors.dimmed)), ], ), ); @@ -357,7 +357,7 @@ class _TracksPanelState extends ConsumerState<_TracksPanel> { title: 'Tracks', active: true, trailing: index.songs.isNotEmpty ? '(${index.songs.length})' : null, - padding: const EdgeInsets.symmetric(vertical: RatuneSpacing.md), + padding: const EdgeInsets.symmetric(vertical: TimbreSpacing.md), child: Column( children: [ Align( @@ -368,11 +368,11 @@ class _TracksPanelState extends ConsumerState<_TracksPanel> { : () => ref.read(libraryIndexProvider.notifier).refresh(), child: const Padding( padding: EdgeInsets.symmetric( - horizontal: RatuneSpacing.lg, - vertical: RatuneSpacing.sm, + horizontal: TimbreSpacing.lg, + vertical: TimbreSpacing.sm, ), child: Text('↻ refresh', - style: TextStyle(color: RatuneColors.dimmed)), + style: TextStyle(color: TimbreColors.dimmed)), ), ), ), @@ -395,13 +395,13 @@ class _Action extends StatelessWidget { return InkWell( onTap: onTap, child: Padding( - padding: const EdgeInsets.symmetric(vertical: RatuneSpacing.sm), + padding: const EdgeInsets.symmetric(vertical: TimbreSpacing.sm), child: Row( mainAxisSize: MainAxisSize.min, children: [ - Icon(icon, size: 16, color: RatuneColors.dimmed), - const SizedBox(width: RatuneSpacing.sm), - Text(label, style: const TextStyle(color: RatuneColors.foreground)), + Icon(icon, size: 16, color: TimbreColors.dimmed), + const SizedBox(width: TimbreSpacing.sm), + Text(label, style: const TextStyle(color: TimbreColors.foreground)), ], ), ), @@ -561,33 +561,33 @@ class BrowseRow extends StatelessWidget { onTap: onTap, child: Container( constraints: - const BoxConstraints(minHeight: RatuneSpacing.minTouchTarget), - padding: const EdgeInsets.only(left: RatuneSpacing.lg), + const BoxConstraints(minHeight: TimbreSpacing.minTouchTarget), + padding: const EdgeInsets.only(left: TimbreSpacing.lg), child: Row( children: [ if (leading != null) SizedBox( width: 28, child: Text(leading!, - style: const TextStyle(color: RatuneColors.dimmed)), + style: const TextStyle(color: TimbreColors.dimmed)), ), Expanded( child: Text( title, maxLines: 1, overflow: TextOverflow.ellipsis, - style: const TextStyle(color: RatuneColors.foreground), + style: const TextStyle(color: TimbreColors.foreground), ), ), if (isDone) Padding( - padding: const EdgeInsets.only(left: RatuneSpacing.sm), + padding: const EdgeInsets.only(left: TimbreSpacing.sm), child: Icon(Icons.download_done, size: 14, color: accent), ) else if (isActive) const Padding( - padding: EdgeInsets.only(left: RatuneSpacing.sm), + padding: EdgeInsets.only(left: TimbreSpacing.sm), child: SizedBox( width: 12, height: 12, @@ -595,9 +595,9 @@ class BrowseRow extends StatelessWidget { ), ), if (trailing != null) ...[ - const SizedBox(width: RatuneSpacing.md), + const SizedBox(width: TimbreSpacing.md), Text(trailing!, - style: const TextStyle(color: RatuneColors.dimmed)), + style: const TextStyle(color: TimbreColors.dimmed)), ], if (onPlayNext != null) _RowIcon(icon: Icons.playlist_play, onTap: onPlayNext!), @@ -612,7 +612,7 @@ class BrowseRow extends StatelessWidget { onRemoveDownload: onRemoveDownload, ), if (onPlayNext == null && onAddToQueue == null && !_hasMenu) - const SizedBox(width: RatuneSpacing.lg), + const SizedBox(width: TimbreSpacing.lg), ], ), ), @@ -639,11 +639,11 @@ class _RowMenu extends StatelessWidget { @override Widget build(BuildContext context) { return PopupMenuButton( - icon: const Icon(Icons.more_vert, size: 20, color: RatuneColors.dimmed), - color: RatuneColors.surface, + icon: const Icon(Icons.more_vert, size: 20, color: TimbreColors.dimmed), + color: TimbreColors.surface, padding: EdgeInsets.zero, constraints: - const BoxConstraints(minWidth: RatuneSpacing.minTouchTarget), + const BoxConstraints(minWidth: TimbreSpacing.minTouchTarget), onSelected: (v) { switch (v) { case 'playlist': @@ -684,9 +684,9 @@ class _RowIcon extends StatelessWidget { onTap: onTap, customBorder: const CircleBorder(), child: SizedBox( - width: RatuneSpacing.minTouchTarget, - height: RatuneSpacing.minTouchTarget, - child: Icon(icon, size: 20, color: RatuneColors.dimmed), + width: TimbreSpacing.minTouchTarget, + height: TimbreSpacing.minTouchTarget, + child: Icon(icon, size: 20, color: TimbreColors.dimmed), ), ); } @@ -728,11 +728,11 @@ class _NotConnected extends StatelessWidget { mainAxisSize: MainAxisSize.min, children: [ Text('Not connected.', - style: TextStyle(color: RatuneColors.foreground)), - SizedBox(height: RatuneSpacing.sm), + style: TextStyle(color: TimbreColors.foreground)), + SizedBox(height: TimbreSpacing.sm), Text('Tap the status bar to add a Subsonic server.', textAlign: TextAlign.center, - style: TextStyle(color: RatuneColors.dimmed)), + style: TextStyle(color: TimbreColors.dimmed)), ], ), ); @@ -744,7 +744,7 @@ class _Centered extends StatelessWidget { final Widget child; @override Widget build(BuildContext context) => Padding( - padding: const EdgeInsets.all(RatuneSpacing.xl), + padding: const EdgeInsets.all(TimbreSpacing.xl), child: Center(child: child), ); } @@ -766,7 +766,7 @@ class _ErrorText extends StatelessWidget { Widget build(BuildContext context) => Text( message, textAlign: TextAlign.center, - style: const TextStyle(color: RatuneColors.dimmed), + style: const TextStyle(color: TimbreColors.dimmed), ); } diff --git a/lib/screens/connect_sheet.dart b/lib/screens/connect_sheet.dart index 181590a..741cbf8 100644 --- a/lib/screens/connect_sheet.dart +++ b/lib/screens/connect_sheet.dart @@ -9,7 +9,7 @@ import '../theme/tokens.dart'; Future showConnectSheet(BuildContext context) { return showModalBottomSheet( context: context, - backgroundColor: RatuneColors.background, + backgroundColor: TimbreColors.background, isScrollControlled: true, builder: (_) => const _ConnectSheet(), ); @@ -66,10 +66,10 @@ class _ConnectSheetState extends ConsumerState<_ConnectSheet> { final accent = Theme.of(context).colorScheme.primary; return Padding( padding: EdgeInsets.only( - left: RatuneSpacing.xl, - right: RatuneSpacing.xl, - top: RatuneSpacing.xl, - bottom: MediaQuery.of(context).viewInsets.bottom + RatuneSpacing.xl, + left: TimbreSpacing.xl, + right: TimbreSpacing.xl, + top: TimbreSpacing.xl, + bottom: MediaQuery.of(context).viewInsets.bottom + TimbreSpacing.xl, ), child: Column( mainAxisSize: MainAxisSize.min, @@ -77,22 +77,22 @@ class _ConnectSheetState extends ConsumerState<_ConnectSheet> { children: [ Text('Connect to server', style: TextStyle(color: accent, fontWeight: FontWeight.w700)), - const SizedBox(height: RatuneSpacing.lg), + const SizedBox(height: TimbreSpacing.lg), _field(_url, 'Server URL', 'https://navidrome.example.com', keyboard: TextInputType.url), _field(_user, 'Username', 'you'), _field(_pass, 'Password', '••••••••', obscure: true), if (conn.status == ConnStatus.error && conn.error != null) ...[ - const SizedBox(height: RatuneSpacing.md), + const SizedBox(height: TimbreSpacing.md), Text(conn.error!, style: const TextStyle(color: Color(0xFFE06C75))), ], - const SizedBox(height: RatuneSpacing.lg), + const SizedBox(height: TimbreSpacing.lg), FilledButton( onPressed: _busy ? null : _connect, style: FilledButton.styleFrom( backgroundColor: accent, - foregroundColor: RatuneColors.background, + foregroundColor: TimbreColors.background, shape: const RoundedRectangleBorder(), ), child: _busy @@ -116,22 +116,22 @@ class _ConnectSheetState extends ConsumerState<_ConnectSheet> { TextInputType? keyboard, }) { return Padding( - padding: const EdgeInsets.only(bottom: RatuneSpacing.md), + padding: const EdgeInsets.only(bottom: TimbreSpacing.md), child: TextField( controller: c, obscureText: obscure, keyboardType: keyboard, autocorrect: false, enableSuggestions: false, - style: const TextStyle(color: RatuneColors.foreground), + style: const TextStyle(color: TimbreColors.foreground), decoration: InputDecoration( labelText: label, hintText: hint, - labelStyle: const TextStyle(color: RatuneColors.dimmed), - hintStyle: const TextStyle(color: RatuneColors.dimmed), + labelStyle: const TextStyle(color: TimbreColors.dimmed), + hintStyle: const TextStyle(color: TimbreColors.dimmed), enabledBorder: const OutlineInputBorder( borderRadius: BorderRadius.zero, - borderSide: BorderSide(color: RatuneColors.border), + borderSide: BorderSide(color: TimbreColors.border), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.zero, diff --git a/lib/screens/downloads_screen.dart b/lib/screens/downloads_screen.dart index 560af8a..b3d56c6 100644 --- a/lib/screens/downloads_screen.dart +++ b/lib/screens/downloads_screen.dart @@ -29,7 +29,7 @@ class DownloadsScreen extends ConsumerWidget { TextButton( onPressed: () => _confirmClear(context, controller), child: const Text('Clear all', - style: TextStyle(color: RatuneColors.dimmed)), + style: TextStyle(color: TimbreColors.dimmed)), ), ], ), @@ -37,24 +37,24 @@ class DownloadsScreen extends ConsumerWidget { child: (active.isEmpty && completed.isEmpty) ? const Center( child: Text('No downloads yet.', - style: TextStyle(color: RatuneColors.dimmed)), + style: TextStyle(color: TimbreColors.dimmed)), ) : ListView( - padding: const EdgeInsets.all(RatuneSpacing.lg), + padding: const EdgeInsets.all(TimbreSpacing.lg), children: [ if (active.isNotEmpty) ...[ HairlinePanel( title: 'Downloading', trailing: '(${active.length})', padding: const EdgeInsets.symmetric( - vertical: RatuneSpacing.md), + vertical: TimbreSpacing.md), child: Column( children: [ for (final d in active) _ActiveRow(info: d), ], ), ), - const SizedBox(height: RatuneSpacing.xl), + const SizedBox(height: TimbreSpacing.xl), ], HairlinePanel( title: 'Saved', @@ -63,12 +63,12 @@ class DownloadsScreen extends ConsumerWidget { ? null : '${completed.length} · ${_fmtBytes(downloads.totalBytes)}', padding: - const EdgeInsets.symmetric(vertical: RatuneSpacing.md), + const EdgeInsets.symmetric(vertical: TimbreSpacing.md), child: completed.isEmpty ? const Padding( - padding: EdgeInsets.all(RatuneSpacing.lg), + padding: EdgeInsets.all(TimbreSpacing.lg), child: Text('Nothing saved for offline yet.', - style: TextStyle(color: RatuneColors.dimmed)), + style: TextStyle(color: TimbreColors.dimmed)), ) : Column( children: [ @@ -93,7 +93,7 @@ class DownloadsScreen extends ConsumerWidget { final ok = await showDialog( context: context, builder: (ctx) => AlertDialog( - backgroundColor: RatuneColors.surface, + backgroundColor: TimbreColors.surface, title: const Text('Remove all downloads?'), content: const Text( 'This deletes every saved file for this server. It cannot be undone.'), @@ -121,7 +121,7 @@ class _ActiveRow extends StatelessWidget { final failed = info.status == DownloadStatus.failed; return Padding( padding: const EdgeInsets.symmetric( - horizontal: RatuneSpacing.lg, vertical: RatuneSpacing.xs), + horizontal: TimbreSpacing.lg, vertical: TimbreSpacing.xs), child: Row( children: [ Expanded( @@ -131,8 +131,8 @@ class _ActiveRow extends StatelessWidget { Text(info.song.title ?? 'Untitled', maxLines: 1, overflow: TextOverflow.ellipsis, - style: const TextStyle(color: RatuneColors.foreground)), - const SizedBox(height: RatuneSpacing.xs), + style: const TextStyle(color: TimbreColors.foreground)), + const SizedBox(height: TimbreSpacing.xs), if (failed) const Text('Failed', style: TextStyle(color: Color(0xFFE06C75), fontSize: 12)) @@ -140,18 +140,18 @@ class _ActiveRow extends StatelessWidget { LinearProgressIndicator( value: info.progress > 0 ? info.progress : null, minHeight: 3, - backgroundColor: RatuneColors.border, + backgroundColor: TimbreColors.border, color: accent, ), ], ), ), - const SizedBox(width: RatuneSpacing.md), + const SizedBox(width: TimbreSpacing.md), Text( failed ? '—' : (info.status == DownloadStatus.queued ? 'Queued' : ''), - style: const TextStyle(color: RatuneColors.dimmed, fontSize: 12), + style: const TextStyle(color: TimbreColors.dimmed, fontSize: 12), ), ], ), @@ -178,8 +178,8 @@ class _SavedRow extends StatelessWidget { onTap: onPlay, child: Container( constraints: - const BoxConstraints(minHeight: RatuneSpacing.minTouchTarget), - padding: const EdgeInsets.only(left: RatuneSpacing.lg), + const BoxConstraints(minHeight: TimbreSpacing.minTouchTarget), + padding: const EdgeInsets.only(left: TimbreSpacing.lg), child: Row( children: [ Expanded( @@ -190,7 +190,7 @@ class _SavedRow extends StatelessWidget { Text(info.song.title ?? 'Untitled', maxLines: 1, overflow: TextOverflow.ellipsis, - style: const TextStyle(color: RatuneColors.foreground)), + style: const TextStyle(color: TimbreColors.foreground)), Text( [ info.song.artist, @@ -199,7 +199,7 @@ class _SavedRow extends StatelessWidget { maxLines: 1, overflow: TextOverflow.ellipsis, style: - const TextStyle(color: RatuneColors.dimmed, fontSize: 12), + const TextStyle(color: TimbreColors.dimmed, fontSize: 12), ), ], ), @@ -208,10 +208,10 @@ class _SavedRow extends StatelessWidget { onTap: onRemove, customBorder: const CircleBorder(), child: const SizedBox( - width: RatuneSpacing.minTouchTarget, - height: RatuneSpacing.minTouchTarget, + width: TimbreSpacing.minTouchTarget, + height: TimbreSpacing.minTouchTarget, child: Icon(Icons.delete_outline, - size: 20, color: RatuneColors.dimmed), + size: 20, color: TimbreColors.dimmed), ), ), ], diff --git a/lib/screens/favorites_screen.dart b/lib/screens/favorites_screen.dart index aa11911..62fc26e 100644 --- a/lib/screens/favorites_screen.dart +++ b/lib/screens/favorites_screen.dart @@ -28,13 +28,13 @@ class FavoritesScreen extends ConsumerWidget { ), error: (e, _) => Center( child: Text('$e', - style: const TextStyle(color: RatuneColors.dimmed)), + style: const TextStyle(color: TimbreColors.dimmed)), ), data: (s) { if (s.songs.isEmpty && s.albums.isEmpty && s.artists.isEmpty) { return const Center( child: Text('No favorites yet.', - style: TextStyle(color: RatuneColors.dimmed)), + style: TextStyle(color: TimbreColors.dimmed)), ); } return ListView( @@ -96,10 +96,10 @@ class _SectionLabel extends StatelessWidget { @override Widget build(BuildContext context) => Padding( padding: const EdgeInsets.fromLTRB( - RatuneSpacing.lg, - RatuneSpacing.lg, - RatuneSpacing.lg, - RatuneSpacing.sm, + TimbreSpacing.lg, + TimbreSpacing.lg, + TimbreSpacing.lg, + TimbreSpacing.sm, ), child: Text( text, diff --git a/lib/screens/home_screen.dart b/lib/screens/home_screen.dart index 9a04f98..442b1e8 100644 --- a/lib/screens/home_screen.dart +++ b/lib/screens/home_screen.dart @@ -29,14 +29,14 @@ class HomeScreen extends ConsumerWidget { return ListView( padding: const EdgeInsets.fromLTRB( - RatuneSpacing.lg, - RatuneSpacing.xl, - RatuneSpacing.lg, - RatuneSpacing.lg, + TimbreSpacing.lg, + TimbreSpacing.xl, + TimbreSpacing.lg, + TimbreSpacing.lg, ), children: [ const _HeroCard(), - const SizedBox(height: RatuneSpacing.xl), + const SizedBox(height: TimbreSpacing.xl), // Recently Played — from local history. if (recentAlbums.isNotEmpty) @@ -93,10 +93,10 @@ class HomeScreen extends ConsumerWidget { if (recentAlbums.isEmpty && client == null) const Padding( - padding: EdgeInsets.only(top: RatuneSpacing.xl), + padding: EdgeInsets.only(top: TimbreSpacing.xl), child: Text( 'Connect to a server and start listening — your home fills in as you play.', - style: TextStyle(color: RatuneColors.dimmed), + style: TextStyle(color: TimbreColors.dimmed), ), ), ], @@ -143,11 +143,11 @@ class _HeroCard extends ConsumerWidget { child: const Row( children: [ Icon(Icons.library_music_outlined, - color: RatuneColors.dimmed, size: 40), - SizedBox(width: RatuneSpacing.lg), + color: TimbreColors.dimmed, size: 40), + SizedBox(width: TimbreSpacing.lg), Expanded( child: Text('Browse your library to start listening', - style: TextStyle(color: RatuneColors.foreground)), + style: TextStyle(color: TimbreColors.foreground)), ), ], ), @@ -170,19 +170,19 @@ class _HeroCard extends ConsumerWidget { width: 64, height: 64, child: ColoredBox( - color: RatuneColors.surface, + color: TimbreColors.surface, child: artUri != null ? Image.network(artUri, key: ValueKey(artUri), fit: BoxFit.cover, gaplessPlayback: true, errorBuilder: (_, _, _) => const Icon( - Icons.album_outlined, color: RatuneColors.dimmed)) + Icons.album_outlined, color: TimbreColors.dimmed)) : const Icon(Icons.album_outlined, - color: RatuneColors.dimmed), + color: TimbreColors.dimmed), ), ), - const SizedBox(width: RatuneSpacing.lg), + const SizedBox(width: TimbreSpacing.lg), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -192,7 +192,7 @@ class _HeroCard extends ConsumerWidget { children: [ Icon(hasCurrent ? Icons.play_arrow : Icons.history, size: 14, color: accent), - const SizedBox(width: RatuneSpacing.xs), + const SizedBox(width: TimbreSpacing.xs), Text(hasCurrent ? 'NOW PLAYING' : 'RESUME', style: TextStyle( color: accent, @@ -201,20 +201,20 @@ class _HeroCard extends ConsumerWidget { fontWeight: FontWeight.w700)), ], ), - const SizedBox(height: RatuneSpacing.xs), + const SizedBox(height: TimbreSpacing.xs), Text(title, maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle( - color: RatuneColors.foreground, + color: TimbreColors.foreground, fontWeight: FontWeight.w700)), if (subtitle != null) Text(subtitle, maxLines: 1, overflow: TextOverflow.ellipsis, - style: const TextStyle(color: RatuneColors.dimmed)), + style: const TextStyle(color: TimbreColors.dimmed)), if (hasCurrent) ...[ - const SizedBox(height: RatuneSpacing.md), + const SizedBox(height: TimbreSpacing.md), BlockProgressBar(progress: progress, cells: 32, height: 6), ], ], @@ -239,10 +239,10 @@ class _HeroShell extends StatelessWidget { return InkWell( onTap: onTap, child: Container( - padding: const EdgeInsets.all(RatuneSpacing.lg), + padding: const EdgeInsets.all(TimbreSpacing.lg), decoration: BoxDecoration( - color: RatuneColors.surface, - border: Border.all(color: RatuneColors.borderActive), + color: TimbreColors.surface, + border: Border.all(color: TimbreColors.borderActive), ), child: child, ), @@ -263,19 +263,19 @@ class _Shelf extends StatelessWidget { Widget build(BuildContext context) { if (cards.isEmpty) return const SizedBox.shrink(); return Padding( - padding: const EdgeInsets.only(bottom: RatuneSpacing.xl), + padding: const EdgeInsets.only(bottom: TimbreSpacing.xl), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _ShelfHeader(title: title, onShuffle: onShuffle), - const SizedBox(height: RatuneSpacing.md), + const SizedBox(height: TimbreSpacing.md), SizedBox( height: 182, child: ListView.separated( scrollDirection: Axis.horizontal, itemCount: cards.length, separatorBuilder: (_, _) => - const SizedBox(width: RatuneSpacing.md), + const SizedBox(width: TimbreSpacing.md), itemBuilder: (_, i) => cards[i], ), ), @@ -297,7 +297,7 @@ class _ShelfHeader extends StatelessWidget { children: [ Text(title, style: const TextStyle( - color: RatuneColors.foreground, + color: TimbreColors.foreground, fontWeight: FontWeight.w700, letterSpacing: 0.5)), const Spacer(), @@ -306,9 +306,9 @@ class _ShelfHeader extends StatelessWidget { onTap: onShuffle, customBorder: const CircleBorder(), child: const SizedBox( - width: RatuneSpacing.minTouchTarget, + width: TimbreSpacing.minTouchTarget, height: 28, - child: Icon(Icons.shuffle, size: 18, color: RatuneColors.dimmed), + child: Icon(Icons.shuffle, size: 18, color: TimbreColors.dimmed), ), ), ], @@ -392,29 +392,29 @@ class _ArtCard extends StatelessWidget { width: _size, height: _size, child: ColoredBox( - color: RatuneColors.surface, + color: TimbreColors.surface, child: artUri != null ? Image.network(artUri!, key: ValueKey(artUri), fit: BoxFit.cover, gaplessPlayback: true, errorBuilder: (_, _, _) => - Icon(fallbackIcon, color: RatuneColors.dimmed)) - : Icon(fallbackIcon, color: RatuneColors.dimmed), + Icon(fallbackIcon, color: TimbreColors.dimmed)) + : Icon(fallbackIcon, color: TimbreColors.dimmed), ), ), ), - const SizedBox(height: RatuneSpacing.sm), + const SizedBox(height: TimbreSpacing.sm), Text(title, maxLines: 1, overflow: TextOverflow.ellipsis, - style: const TextStyle(color: RatuneColors.foreground)), + style: const TextStyle(color: TimbreColors.foreground)), if (subtitle != null) Text(subtitle!, maxLines: 1, overflow: TextOverflow.ellipsis, style: - const TextStyle(color: RatuneColors.dimmed, fontSize: 12)), + const TextStyle(color: TimbreColors.dimmed, fontSize: 12)), ], ), ), @@ -434,7 +434,7 @@ class _ArtCardSkeleton extends StatelessWidget { child: const SizedBox( width: _ArtCard._size, height: _ArtCard._size, - child: ColoredBox(color: RatuneColors.surface), + child: ColoredBox(color: TimbreColors.surface), ), ), ); diff --git a/lib/screens/now_playing_screen.dart b/lib/screens/now_playing_screen.dart index 3f24a33..487ceed 100644 --- a/lib/screens/now_playing_screen.dart +++ b/lib/screens/now_playing_screen.dart @@ -34,16 +34,16 @@ class _NowPlayingScreenState extends ConsumerState { if (current == null) { return const Center( child: Text('Nothing playing.', - style: TextStyle(color: RatuneColors.dimmed)), + style: TextStyle(color: TimbreColors.dimmed)), ); } return Padding( padding: const EdgeInsets.fromLTRB( - RatuneSpacing.lg, - RatuneSpacing.xl, - RatuneSpacing.lg, - RatuneSpacing.lg, + TimbreSpacing.lg, + TimbreSpacing.xl, + TimbreSpacing.lg, + TimbreSpacing.lg, ), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, @@ -53,21 +53,21 @@ class _NowPlayingScreenState extends ConsumerState { Expanded( child: _showQueue ? const _QueuePanel() : const _AlbumArtPanel(), ), - const SizedBox(height: RatuneSpacing.xl), + const SizedBox(height: TimbreSpacing.xl), _InfoStrip(song: current, state: state, accent: accent), - const SizedBox(height: RatuneSpacing.sm), + const SizedBox(height: TimbreSpacing.sm), _FavRating(song: current), - const SizedBox(height: RatuneSpacing.xs), + const SizedBox(height: TimbreSpacing.xs), _Transport(state: state, ref: ref, accent: accent), if (!state.supported) const Padding( - padding: EdgeInsets.only(top: RatuneSpacing.sm), + padding: EdgeInsets.only(top: TimbreSpacing.sm), child: Text( 'Audio output unavailable on this platform — test on Android/iOS.', - style: TextStyle(color: RatuneColors.dimmed, fontSize: 11), + style: TextStyle(color: TimbreColors.dimmed, fontSize: 11), ), ), - const SizedBox(height: RatuneSpacing.sm), + const SizedBox(height: TimbreSpacing.sm), _QueueToggle( showQueue: _showQueue, queueLength: state.queue.length, @@ -102,8 +102,8 @@ class _QueueToggle extends StatelessWidget { onTap: onTap, child: Padding( padding: const EdgeInsets.symmetric( - horizontal: RatuneSpacing.lg, - vertical: RatuneSpacing.sm, + horizontal: TimbreSpacing.lg, + vertical: TimbreSpacing.sm, ), child: Row( mainAxisSize: MainAxisSize.min, @@ -111,13 +111,13 @@ class _QueueToggle extends StatelessWidget { Icon( showQueue ? Icons.album_outlined : Icons.queue_music, size: 16, - color: showQueue ? accent : RatuneColors.dimmed, + color: showQueue ? accent : TimbreColors.dimmed, ), - const SizedBox(width: RatuneSpacing.sm), + const SizedBox(width: TimbreSpacing.sm), Text( showQueue ? 'Album art' : 'Queue ($queueLength)', style: TextStyle( - color: showQueue ? accent : RatuneColors.foreground, + color: showQueue ? accent : TimbreColors.foreground, ), ), ], @@ -140,7 +140,7 @@ class _QueuePanel extends ConsumerWidget { return HairlinePanel( title: 'Queue', trailing: '(${state.queue.length})', - padding: const EdgeInsets.symmetric(vertical: RatuneSpacing.md), + padding: const EdgeInsets.symmetric(vertical: TimbreSpacing.md), child: ListView.builder( padding: EdgeInsets.zero, itemCount: state.queue.length, @@ -153,15 +153,15 @@ class _QueuePanel extends ConsumerWidget { .playSongs(state.queue, startIndex: i), child: Padding( padding: const EdgeInsets.symmetric( - horizontal: RatuneSpacing.lg, - vertical: RatuneSpacing.xs, + horizontal: TimbreSpacing.lg, + vertical: TimbreSpacing.xs, ), child: Row( children: [ SizedBox( width: 28, child: Text('${i + 1}', - style: const TextStyle(color: RatuneColors.dimmed)), + style: const TextStyle(color: TimbreColors.dimmed)), ), Expanded( child: Text( @@ -169,23 +169,23 @@ class _QueuePanel extends ConsumerWidget { maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle( - color: isCurrent ? accent : RatuneColors.foreground, + color: isCurrent ? accent : TimbreColors.foreground, fontWeight: isCurrent ? FontWeight.w700 : FontWeight.w400, ), ), ), Text(_fmt(song.duration), - style: const TextStyle(color: RatuneColors.dimmed)), + style: const TextStyle(color: TimbreColors.dimmed)), InkWell( onTap: () => ref.read(playbackProvider.notifier).removeAt(i), customBorder: const CircleBorder(), child: const SizedBox( - width: RatuneSpacing.minTouchTarget, - height: RatuneSpacing.minTouchTarget, + width: TimbreSpacing.minTouchTarget, + height: TimbreSpacing.minTouchTarget, child: Icon(Icons.close, - size: 18, color: RatuneColors.dimmed), + size: 18, color: TimbreColors.dimmed), ), ), ], @@ -216,11 +216,11 @@ class _AlbumArtPanel extends ConsumerWidget { return HairlinePanel( title: 'Album Art', - padding: const EdgeInsets.all(RatuneSpacing.md), + padding: const EdgeInsets.all(TimbreSpacing.md), child: AspectRatio( aspectRatio: 1, child: ColoredBox( - color: RatuneColors.surface, + color: TimbreColors.surface, child: artUri != null ? Image.network( artUri, @@ -263,15 +263,15 @@ class _FavRating extends ConsumerWidget { onTap: () => ref.read(favoritesProvider.notifier).toggleSong(song), customBorder: const CircleBorder(), child: Padding( - padding: const EdgeInsets.all(RatuneSpacing.sm), + padding: const EdgeInsets.all(TimbreSpacing.sm), child: Icon( starred ? Icons.favorite : Icons.favorite_border, - color: starred ? accent : RatuneColors.dimmed, + color: starred ? accent : TimbreColors.dimmed, size: 22, ), ), ), - const SizedBox(width: RatuneSpacing.md), + const SizedBox(width: TimbreSpacing.md), for (int i = 1; i <= 5; i++) GestureDetector( onTap: () => ref @@ -282,7 +282,7 @@ class _FavRating extends ConsumerWidget { child: Icon( i <= rating ? Icons.star : Icons.star_border, size: 18, - color: i <= rating ? accent : RatuneColors.dimmed, + color: i <= rating ? accent : TimbreColors.dimmed, ), ), ), @@ -291,9 +291,9 @@ class _FavRating extends ConsumerWidget { onTap: () => showAddToPlaylistSheet(context, songs: [song]), customBorder: const CircleBorder(), child: const Padding( - padding: EdgeInsets.all(RatuneSpacing.sm), + padding: EdgeInsets.all(TimbreSpacing.sm), child: Icon(Icons.playlist_add, - size: 22, color: RatuneColors.dimmed), + size: 22, color: TimbreColors.dimmed), ), ), InkWell( @@ -305,7 +305,7 @@ class _FavRating extends ConsumerWidget { }, customBorder: const CircleBorder(), child: Padding( - padding: const EdgeInsets.all(RatuneSpacing.sm), + padding: const EdgeInsets.all(TimbreSpacing.sm), child: isDownloading ? const SizedBox( width: 18, @@ -317,7 +317,7 @@ class _FavRating extends ConsumerWidget { ? Icons.download_done : Icons.download_outlined, size: 22, - color: isDownloaded ? accent : RatuneColors.dimmed, + color: isDownloaded ? accent : TimbreColors.dimmed, ), ), ), @@ -348,19 +348,19 @@ class _InfoStrip extends StatelessWidget { overflow: TextOverflow.ellipsis, style: TextStyle(color: accent, fontWeight: FontWeight.w700)), Text(song.artist ?? 'Unknown artist', - style: const TextStyle(color: RatuneColors.foreground)), + style: const TextStyle(color: TimbreColors.foreground)), if (album.isNotEmpty) - Text(album, style: const TextStyle(color: RatuneColors.dimmed)), - const SizedBox(height: RatuneSpacing.md), + Text(album, style: const TextStyle(color: TimbreColors.dimmed)), + const SizedBox(height: TimbreSpacing.md), Row( children: [ Text(_fmtDur(state.position), - style: const TextStyle(color: RatuneColors.dimmed)), - const SizedBox(width: RatuneSpacing.md), + style: const TextStyle(color: TimbreColors.dimmed)), + const SizedBox(width: TimbreSpacing.md), Expanded(child: BlockProgressBar(progress: state.progress)), - const SizedBox(width: RatuneSpacing.md), + const SizedBox(width: TimbreSpacing.md), Text(_fmtDur(state.duration), - style: const TextStyle(color: RatuneColors.dimmed)), + style: const TextStyle(color: TimbreColors.dimmed)), ], ), ], @@ -387,20 +387,20 @@ class _Transport extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ _btn(Icons.shuffle, controller.toggleShuffle, - color: state.shuffle ? accent : RatuneColors.dimmed), + color: state.shuffle ? accent : TimbreColors.dimmed), _btn(Icons.skip_previous, controller.previous), _btn(state.playing ? Icons.pause : Icons.play_arrow, controller.togglePlayPause, color: accent, size: 40), _btn(Icons.skip_next, controller.next), _btn(loopIcon, controller.cycleLoop, - color: state.loop != LoopMode.off ? accent : RatuneColors.dimmed), + color: state.loop != LoopMode.off ? accent : TimbreColors.dimmed), ], ); } Widget _btn(IconData icon, VoidCallback onTap, - {Color color = RatuneColors.foreground, double size = 28}) { + {Color color = TimbreColors.foreground, double size = 28}) { return IconButton( onPressed: onTap, icon: Icon(icon, color: color, size: size), @@ -413,7 +413,7 @@ class _ArtFallback extends StatelessWidget { @override Widget build(BuildContext context) => const Center( child: Icon(Icons.album_outlined, - color: RatuneColors.dimmed, size: 48), + color: TimbreColors.dimmed, size: 48), ); } diff --git a/lib/screens/playlists_screen.dart b/lib/screens/playlists_screen.dart index b0376f9..f2bb989 100644 --- a/lib/screens/playlists_screen.dart +++ b/lib/screens/playlists_screen.dart @@ -34,13 +34,13 @@ class PlaylistsScreen extends ConsumerWidget { ), body: SafeArea( child: Padding( - padding: const EdgeInsets.all(RatuneSpacing.lg), + padding: const EdgeInsets.all(TimbreSpacing.lg), child: HairlinePanel( title: 'Playlists', active: true, trailing: state.playlists.isEmpty ? null : '(${state.playlists.length})', - padding: const EdgeInsets.symmetric(vertical: RatuneSpacing.md), + padding: const EdgeInsets.symmetric(vertical: TimbreSpacing.md), child: state.playlists.isEmpty ? Center( child: Text( @@ -48,7 +48,7 @@ class PlaylistsScreen extends ConsumerWidget { ? 'No playlists yet. Tap + to create one.' : 'Connect to a server to see playlists.', textAlign: TextAlign.center, - style: const TextStyle(color: RatuneColors.dimmed), + style: const TextStyle(color: TimbreColors.dimmed), ), ) : ListView.builder( @@ -99,7 +99,7 @@ class PlaylistsScreen extends ConsumerWidget { final ok = await showDialog( context: context, builder: (ctx) => AlertDialog( - backgroundColor: RatuneColors.surface, + backgroundColor: TimbreColors.surface, title: Text('Delete "${p.name}"?'), actions: [ TextButton( @@ -136,12 +136,12 @@ class _PlaylistRow extends StatelessWidget { onTap: onTap, child: Container( constraints: - const BoxConstraints(minHeight: RatuneSpacing.minTouchTarget), - padding: const EdgeInsets.only(left: RatuneSpacing.lg), + const BoxConstraints(minHeight: TimbreSpacing.minTouchTarget), + padding: const EdgeInsets.only(left: TimbreSpacing.lg), child: Row( children: [ - const Icon(Icons.queue_music, size: 18, color: RatuneColors.dimmed), - const SizedBox(width: RatuneSpacing.md), + const Icon(Icons.queue_music, size: 18, color: TimbreColors.dimmed), + const SizedBox(width: TimbreSpacing.md), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -150,19 +150,19 @@ class _PlaylistRow extends StatelessWidget { Text(name, maxLines: 1, overflow: TextOverflow.ellipsis, - style: const TextStyle(color: RatuneColors.foreground)), + style: const TextStyle(color: TimbreColors.foreground)), if (subtitle != null) Text(subtitle!, style: const TextStyle( - color: RatuneColors.dimmed, fontSize: 12)), + color: TimbreColors.dimmed, fontSize: 12)), ], ), ), if (onRename != null || onDelete != null) PopupMenuButton( icon: const Icon(Icons.more_vert, - size: 20, color: RatuneColors.dimmed), - color: RatuneColors.surface, + size: 20, color: TimbreColors.dimmed), + color: TimbreColors.surface, onSelected: (v) { if (v == 'rename') onRename?.call(); if (v == 'delete') onDelete?.call(); @@ -247,16 +247,16 @@ class _PlaylistDetailScreenState extends ConsumerState { child: detail == null ? const Center( child: Text('Loading…', - style: TextStyle(color: RatuneColors.dimmed)), + style: TextStyle(color: TimbreColors.dimmed)), ) : songs.isEmpty ? const Center( child: Text('This playlist is empty.', - style: TextStyle(color: RatuneColors.dimmed)), + style: TextStyle(color: TimbreColors.dimmed)), ) : ListView.builder( padding: const EdgeInsets.symmetric( - vertical: RatuneSpacing.md), + vertical: TimbreSpacing.md), itemCount: songs.length, itemBuilder: (context, i) { final song = songs[i]; @@ -302,14 +302,14 @@ class _TrackRow extends StatelessWidget { onTap: onTap, child: Container( constraints: - const BoxConstraints(minHeight: RatuneSpacing.minTouchTarget), - padding: const EdgeInsets.only(left: RatuneSpacing.lg), + const BoxConstraints(minHeight: TimbreSpacing.minTouchTarget), + padding: const EdgeInsets.only(left: TimbreSpacing.lg), child: Row( children: [ SizedBox( width: 28, child: Text('$index', - style: const TextStyle(color: RatuneColors.dimmed)), + style: const TextStyle(color: TimbreColors.dimmed)), ), Expanded( child: Column( @@ -319,20 +319,20 @@ class _TrackRow extends StatelessWidget { Text(song.title ?? 'Untitled', maxLines: 1, overflow: TextOverflow.ellipsis, - style: const TextStyle(color: RatuneColors.foreground)), + style: const TextStyle(color: TimbreColors.foreground)), if (song.artist != null) Text(song.artist!, maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle( - color: RatuneColors.dimmed, fontSize: 12)), + color: TimbreColors.dimmed, fontSize: 12)), ], ), ), PopupMenuButton( icon: const Icon(Icons.more_vert, - size: 20, color: RatuneColors.dimmed), - color: RatuneColors.surface, + size: 20, color: TimbreColors.dimmed), + color: TimbreColors.surface, onSelected: (v) { switch (v) { case 'next': diff --git a/lib/screens/search_screen.dart b/lib/screens/search_screen.dart index 4b3438d..85e8b57 100644 --- a/lib/screens/search_screen.dart +++ b/lib/screens/search_screen.dart @@ -32,11 +32,11 @@ class _SearchScreenState extends ConsumerState { controller: _controller, autofocus: true, textInputAction: TextInputAction.search, - style: const TextStyle(color: RatuneColors.foreground), + style: const TextStyle(color: TimbreColors.foreground), cursorColor: accent, decoration: const InputDecoration( hintText: 'Search artists, albums, songs…', - hintStyle: TextStyle(color: RatuneColors.dimmed), + hintStyle: TextStyle(color: TimbreColors.dimmed), border: InputBorder.none, ), onSubmitted: (v) => setState(() => _query = v), @@ -46,7 +46,7 @@ class _SearchScreenState extends ConsumerState { child: _query.trim().isEmpty ? const Center( child: Text('Type and press search.', - style: TextStyle(color: RatuneColors.dimmed)), + style: TextStyle(color: TimbreColors.dimmed)), ) : _Results(query: _query), ), @@ -71,13 +71,13 @@ class _Results extends ConsumerWidget { ), ), error: (e, _) => Center( - child: Text('$e', style: const TextStyle(color: RatuneColors.dimmed)), + child: Text('$e', style: const TextStyle(color: TimbreColors.dimmed)), ), data: (r) { if (r.artists.isEmpty && r.albums.isEmpty && r.songs.isEmpty) { return const Center( child: Text('No results.', - style: TextStyle(color: RatuneColors.dimmed)), + style: TextStyle(color: TimbreColors.dimmed)), ); } return ListView( diff --git a/lib/screens/settings_screen.dart b/lib/screens/settings_screen.dart index c2f9bbd..be392c8 100644 --- a/lib/screens/settings_screen.dart +++ b/lib/screens/settings_screen.dart @@ -22,7 +22,7 @@ class SettingsScreen extends ConsumerWidget { ), body: SafeArea( child: ListView( - padding: const EdgeInsets.all(RatuneSpacing.lg), + padding: const EdgeInsets.all(TimbreSpacing.lg), children: [ HairlinePanel( title: 'Streaming', @@ -32,7 +32,7 @@ class SettingsScreen extends ConsumerWidget { children: [ const _Caption( 'Quality used when playing tracks that are not downloaded.'), - const SizedBox(height: RatuneSpacing.md), + const SizedBox(height: TimbreSpacing.md), _ChoiceChips( label: 'Max bitrate', values: AppSettings.bitrateChoices, @@ -43,7 +43,7 @@ class SettingsScreen extends ConsumerWidget { ], ), ), - const SizedBox(height: RatuneSpacing.xl), + const SizedBox(height: TimbreSpacing.xl), HairlinePanel( title: 'Downloads', active: true, @@ -53,7 +53,7 @@ class SettingsScreen extends ConsumerWidget { const _Caption( 'Quality used when saving tracks for offline playback. ' '"Original" keeps the source file (best quality, largest).'), - const SizedBox(height: RatuneSpacing.md), + const SizedBox(height: TimbreSpacing.md), _ChoiceChips( label: 'Max bitrate', values: AppSettings.bitrateChoices, @@ -61,7 +61,7 @@ class SettingsScreen extends ConsumerWidget { labelFor: AppSettings.bitrateLabel, onSelect: controller.setDownloadMaxBitRate, ), - const SizedBox(height: RatuneSpacing.lg), + const SizedBox(height: TimbreSpacing.lg), _ChoiceChips( label: 'Format', values: AppSettings.formatChoices, @@ -85,7 +85,7 @@ class _Caption extends StatelessWidget { @override Widget build(BuildContext context) => Text( text, - style: const TextStyle(color: RatuneColors.dimmed, fontSize: 12), + style: const TextStyle(color: TimbreColors.dimmed, fontSize: 12), ); } @@ -112,11 +112,11 @@ class _ChoiceChips extends StatelessWidget { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(label, style: const TextStyle(color: RatuneColors.foreground)), - const SizedBox(height: RatuneSpacing.sm), + Text(label, style: const TextStyle(color: TimbreColors.foreground)), + const SizedBox(height: TimbreSpacing.sm), Wrap( - spacing: RatuneSpacing.sm, - runSpacing: RatuneSpacing.sm, + spacing: TimbreSpacing.sm, + runSpacing: TimbreSpacing.sm, children: [ for (final v in values) _Chip( @@ -150,21 +150,21 @@ class _Chip extends StatelessWidget { return InkWell( onTap: onTap, child: Container( - constraints: const BoxConstraints(minHeight: RatuneSpacing.minTouchTarget), + constraints: const BoxConstraints(minHeight: TimbreSpacing.minTouchTarget), padding: const EdgeInsets.symmetric( - horizontal: RatuneSpacing.lg, - vertical: RatuneSpacing.md, + horizontal: TimbreSpacing.lg, + vertical: TimbreSpacing.md, ), decoration: BoxDecoration( border: Border.all( - color: active ? accent : RatuneColors.border, + color: active ? accent : TimbreColors.border, ), - color: active ? accent.withValues(alpha: 0.12) : RatuneColors.surface, + color: active ? accent.withValues(alpha: 0.12) : TimbreColors.surface, ), child: Text( text, style: TextStyle( - color: active ? accent : RatuneColors.foreground, + color: active ? accent : TimbreColors.foreground, fontWeight: active ? FontWeight.w700 : FontWeight.w400, ), ), diff --git a/lib/shell/app_shell.dart b/lib/shell/app_shell.dart index 063d8a8..3751315 100644 --- a/lib/shell/app_shell.dart +++ b/lib/shell/app_shell.dart @@ -141,16 +141,16 @@ class _TabBar extends StatelessWidget { onTap: () => onSelect(i), child: Container( constraints: - const BoxConstraints(minHeight: RatuneSpacing.minTouchTarget), + const BoxConstraints(minHeight: TimbreSpacing.minTouchTarget), padding: const EdgeInsets.symmetric( - horizontal: RatuneSpacing.md, - vertical: RatuneSpacing.md, + horizontal: TimbreSpacing.md, + vertical: TimbreSpacing.md, ), child: Center( child: Text( tabs[i], style: TextStyle( - color: active ? RatuneColors.foreground : RatuneColors.dimmed, + color: active ? TimbreColors.foreground : TimbreColors.dimmed, fontWeight: active ? FontWeight.w700 : FontWeight.w400, decoration: active ? TextDecoration.underline : TextDecoration.none, @@ -164,13 +164,13 @@ class _TabBar extends StatelessWidget { ); if (i < tabs.length - 1) { children.add( - const Text('|', style: TextStyle(color: RatuneColors.border)), + const Text('|', style: TextStyle(color: TimbreColors.border)), ); } } return Container( decoration: const BoxDecoration( - border: Border(top: BorderSide(color: RatuneColors.border)), + border: Border(top: BorderSide(color: TimbreColors.border)), ), child: Row(mainAxisAlignment: MainAxisAlignment.center, children: children), ); @@ -191,14 +191,14 @@ class _StatusBar extends ConsumerWidget { conn.credentials?.display ?? 'online', accent, ), - ConnStatus.connecting => ('◐', 'connecting…', RatuneColors.dimmed), + ConnStatus.connecting => ('◐', 'connecting…', TimbreColors.dimmed), ConnStatus.error => ('○', 'offline', const Color(0xFFE06C75)), - ConnStatus.disconnected => ('○', 'tap to connect', RatuneColors.dimmed), + ConnStatus.disconnected => ('○', 'tap to connect', TimbreColors.dimmed), }; return Container( height: 22, - color: RatuneColors.surface, + color: TimbreColors.surface, child: Row( children: [ // Left: connection status — tap to open the connect sheet. @@ -207,7 +207,7 @@ class _StatusBar extends ConsumerWidget { onTap: () => showConnectSheet(context), child: Padding( padding: - const EdgeInsets.symmetric(horizontal: RatuneSpacing.lg), + const EdgeInsets.symmetric(horizontal: TimbreSpacing.lg), child: Row( children: [ Text('$glyph ', style: TextStyle(color: color)), @@ -230,14 +230,14 @@ class _StatusBar extends ConsumerWidget { MaterialPageRoute(builder: (_) => const SettingsScreen()), ), child: const Padding( - padding: EdgeInsets.symmetric(horizontal: RatuneSpacing.lg), + padding: EdgeInsets.symmetric(horizontal: TimbreSpacing.lg), child: Row( mainAxisSize: MainAxisSize.min, children: [ - Icon(Icons.settings, size: 12, color: RatuneColors.dimmed), - SizedBox(width: RatuneSpacing.xs), + Icon(Icons.settings, size: 12, color: TimbreColors.dimmed), + SizedBox(width: TimbreSpacing.xs), Text('settings', - style: TextStyle(color: RatuneColors.dimmed)), + style: TextStyle(color: TimbreColors.dimmed)), ], ), ), diff --git a/lib/subsonic/subsonic_client.dart b/lib/subsonic/subsonic_client.dart index 878bf9a..fff0684 100644 --- a/lib/subsonic/subsonic_client.dart +++ b/lib/subsonic/subsonic_client.dart @@ -40,7 +40,7 @@ class SubsonicClient { Dio(BaseOptions( connectTimeout: const Duration(seconds: 30), receiveTimeout: const Duration(seconds: 30), - headers: {'User-Agent': 'ratune-mobile'}, + headers: {'User-Agent': 'timbre'}, )); final String baseUrl; @@ -49,7 +49,7 @@ class SubsonicClient { final Dio _dio; static const String apiVersion = '1.16.1'; - static const String clientName = 'ratune-mobile'; + static const String clientName = 'timbre'; static const String _saltAlphabet = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; diff --git a/lib/theme/accent.dart b/lib/theme/accent.dart index 3ef1855..6154212 100644 --- a/lib/theme/accent.dart +++ b/lib/theme/accent.dart @@ -10,13 +10,13 @@ import 'tokens.dart'; /// then interpolated toward over ~400ms. Phase 1 just exposes the default and /// a setter; the extraction + animation land in Phase 2 with playback. class AccentNotifier extends StateNotifier { - AccentNotifier() : super(RatuneColors.accentDefault); + AccentNotifier() : super(TimbreColors.accentDefault); /// Snap to a new accent (e.g. from album-art extraction). void set(Color color) => state = color; /// Return to the fixed default (e.g. no art / `static` theme). - void reset() => state = RatuneColors.accentDefault; + void reset() => state = TimbreColors.accentDefault; } final accentProvider = StateNotifierProvider( diff --git a/lib/theme/app_theme.dart b/lib/theme/app_theme.dart index a9b78c8..01732e1 100644 --- a/lib/theme/app_theme.dart +++ b/lib/theme/app_theme.dart @@ -8,45 +8,45 @@ import 'tokens.dart'; /// Monospace type is core to the identity — every surface uses JetBrains Mono /// (a close analog to the terminal fonts in Ratune's screenshots). The [accent] /// is passed in so the theme rebuilds when album-art extraction changes it. -ThemeData buildRatuneTheme(Color accent) { +ThemeData buildTimbreTheme(Color accent) { final mono = GoogleFonts.jetBrainsMonoTextTheme( ThemeData.dark().textTheme, ).apply( - bodyColor: RatuneColors.foreground, - displayColor: RatuneColors.foreground, + bodyColor: TimbreColors.foreground, + displayColor: TimbreColors.foreground, ); final scheme = ColorScheme.fromSeed( seedColor: accent, brightness: Brightness.dark, - surface: RatuneColors.surface, + surface: TimbreColors.surface, ).copyWith( primary: accent, secondary: accent, - onSurface: RatuneColors.foreground, + onSurface: TimbreColors.foreground, ); return ThemeData( useMaterial3: true, brightness: Brightness.dark, - scaffoldBackgroundColor: RatuneColors.background, - canvasColor: RatuneColors.background, + scaffoldBackgroundColor: TimbreColors.background, + canvasColor: TimbreColors.background, colorScheme: scheme, textTheme: mono, primaryTextTheme: mono, - dividerColor: RatuneColors.border, + dividerColor: TimbreColors.border, splashFactory: NoSplash.splashFactory, highlightColor: Colors.transparent, // Keep chrome flat and quiet — the content (and accent) carry the look. appBarTheme: const AppBarTheme( - backgroundColor: RatuneColors.background, + backgroundColor: TimbreColors.background, surfaceTintColor: Colors.transparent, elevation: 0, centerTitle: false, ), listTileTheme: const ListTileThemeData( dense: true, - minVerticalPadding: RatuneSpacing.sm, + minVerticalPadding: TimbreSpacing.sm, ), ); } diff --git a/lib/theme/tokens.dart b/lib/theme/tokens.dart index bed0544..f05ff47 100644 --- a/lib/theme/tokens.dart +++ b/lib/theme/tokens.dart @@ -6,8 +6,8 @@ import 'package:flutter/widgets.dart'; /// The palette is intentionally near-black and low-contrast; the *accent* /// is the one lively color and, in the `dynamic` theme, is extracted live /// from the current album art (Phase 2). Everything else stays fixed. -class RatuneColors { - const RatuneColors._(); +class TimbreColors { + const TimbreColors._(); /// App canvas — `background = #1a1a1a`. static const Color background = Color(0xFF1A1A1A); @@ -34,8 +34,8 @@ class RatuneColors { } /// Spacing scale — dense, "player as instrument" rather than airy mobile cards. -class RatuneSpacing { - const RatuneSpacing._(); +class TimbreSpacing { + const TimbreSpacing._(); static const double xs = 2; static const double sm = 4; diff --git a/lib/widgets/block_progress_bar.dart b/lib/widgets/block_progress_bar.dart index cba346b..a5d6f9e 100644 --- a/lib/widgets/block_progress_bar.dart +++ b/lib/widgets/block_progress_bar.dart @@ -37,7 +37,7 @@ class BlockProgressBar extends StatelessWidget { child: Padding( padding: const EdgeInsets.symmetric(horizontal: 0.5), child: ColoredBox( - color: i < filled ? filledColor : RatuneColors.border, + color: i < filled ? filledColor : TimbreColors.border, ), ), ); diff --git a/lib/widgets/hairline_panel.dart b/lib/widgets/hairline_panel.dart index 4e16c21..8cc5075 100644 --- a/lib/widgets/hairline_panel.dart +++ b/lib/widgets/hairline_panel.dart @@ -14,8 +14,8 @@ class HairlinePanel extends StatelessWidget { required this.child, this.trailing, this.active = false, - this.padding = const EdgeInsets.all(RatuneSpacing.lg), - this.backgroundColor = RatuneColors.background, + this.padding = const EdgeInsets.all(TimbreSpacing.lg), + this.backgroundColor = TimbreColors.background, }); /// Panel label, e.g. "Queue". Rendered uppercase-ish in mono. @@ -41,9 +41,9 @@ class HairlinePanel extends StatelessWidget { @override Widget build(BuildContext context) { final borderColor = - active ? RatuneColors.borderActive : RatuneColors.border; + active ? TimbreColors.borderActive : TimbreColors.border; final titleColor = - active ? Theme.of(context).colorScheme.primary : RatuneColors.dimmed; + active ? Theme.of(context).colorScheme.primary : TimbreColors.dimmed; return Stack( clipBehavior: Clip.none, @@ -56,12 +56,12 @@ class HairlinePanel extends StatelessWidget { child: Padding(padding: padding, child: child), ), Positioned( - left: RatuneSpacing.lg, + left: TimbreSpacing.lg, top: 0, child: Container( color: backgroundColor, padding: const EdgeInsets.symmetric( - horizontal: RatuneSpacing.md, + horizontal: TimbreSpacing.md, ), child: Text.rich( TextSpan( @@ -77,7 +77,7 @@ class HairlinePanel extends StatelessWidget { if (trailing != null) TextSpan( text: ' $trailing', - style: const TextStyle(color: RatuneColors.dimmed), + style: const TextStyle(color: TimbreColors.dimmed), ), ], ), diff --git a/lib/widgets/mini_player.dart b/lib/widgets/mini_player.dart index dcb160d..e20c7e4 100644 --- a/lib/widgets/mini_player.dart +++ b/lib/widgets/mini_player.dart @@ -38,15 +38,15 @@ class MiniPlayer extends ConsumerWidget { ref.read(selectedTabProvider.notifier).state = nowPlayingTabIndex, child: Container( height: 52, - color: RatuneColors.surface, - padding: const EdgeInsets.only(left: RatuneSpacing.md), + color: TimbreColors.surface, + padding: const EdgeInsets.only(left: TimbreSpacing.md), child: Row( children: [ SizedBox( width: 40, height: 40, child: ColoredBox( - color: RatuneColors.background, + color: TimbreColors.background, child: artUri != null ? Image.network( artUri, @@ -58,7 +58,7 @@ class MiniPlayer extends ConsumerWidget { : const _ArtFallback(), ), ), - const SizedBox(width: RatuneSpacing.md), + const SizedBox(width: TimbreSpacing.md), Expanded( child: Column( mainAxisAlignment: MainAxisAlignment.center, @@ -69,7 +69,7 @@ class MiniPlayer extends ConsumerWidget { maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle( - color: RatuneColors.foreground, + color: TimbreColors.foreground, fontWeight: FontWeight.w700, ), ), @@ -77,7 +77,7 @@ class MiniPlayer extends ConsumerWidget { current.artist ?? 'Unknown artist', maxLines: 1, overflow: TextOverflow.ellipsis, - style: const TextStyle(color: RatuneColors.dimmed), + style: const TextStyle(color: TimbreColors.dimmed), ), ], ), @@ -96,7 +96,7 @@ class MiniPlayer extends ConsumerWidget { } Widget _btn(IconData icon, VoidCallback onTap, - {Color color = RatuneColors.foreground}) { + {Color color = TimbreColors.foreground}) { return IconButton( onPressed: onTap, visualDensity: VisualDensity.compact, @@ -119,7 +119,7 @@ class _MiniProgress extends ConsumerWidget { child: LinearProgressIndicator( value: progress, minHeight: 2, - backgroundColor: RatuneColors.border, + backgroundColor: TimbreColors.border, valueColor: AlwaysStoppedAnimation(accent), ), ); @@ -131,6 +131,6 @@ class _ArtFallback extends StatelessWidget { @override Widget build(BuildContext context) => const Center( child: Icon(Icons.album_outlined, - color: RatuneColors.dimmed, size: 22), + color: TimbreColors.dimmed, size: 22), ); } diff --git a/linux/CMakeLists.txt b/linux/CMakeLists.txt index 396c434..14d2c0e 100644 --- a/linux/CMakeLists.txt +++ b/linux/CMakeLists.txt @@ -4,10 +4,10 @@ project(runner LANGUAGES CXX) # The name of the executable created for the application. Change this to change # the on-disk name of your application. -set(BINARY_NAME "ratune_mobile") +set(BINARY_NAME "timbre") # The unique GTK application identifier for this application. See: # https://wiki.gnome.org/HowDoI/ChooseApplicationID -set(APPLICATION_ID "com.conversionpath.ratune_mobile") +set(APPLICATION_ID "com.laforrestchurch.timbre") # Explicitly opt in to modern CMake behaviors to avoid warnings with recent # versions of CMake. diff --git a/linux/runner/my_application.cc b/linux/runner/my_application.cc index 037f0c1..67e3863 100644 --- a/linux/runner/my_application.cc +++ b/linux/runner/my_application.cc @@ -45,11 +45,11 @@ static void my_application_activate(GApplication* application) { if (use_header_bar) { GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); gtk_widget_show(GTK_WIDGET(header_bar)); - gtk_header_bar_set_title(header_bar, "ratune_mobile"); + gtk_header_bar_set_title(header_bar, "Timbre"); gtk_header_bar_set_show_close_button(header_bar, TRUE); gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); } else { - gtk_window_set_title(window, "ratune_mobile"); + gtk_window_set_title(window, "Timbre"); } gtk_window_set_default_size(window, 1280, 720); diff --git a/pubspec.yaml b/pubspec.yaml index ac09600..4aaabed 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ -name: ratune_mobile -description: "A new Flutter project." +name: timbre +description: "Timbre — a Subsonic music client." # The following line prevents the package from being accidentally published to # pub.dev using `flutter pub publish`. This is preferred for private packages. publish_to: 'none' # Remove this line if you wish to publish to pub.dev diff --git a/test/features_test.dart b/test/features_test.dart index f95987d..169fa6e 100644 --- a/test/features_test.dart +++ b/test/features_test.dart @@ -1,8 +1,8 @@ import 'package:flutter_test/flutter_test.dart'; -import 'package:ratune_mobile/playlists/playlists.dart'; -import 'package:ratune_mobile/settings/settings_store.dart'; -import 'package:ratune_mobile/subsonic/models.dart'; -import 'package:ratune_mobile/subsonic/subsonic_client.dart'; +import 'package:timbre/playlists/playlists.dart'; +import 'package:timbre/settings/settings_store.dart'; +import 'package:timbre/subsonic/models.dart'; +import 'package:timbre/subsonic/subsonic_client.dart'; /// Fake client so playlist mutations can be tested without a server. Methods /// are overridden; the (unused) real Dio is created but never hit. diff --git a/test/widget_test.dart b/test/widget_test.dart index 82aff2e..ba8ea6e 100644 --- a/test/widget_test.dart +++ b/test/widget_test.dart @@ -1,11 +1,11 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:ratune_mobile/main.dart'; +import 'package:timbre/main.dart'; void main() { testWidgets('App shell renders the three tabs', (WidgetTester tester) async { - await tester.pumpWidget(const ProviderScope(child: RatuneApp())); + await tester.pumpWidget(const ProviderScope(child: TimbreApp())); await tester.pump(); expect(find.text('Home'), findsOneWidget);