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);