From a7c5afdd20c019776e4a001fdc918200a0ebd96b Mon Sep 17 00:00:00 2001 From: zarzet Date: Fri, 2 Jan 2026 20:15:29 +0700 Subject: [PATCH] ui: redesign About page with contributors and fix title alignment --- CHANGELOG.md | 6 + lib/constants/app_info.dart | 4 +- lib/screens/home_tab.dart | 17 +- lib/screens/main_shell.dart | 6 +- lib/screens/settings/about_page.dart | 373 +++++++++++++----- .../settings/appearance_settings_page.dart | 2 +- .../settings/download_settings_page.dart | 2 +- .../settings/options_settings_page.dart | 2 +- pubspec.yaml | 2 +- 9 files changed, 316 insertions(+), 98 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4506906d..a62cbf51 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## [1.6.2] - 2026-01-02 + +### Changed +- **Home Tab Rename**: Renamed "Search" tab to "Home" with home icon +- **Branding**: Changed idle screen title from "Search Music" to "SpotiFLAC" + ## [1.6.1] - 2026-01-02 ### Added diff --git a/lib/constants/app_info.dart b/lib/constants/app_info.dart index 6b1dcbc2..23bbbad8 100644 --- a/lib/constants/app_info.dart +++ b/lib/constants/app_info.dart @@ -1,8 +1,8 @@ /// App version and info constants /// Update version here only - all other files will reference this class AppInfo { - static const String version = '1.6.1'; - static const String buildNumber = '26'; + static const String version = '1.6.2'; + static const String buildNumber = '27'; static const String fullVersion = '$version+$buildNumber'; static const String appName = 'SpotiFLAC'; diff --git a/lib/screens/home_tab.dart b/lib/screens/home_tab.dart index 189b4aa8..7e5f7abf 100644 --- a/lib/screens/home_tab.dart +++ b/lib/screens/home_tab.dart @@ -231,7 +231,7 @@ class _HomeTabState extends ConsumerState with AutomaticKeepAliveClient expandedTitleScale: 1.3, titlePadding: const EdgeInsets.only(left: 24, bottom: 16), title: Text( - 'Search', + 'Home', style: TextStyle( fontSize: 28, fontWeight: FontWeight.bold, @@ -259,6 +259,21 @@ class _HomeTabState extends ConsumerState with AutomaticKeepAliveClient ), child: Icon(Icons.music_note, size: 48, color: colorScheme.primary), ), + const SizedBox(height: 16), + Text( + 'SpotiFLAC', + style: Theme.of(context).textTheme.headlineSmall?.copyWith( + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 8), + Text( + 'Paste a Spotify link or search by name', + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + color: colorScheme.onSurfaceVariant, + ), + ), ], ), ), diff --git a/lib/screens/main_shell.dart b/lib/screens/main_shell.dart index a0dc616e..c7672fb9 100644 --- a/lib/screens/main_shell.dart +++ b/lib/screens/main_shell.dart @@ -191,9 +191,9 @@ class _MainShellState extends ConsumerState { animationDuration: const Duration(milliseconds: 200), destinations: [ const NavigationDestination( - icon: Icon(Icons.search_outlined), - selectedIcon: Icon(Icons.search), - label: 'Search', + icon: Icon(Icons.home_outlined), + selectedIcon: Icon(Icons.home), + label: 'Home', ), NavigationDestination( icon: Badge( diff --git a/lib/screens/settings/about_page.dart b/lib/screens/settings/about_page.dart index 853644f0..a05258e8 100644 --- a/lib/screens/settings/about_page.dart +++ b/lib/screens/settings/about_page.dart @@ -1,6 +1,8 @@ import 'package:flutter/material.dart'; import 'package:url_launcher/url_launcher.dart'; +import 'package:cached_network_image/cached_network_image.dart'; import 'package:spotiflac_android/constants/app_info.dart'; +import 'package:spotiflac_android/widgets/settings_group.dart'; class AboutPage extends StatelessWidget { const AboutPage({super.key}); @@ -21,7 +23,10 @@ class AboutPage extends StatelessWidget { pinned: true, backgroundColor: colorScheme.surface, surfaceTintColor: Colors.transparent, - leading: IconButton(icon: const Icon(Icons.arrow_back), onPressed: () => Navigator.pop(context)), + leading: IconButton( + icon: const Icon(Icons.arrow_back), + onPressed: () => Navigator.pop(context), + ), flexibleSpace: LayoutBuilder( builder: (context, constraints) { final maxHeight = 120 + topPadding; @@ -35,10 +40,13 @@ class AboutPage extends StatelessWidget { child: Container( alignment: Alignment.bottomLeft, padding: EdgeInsets.only( + // When collapsed (expandRatio=0): left=56 to align with back button + // When expanded (expandRatio=1): left=24 for normal padding left: Tween(begin: 56, end: 24).evaluate(animation), - bottom: Tween(begin: 12, end: 16).evaluate(animation), + bottom: Tween(begin: 16, end: 16).evaluate(animation), ), - child: Text('About', + child: Text( + 'About', style: TextStyle( fontSize: Tween(begin: 20, end: 28).evaluate(animation), fontWeight: FontWeight.bold, @@ -52,73 +60,90 @@ class AboutPage extends StatelessWidget { ), ), - // App info card + // App header card with logo and description SliverToBoxAdapter( child: Padding( padding: const EdgeInsets.fromLTRB(16, 16, 16, 8), - child: Card( - elevation: 0, - color: colorScheme.surfaceContainerHigh, - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), - child: Padding( - padding: const EdgeInsets.all(16), - child: Row(children: [ - Container( - width: 56, height: 56, - decoration: BoxDecoration(color: colorScheme.primaryContainer, borderRadius: BorderRadius.circular(16)), - child: ClipRRect( - borderRadius: BorderRadius.circular(16), - child: Image.asset('assets/images/logo.png', fit: BoxFit.cover, - errorBuilder: (_, _, _) => Icon(Icons.music_note, size: 32, color: colorScheme.onPrimaryContainer)), - ), - ), - const SizedBox(width: 16), - Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(AppInfo.appName, style: Theme.of(context).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.bold)), - const SizedBox(height: 4), - Container( - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), - decoration: BoxDecoration(color: colorScheme.secondaryContainer, borderRadius: BorderRadius.circular(12)), - child: Text('v${AppInfo.version}', style: Theme.of(context).textTheme.labelMedium?.copyWith(color: colorScheme.onSecondaryContainer)), - ), - ]), - ]), - ), - ), + child: _AppHeaderCard(), ), ), - // GitHub section - SliverToBoxAdapter(child: _SectionHeader(title: 'GitHub')), - SliverList(delegate: SliverChildListDelegate([ - ListTile( - contentPadding: const EdgeInsets.symmetric(horizontal: 24), - leading: Icon(Icons.phone_android, color: colorScheme.onSurfaceVariant), - title: Text('${AppInfo.appName} Mobile'), - subtitle: Text('github.com/${AppInfo.githubRepo}'), - trailing: const Icon(Icons.open_in_new, size: 20), - onTap: () => _launchUrl(AppInfo.githubUrl), - ), - ListTile( - contentPadding: const EdgeInsets.symmetric(horizontal: 24), - leading: Icon(Icons.computer, color: colorScheme.onSurfaceVariant), - title: Text('Original ${AppInfo.appName}'), - subtitle: Text('github.com/${AppInfo.originalAuthor}/SpotiFLAC'), - trailing: const Icon(Icons.open_in_new, size: 20), - onTap: () => _launchUrl(AppInfo.originalGithubUrl), - ), - ])), - - // Credits section - SliverToBoxAdapter(child: _SectionHeader(title: 'Credits')), + // Contributors section + const SliverToBoxAdapter( + child: SettingsSectionHeader(title: 'Contributors'), + ), SliverToBoxAdapter( - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 24), - child: Column(children: [ - _CreditRow(label: 'Mobile Version', value: AppInfo.mobileAuthor), - const SizedBox(height: 12), - _CreditRow(label: 'Original Project', value: AppInfo.originalAuthor), - ]), + child: SettingsGroup( + children: [ + _ContributorItem( + name: AppInfo.mobileAuthor, + description: 'Mobile version developer', + githubUsername: AppInfo.mobileAuthor, + showDivider: true, + ), + _ContributorItem( + name: AppInfo.originalAuthor, + description: 'Creator of the original SpotiFLAC', + githubUsername: AppInfo.originalAuthor, + showDivider: false, + ), + ], + ), + ), + + // Links section + const SliverToBoxAdapter( + child: SettingsSectionHeader(title: 'Links'), + ), + SliverToBoxAdapter( + child: SettingsGroup( + children: [ + SettingsItem( + icon: Icons.phone_android, + title: 'Mobile source code', + subtitle: 'github.com/${AppInfo.githubRepo}', + onTap: () => _launchUrl(AppInfo.githubUrl), + showDivider: true, + ), + SettingsItem( + icon: Icons.computer, + title: 'PC source code', + subtitle: 'github.com/${AppInfo.originalAuthor}/SpotiFLAC', + onTap: () => _launchUrl(AppInfo.originalGithubUrl), + showDivider: true, + ), + SettingsItem( + icon: Icons.bug_report_outlined, + title: 'Report an issue', + subtitle: 'Report any problems you encounter', + onTap: () => _launchUrl('${AppInfo.githubUrl}/issues/new'), + showDivider: true, + ), + SettingsItem( + icon: Icons.lightbulb_outline, + title: 'Feature request', + subtitle: 'Suggest new features for the app', + onTap: () => _launchUrl('${AppInfo.githubUrl}/issues/new'), + showDivider: false, + ), + ], + ), + ), + + // App info section + const SliverToBoxAdapter( + child: SettingsSectionHeader(title: 'App'), + ), + SliverToBoxAdapter( + child: SettingsGroup( + children: [ + SettingsItem( + icon: Icons.info_outline, + title: 'Version', + subtitle: 'v${AppInfo.version} (build ${AppInfo.buildNumber})', + showDivider: false, + ), + ], ), ), @@ -126,42 +151,214 @@ class AboutPage extends StatelessWidget { SliverToBoxAdapter( child: Padding( padding: const EdgeInsets.all(24), - child: Center(child: Text(AppInfo.copyright, - style: Theme.of(context).textTheme.bodySmall?.copyWith(color: colorScheme.onSurfaceVariant))), + child: Center( + child: Text( + AppInfo.copyright, + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: colorScheme.onSurfaceVariant, + ), + ), + ), + ), + ), + + // Bottom padding + const SliverToBoxAdapter(child: SizedBox(height: 16)), + ], + ), + ); + } + + static Future _launchUrl(String url) async { + final uri = Uri.parse(url); + // Use inAppBrowserView for reliable URL opening with app chooser + await launchUrl(uri, mode: LaunchMode.inAppBrowserView); + } +} + +class _AppHeaderCard extends StatelessWidget { + @override + Widget build(BuildContext context) { + final colorScheme = Theme.of(context).colorScheme; + final isDark = Theme.of(context).brightness == Brightness.dark; + + final cardColor = isDark + ? Color.alphaBlend(Colors.white.withValues(alpha: 0.08), colorScheme.surface) + : colorScheme.surfaceContainerHighest; + + return Container( + decoration: BoxDecoration( + color: cardColor, + borderRadius: BorderRadius.circular(20), + ), + padding: const EdgeInsets.all(24), + child: Column( + children: [ + // App logo + Container( + width: 88, + height: 88, + decoration: BoxDecoration( + color: colorScheme.primaryContainer, + borderRadius: BorderRadius.circular(24), + boxShadow: [ + BoxShadow( + color: colorScheme.primary.withValues(alpha: 0.2), + blurRadius: 16, + offset: const Offset(0, 4), + ), + ], + ), + child: ClipRRect( + borderRadius: BorderRadius.circular(24), + child: Image.asset( + 'assets/images/logo.png', + fit: BoxFit.cover, + errorBuilder: (_, _, _) => Icon( + Icons.music_note, + size: 48, + color: colorScheme.onPrimaryContainer, + ), + ), + ), + ), + const SizedBox(height: 16), + // App name + Text( + AppInfo.appName, + style: Theme.of(context).textTheme.headlineSmall?.copyWith( + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 4), + // Version badge + Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4), + decoration: BoxDecoration( + color: colorScheme.secondaryContainer, + borderRadius: BorderRadius.circular(12), + ), + child: Text( + 'v${AppInfo.version}', + style: Theme.of(context).textTheme.labelMedium?.copyWith( + color: colorScheme.onSecondaryContainer, + fontWeight: FontWeight.w600, + ), + ), + ), + const SizedBox(height: 16), + // Description + Text( + 'Download Spotify tracks in lossless quality from Tidal, Qobuz, and Amazon Music.', + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + color: colorScheme.onSurfaceVariant, ), ), ], ), ); } - - Future _launchUrl(String url) async { - final uri = Uri.parse(url); - if (await canLaunchUrl(uri)) await launchUrl(uri, mode: LaunchMode.externalApplication); - } } -class _SectionHeader extends StatelessWidget { - final String title; - const _SectionHeader({required this.title}); - @override - Widget build(BuildContext context) => Padding( - padding: const EdgeInsets.fromLTRB(24, 24, 24, 8), - child: Text(title, style: Theme.of(context).textTheme.titleSmall?.copyWith( - color: Theme.of(context).colorScheme.primary, fontWeight: FontWeight.w600)), - ); -} +class _ContributorItem extends StatelessWidget { + final String name; + final String description; + final String githubUsername; + final bool showDivider; + + const _ContributorItem({ + required this.name, + required this.description, + required this.githubUsername, + this.showDivider = false, + }); -class _CreditRow extends StatelessWidget { - final String label; - final String value; - const _CreditRow({required this.label, required this.value}); @override Widget build(BuildContext context) { final colorScheme = Theme.of(context).colorScheme; - return Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text(label, style: Theme.of(context).textTheme.bodyMedium?.copyWith(color: colorScheme.onSurfaceVariant)), - Text(value, style: Theme.of(context).textTheme.bodyMedium?.copyWith(fontWeight: FontWeight.w600)), - ]); + + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + InkWell( + onTap: () => _launchGitHub(githubUsername), + splashColor: colorScheme.primary.withValues(alpha: 0.12), + highlightColor: colorScheme.primary.withValues(alpha: 0.08), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12), + child: Row( + children: [ + // GitHub Avatar + ClipRRect( + borderRadius: BorderRadius.circular(12), + child: CachedNetworkImage( + imageUrl: 'https://github.com/$githubUsername.png', + width: 40, + height: 40, + fit: BoxFit.cover, + placeholder: (context, url) => Container( + width: 40, + height: 40, + color: colorScheme.surfaceContainerHighest, + child: Icon( + Icons.person, + color: colorScheme.onSurfaceVariant, + size: 20, + ), + ), + errorWidget: (context, url, error) => Container( + width: 40, + height: 40, + color: colorScheme.surfaceContainerHighest, + child: Icon( + Icons.person, + color: colorScheme.onSurfaceVariant, + size: 20, + ), + ), + ), + ), + const SizedBox(width: 16), + // Name and description + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + name, + style: Theme.of(context).textTheme.bodyLarge, + ), + const SizedBox(height: 2), + Text( + description, + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + color: colorScheme.onSurfaceVariant, + ), + ), + ], + ), + ), + // GitHub icon + Icon(Icons.chevron_right, color: colorScheme.onSurfaceVariant), + ], + ), + ), + ), + if (showDivider) + Divider( + height: 1, + thickness: 1, + indent: 76, + endIndent: 20, + color: colorScheme.outlineVariant.withValues(alpha: 0.3), + ), + ], + ); + } + + Future _launchGitHub(String username) async { + final uri = Uri.parse('https://github.com/$username'); + await launchUrl(uri, mode: LaunchMode.inAppBrowserView); } } diff --git a/lib/screens/settings/appearance_settings_page.dart b/lib/screens/settings/appearance_settings_page.dart index 40bbd4c5..3bd76127 100644 --- a/lib/screens/settings/appearance_settings_page.dart +++ b/lib/screens/settings/appearance_settings_page.dart @@ -40,7 +40,7 @@ class AppearanceSettingsPage extends ConsumerWidget { alignment: Alignment.bottomLeft, padding: EdgeInsets.only( left: Tween(begin: 56, end: 24).evaluate(animation), - bottom: Tween(begin: 12, end: 16).evaluate(animation), + bottom: Tween(begin: 16, end: 16).evaluate(animation), ), child: Text('Appearance', style: TextStyle( diff --git a/lib/screens/settings/download_settings_page.dart b/lib/screens/settings/download_settings_page.dart index 1c233ff0..4e7c49de 100644 --- a/lib/screens/settings/download_settings_page.dart +++ b/lib/screens/settings/download_settings_page.dart @@ -39,7 +39,7 @@ class DownloadSettingsPage extends ConsumerWidget { alignment: Alignment.bottomLeft, padding: EdgeInsets.only( left: Tween(begin: 56, end: 24).evaluate(animation), - bottom: Tween(begin: 12, end: 16).evaluate(animation), + bottom: Tween(begin: 16, end: 16).evaluate(animation), ), child: Text('Download', style: TextStyle( diff --git a/lib/screens/settings/options_settings_page.dart b/lib/screens/settings/options_settings_page.dart index 45f1d643..96a7c996 100644 --- a/lib/screens/settings/options_settings_page.dart +++ b/lib/screens/settings/options_settings_page.dart @@ -39,7 +39,7 @@ class OptionsSettingsPage extends ConsumerWidget { alignment: Alignment.bottomLeft, padding: EdgeInsets.only( left: Tween(begin: 56, end: 24).evaluate(animation), - bottom: Tween(begin: 12, end: 16).evaluate(animation), + bottom: Tween(begin: 16, end: 16).evaluate(animation), ), child: Text('Options', style: TextStyle( diff --git a/pubspec.yaml b/pubspec.yaml index fbae0b03..a2437c29 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: spotiflac_android description: Download Spotify tracks in FLAC from Tidal, Qobuz & Amazon Music publish_to: 'none' -version: 1.6.1+26 +version: 1.6.2+27 environment: sdk: ^3.10.0