From bd9b527161e39ca09cd2e2b5d32384f26c0e8c71 Mon Sep 17 00:00:00 2001 From: zarzet Date: Fri, 2 Jan 2026 17:13:22 +0700 Subject: [PATCH] release: v1.6.0 - Live search, quality picker, dependency updates --- CHANGELOG.md | 6 +- lib/constants/app_info.dart | 4 +- lib/providers/track_provider.dart | 9 + lib/screens/home_tab.dart | 235 ++++++++++++------------- lib/screens/main_shell.dart | 6 + lib/screens/track_metadata_screen.dart | 8 +- pubspec.lock | 92 ++++------ pubspec.yaml | 8 +- 8 files changed, 181 insertions(+), 187 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 47478ccb..09c556b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## [1.5.7] - 2026-01-02 +## [1.6.0] - 2026-01-02 ### Added - **Manual Quality Selection**: New option to choose audio quality before each download @@ -32,6 +32,10 @@ ### Improved - **Code Quality**: Replaced all `print()` statements with structured logging using `logger` package +- **Dependencies Updated**: + - `share_plus`: 10.1.4 → 12.0.1 + - `flutter_local_notifications`: 18.0.1 → 19.0.0 + - `build_runner`: 2.4.15 → 2.10.4 ## [1.5.5] - 2026-01-02 diff --git a/lib/constants/app_info.dart b/lib/constants/app_info.dart index 02ced4f4..3b5398b4 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.5.7'; - static const String buildNumber = '24'; + static const String version = '1.6.0'; + static const String buildNumber = '25'; static const String fullVersion = '$version+$buildNumber'; static const String appName = 'SpotiFLAC'; diff --git a/lib/providers/track_provider.dart b/lib/providers/track_provider.dart index 8ff1eba0..638f7190 100644 --- a/lib/providers/track_provider.dart +++ b/lib/providers/track_provider.dart @@ -12,6 +12,7 @@ class TrackState { final String? coverUrl; final List? artistAlbums; // For artist page final TrackState? previousState; // For back navigation + final bool hasSearchText; // For back button handling const TrackState({ this.tracks = const [], @@ -23,6 +24,7 @@ class TrackState { this.coverUrl, this.artistAlbums, this.previousState, + this.hasSearchText = false, }); bool get canGoBack => previousState != null; @@ -40,6 +42,7 @@ class TrackState { List? artistAlbums, TrackState? previousState, bool clearPreviousState = false, + bool? hasSearchText, }) { return TrackState( tracks: tracks ?? this.tracks, @@ -51,6 +54,7 @@ class TrackState { coverUrl: coverUrl ?? this.coverUrl, artistAlbums: artistAlbums ?? this.artistAlbums, previousState: clearPreviousState ? null : (previousState ?? this.previousState), + hasSearchText: hasSearchText ?? this.hasSearchText, ); } } @@ -222,6 +226,11 @@ class TrackNotifier extends Notifier { state = const TrackState(); } + /// Set search text state for back button handling + void setSearchText(bool hasText) { + state = state.copyWith(hasSearchText: hasText); + } + /// Go back to previous state (if available) bool goBack() { if (state.previousState != null) { diff --git a/lib/screens/home_tab.dart b/lib/screens/home_tab.dart index 645b0554..508880fc 100644 --- a/lib/screens/home_tab.dart +++ b/lib/screens/home_tab.dart @@ -38,30 +38,24 @@ class _HomeTabState extends ConsumerState with AutomaticKeepAliveClient super.dispose(); } - /// Handle back button - returns true if handled, false to let system handle - bool _handleBack() { - final trackState = ref.read(trackProvider); - - // If we have previous state, go back to it - if (trackState.canGoBack) { - ref.read(trackProvider.notifier).goBack(); - return true; + /// Called when trackState changes - used to sync search bar with state + void _onTrackStateChanged(TrackState? previous, TrackState next) { + // If state was cleared (no content, no search text, not loading), clear the search bar + if (previous != null && + !next.hasContent && + !next.hasSearchText && + !next.isLoading && + _urlController.text.isNotEmpty) { + _urlController.clear(); + setState(() => _isTyping = false); } - - // If we're in results view but no previous state, clear and go to idle - if (_isTyping || trackState.hasContent) { - _clearAndRefresh(); - return true; - } - - // Let system handle (exit app) - return false; - } - - void _onSearchChanged() { + } void _onSearchChanged() { final text = _urlController.text.trim(); final wasFocused = _searchFocusNode.hasFocus; + // Update search text state for MainShell back button handling + ref.read(trackProvider.notifier).setSearchText(text.isNotEmpty); + // Update typing state immediately for UI transition if (text.isNotEmpty && !_isTyping) { setState(() => _isTyping = true); @@ -211,126 +205,121 @@ class _HomeTabState extends ConsumerState with AutomaticKeepAliveClient @override Widget build(BuildContext context) { super.build(context); + + // Listen for state changes to sync search bar + ref.listen(trackProvider, _onTrackStateChanged); + final trackState = ref.watch(trackProvider); final colorScheme = Theme.of(context).colorScheme; final hasResults = _hasResults; final screenHeight = MediaQuery.of(context).size.height; final historyItems = ref.watch(downloadHistoryProvider).items; - return PopScope( - canPop: false, - onPopInvokedWithResult: (didPop, _) { - if (didPop) return; - if (!_handleBack()) { - Navigator.of(context).maybePop(); - } - }, - child: Scaffold( - body: CustomScrollView( - slivers: [ - // App Bar - always present - SliverAppBar( - expandedHeight: 130, - collapsedHeight: kToolbarHeight, - floating: false, - pinned: true, - backgroundColor: colorScheme.surface, - surfaceTintColor: Colors.transparent, - automaticallyImplyLeading: false, - flexibleSpace: FlexibleSpaceBar( - expandedTitleScale: 1.3, - titlePadding: const EdgeInsets.only(left: 24, bottom: 16), - title: Text( - 'Search', - style: TextStyle( - fontSize: 28, - fontWeight: FontWeight.bold, - color: colorScheme.onSurface, - ), + return Scaffold( + body: CustomScrollView( + slivers: [ + // App Bar - always present + SliverAppBar( + expandedHeight: 130, + collapsedHeight: kToolbarHeight, + floating: false, + pinned: true, + backgroundColor: colorScheme.surface, + surfaceTintColor: Colors.transparent, + automaticallyImplyLeading: false, + flexibleSpace: FlexibleSpaceBar( + expandedTitleScale: 1.3, + titlePadding: const EdgeInsets.only(left: 24, bottom: 16), + title: Text( + 'Search', + style: TextStyle( + fontSize: 28, + fontWeight: FontWeight.bold, + color: colorScheme.onSurface, ), ), ), - - // Idle content (logo, title) - always in tree, animated size - SliverToBoxAdapter( - child: AnimatedSize( - duration: const Duration(milliseconds: 250), - curve: Curves.easeOut, - child: hasResults - ? const SizedBox.shrink() - : Column( - children: [ - SizedBox(height: screenHeight * 0.06), - Container( - padding: const EdgeInsets.all(24), - decoration: BoxDecoration( - color: colorScheme.primaryContainer.withValues(alpha: 0.3), - shape: BoxShape.circle, - ), - child: Icon(Icons.music_note, size: 48, color: colorScheme.primary), + ), + + // Idle content (logo, title) - always in tree, animated size + SliverToBoxAdapter( + child: AnimatedSize( + duration: const Duration(milliseconds: 250), + curve: Curves.easeOut, + child: hasResults + ? const SizedBox.shrink() + : Column( + children: [ + SizedBox(height: screenHeight * 0.06), + Container( + padding: const EdgeInsets.all(24), + decoration: BoxDecoration( + color: colorScheme.primaryContainer.withValues(alpha: 0.3), + shape: BoxShape.circle, ), - const SizedBox(height: 16), - Text( - 'Search Music', - style: Theme.of(context).textTheme.headlineSmall?.copyWith( - fontWeight: FontWeight.bold, - ), + child: Icon(Icons.music_note, size: 48, color: colorScheme.primary), + ), + const SizedBox(height: 16), + Text( + 'Search Music', + 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, - ), + ), + 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, ), - ], - ), - ), + ), + ], + ), ), - - // Search bar - always present at same position in tree - SliverToBoxAdapter( - child: Padding( - padding: EdgeInsets.fromLTRB(16, hasResults ? 8 : 32, 16, hasResults ? 8 : 16), - child: _buildSearchBar(colorScheme), - ), + ), + + // Search bar - always present at same position in tree + SliverToBoxAdapter( + child: Padding( + padding: EdgeInsets.fromLTRB(16, hasResults ? 8 : 32, 16, hasResults ? 8 : 16), + child: _buildSearchBar(colorScheme), ), - - // Idle content below search bar - always in tree - SliverToBoxAdapter( - child: AnimatedSize( - duration: const Duration(milliseconds: 250), - curve: Curves.easeOut, - child: hasResults - ? const SizedBox.shrink() - : Column( - children: [ - if (!ref.watch(settingsProvider).hasSearchedBefore) - Padding( - padding: const EdgeInsets.only(top: 8), - child: Text( - 'Supports: Track, Album, Playlist, Artist URLs', - textAlign: TextAlign.center, - style: Theme.of(context).textTheme.bodySmall?.copyWith( - color: colorScheme.onSurfaceVariant, - ), + ), + + // Idle content below search bar - always in tree + SliverToBoxAdapter( + child: AnimatedSize( + duration: const Duration(milliseconds: 250), + curve: Curves.easeOut, + child: hasResults + ? const SizedBox.shrink() + : Column( + children: [ + if (!ref.watch(settingsProvider).hasSearchedBefore) + Padding( + padding: const EdgeInsets.only(top: 8), + child: Text( + 'Supports: Track, Album, Playlist, Artist URLs', + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: colorScheme.onSurfaceVariant, ), ), - if (historyItems.isNotEmpty) - Padding( - padding: const EdgeInsets.fromLTRB(24, 32, 24, 24), - child: _buildRecentDownloads(historyItems, colorScheme), - ), - ], - ), - ), + ), + if (historyItems.isNotEmpty) + Padding( + padding: const EdgeInsets.fromLTRB(24, 32, 24, 24), + child: _buildRecentDownloads(historyItems, colorScheme), + ), + ], + ), ), - - // Results content - always in tree - ..._buildResultsContent(trackState, colorScheme, hasResults), - ], - ), + ), + + // Results content - always in tree + ..._buildResultsContent(trackState, colorScheme, hasResults), + ], ), ); } diff --git a/lib/screens/main_shell.dart b/lib/screens/main_shell.dart index d2b9f3d2..b4865aa9 100644 --- a/lib/screens/main_shell.dart +++ b/lib/screens/main_shell.dart @@ -150,6 +150,12 @@ class _MainShellState extends ConsumerState { return; } + // If on Search tab and has text in search bar or has content, clear it + if (_currentIndex == 0 && (trackState.hasSearchText || trackState.hasContent || trackState.isLoading)) { + ref.read(trackProvider.notifier).clear(); + return; + } + // If not on Search tab, go to Search tab first if (_currentIndex != 0) { _onNavTap(0); diff --git a/lib/screens/track_metadata_screen.dart b/lib/screens/track_metadata_screen.dart index 5fdec025..6012c291 100644 --- a/lib/screens/track_metadata_screen.dart +++ b/lib/screens/track_metadata_screen.dart @@ -938,9 +938,11 @@ class _TrackMetadataScreenState extends ConsumerState { return; } - await Share.shareXFiles( - [XFile(item.filePath)], - text: '${item.trackName} - ${item.artistName}', + await SharePlus.instance.share( + ShareParams( + files: [XFile(item.filePath)], + text: '${item.trackName} - ${item.artistName}', + ), ); } diff --git a/pubspec.lock b/pubspec.lock index 8747d330..ee612515 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,26 +5,26 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: da0d9209ca76bde579f2da330aeb9df62b6319c834fa7baae052021b0462401f + sha256: c209688d9f5a5f26b2fb47a188131a6fb9e876ae9e47af3737c0b4f58a93470d url: "https://pub.dev" source: hosted - version: "85.0.0" + version: "91.0.0" analyzer: dependency: transitive description: name: analyzer - sha256: f4ad0fea5f102201015c9aae9d93bc02f75dd9491529a8c21f88d17a8523d44c + sha256: f51c8499b35f9b26820cfe914828a6a98a94efd5cc78b37bb7d03debae3a1d08 url: "https://pub.dev" source: hosted - version: "7.6.0" + version: "8.4.1" analyzer_buffer: dependency: transitive description: name: analyzer_buffer - sha256: f7833bee67c03c37241c67f8741b17cc501b69d9758df7a5a4a13ed6c947be43 + sha256: aba2f75e63b3135fd1efaa8b6abefe1aa6e41b6bd9806221620fa48f98156033 url: "https://pub.dev" source: hosted - version: "0.1.10" + version: "0.1.11" archive: dependency: transitive description: @@ -61,18 +61,18 @@ packages: dependency: transitive description: name: build - sha256: "7174c5d84b0fed00a1f5e7543597b35d67560465ae3d909f0889b8b20419d5e3" + sha256: c1668065e9ba04752570ad7e038288559d1e2ca5c6d0131c0f5f55e39e777413 url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "4.0.3" build_config: dependency: transitive description: name: build_config - sha256: "4ae2de3e1e67ea270081eaee972e1bd8f027d459f249e0f1186730784c2e7e33" + sha256: "4f64382b97504dc2fcdf487d5aae33418e08b4703fc21249e4db6d804a4d0187" url: "https://pub.dev" source: hosted - version: "1.1.2" + version: "1.2.0" build_daemon: dependency: transitive description: @@ -81,30 +81,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.1.1" - build_resolvers: - dependency: transitive - description: - name: build_resolvers - sha256: "82730bf3d9043366ba8c02e4add05842a10739899520a6a22ddbd22d333bd5bb" - url: "https://pub.dev" - source: hosted - version: "3.0.1" build_runner: dependency: "direct dev" description: name: build_runner - sha256: "32c6b3d172f1f46b7c4df6bc4a47b8d88afb9e505dd4ace4af80b3c37e89832b" + sha256: "110c56ef29b5eb367b4d17fc79375fa8c18a6cd7acd92c05bb3986c17a079057" url: "https://pub.dev" source: hosted - version: "2.6.1" - build_runner_core: - dependency: transitive - description: - name: build_runner_core - sha256: "4b188774b369104ad96c0e4ca2471e5162f0566ce277771b179bed5eabf2d048" - url: "https://pub.dev" - source: hosted - version: "9.2.1" + version: "2.10.4" built_collection: dependency: transitive description: @@ -245,10 +229,10 @@ packages: dependency: transitive description: name: dart_style - sha256: "8a0e5fba27e8ee025d2ffb4ee820b4e6e2cf5e4246a6b1a477eb66866947e0bb" + sha256: a9c30492da18ff84efe2422ba2d319a89942d93e58eb0b73d32abe822ef54b7b url: "https://pub.dev" source: hosted - version: "3.1.1" + version: "3.1.3" dbus: dependency: transitive description: @@ -386,26 +370,34 @@ packages: dependency: "direct main" description: name: flutter_local_notifications - sha256: ef41ae901e7529e52934feba19ed82827b11baa67336829564aeab3129460610 + sha256: "19ffb0a8bb7407875555e5e98d7343a633bb73707bae6c6a5f37c90014077875" url: "https://pub.dev" source: hosted - version: "18.0.1" + version: "19.5.0" flutter_local_notifications_linux: dependency: transitive description: name: flutter_local_notifications_linux - sha256: "8f685642876742c941b29c32030f6f4f6dacd0e4eaecb3efbb187d6a3812ca01" + sha256: e3c277b2daab8e36ac5a6820536668d07e83851aeeb79c446e525a70710770a5 url: "https://pub.dev" source: hosted - version: "5.0.0" + version: "6.0.0" flutter_local_notifications_platform_interface: dependency: transitive description: name: flutter_local_notifications_platform_interface - sha256: "6c5b83c86bf819cdb177a9247a3722067dd8cc6313827ce7c77a4b238a26fd52" + sha256: "277d25d960c15674ce78ca97f57d0bae2ee401c844b6ac80fcd972a9c99d09fe" url: "https://pub.dev" source: hosted - version: "8.0.0" + version: "9.1.0" + flutter_local_notifications_windows: + dependency: transitive + description: + name: flutter_local_notifications_windows + sha256: "8d658f0d367c48bd420e7cf2d26655e2d1130147bca1eea917e576ca76668aaf" + url: "https://pub.dev" + source: hosted + version: "1.0.3" flutter_plugin_android_lifecycle: dependency: transitive description: @@ -628,10 +620,10 @@ packages: dependency: transitive description: name: mockito - sha256: "2314cbe9165bcd16106513df9cf3c3224713087f09723b128928dc11a4379f99" + sha256: dac24d461418d363778d53198d9ac0510b9d073869f078450f195766ec48d05e url: "https://pub.dev" source: hosted - version: "5.5.0" + version: "5.6.1" node_preamble: dependency: transitive description: @@ -884,18 +876,18 @@ packages: dependency: "direct main" description: name: share_plus - sha256: fce43200aa03ea87b91ce4c3ac79f0cecd52e2a7a56c7a4185023c271fbfa6da + sha256: "14c8860d4de93d3a7e53af51bff479598c4e999605290756bbbe45cf65b37840" url: "https://pub.dev" source: hosted - version: "10.1.4" + version: "12.0.1" share_plus_platform_interface: dependency: transitive description: name: share_plus_platform_interface - sha256: cc012a23fc2d479854e6c80150696c4a5f5bb62cb89af4de1c505cf78d0a5d0b + sha256: "88023e53a13429bd65d8e85e11a9b484f49d4c190abbd96c7932b74d6927cc9a" url: "https://pub.dev" source: hosted - version: "5.0.2" + version: "6.1.0" shared_preferences: dependency: "direct main" description: @@ -993,18 +985,18 @@ packages: dependency: transitive description: name: source_gen - sha256: "7b19d6ba131c6eb98bfcbf8d56c1a7002eba438af2e7ae6f8398b2b0f4f381e3" + sha256: "07b277b67e0096c45196cbddddf2d8c6ffc49342e88bf31d460ce04605ddac75" url: "https://pub.dev" source: hosted - version: "3.1.0" + version: "4.1.1" source_helper: dependency: transitive description: name: source_helper - sha256: a447acb083d3a5ef17f983dd36201aeea33fedadb3228fa831f2f0c92f0f3aca + sha256: "6a3c6cc82073a8797f8c4dc4572146114a39652851c157db37e964d9c7038723" url: "https://pub.dev" source: hosted - version: "1.3.7" + version: "1.3.8" source_map_stack_trace: dependency: transitive description: @@ -1157,14 +1149,6 @@ packages: url: "https://pub.dev" source: hosted version: "0.10.1" - timing: - dependency: transitive - description: - name: timing - sha256: "62ee18aca144e4a9f29d212f5a4c6a053be252b895ab14b5821996cff4ed90fe" - url: "https://pub.dev" - source: hosted - version: "1.0.2" typed_data: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 23fbaad7..520346c1 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.5.7+24 +version: 1.6.0+25 environment: sdk: ^3.10.0 @@ -46,7 +46,7 @@ dependencies: # Utils url_launcher: ^6.3.1 device_info_plus: ^12.3.0 - share_plus: ^10.1.4 + share_plus: ^12.0.1 receive_sharing_intent: ^1.8.1 logger: ^2.5.0 @@ -55,13 +55,13 @@ dependencies: open_filex: ^4.7.0 # Notifications - flutter_local_notifications: ^18.0.1 + flutter_local_notifications: ^19.0.0 dev_dependencies: flutter_test: sdk: flutter flutter_lints: ^6.0.0 - build_runner: ^2.4.15 + build_runner: ^2.10.4 riverpod_generator: ^4.0.0 json_serializable: ^6.11.2 flutter_launcher_icons: ^0.14.3