Error notifier

This commit is contained in:
LittleSheep 2024-09-02 21:20:30 +08:00
parent ddeda2ce23
commit ee2633db52
18 changed files with 183 additions and 73 deletions

View File

@ -8,6 +8,7 @@ import 'package:rhythm_box/providers/audio_player_stream.dart';
import 'package:rhythm_box/providers/auth.dart'; import 'package:rhythm_box/providers/auth.dart';
import 'package:rhythm_box/providers/database.dart'; import 'package:rhythm_box/providers/database.dart';
import 'package:rhythm_box/providers/endless_playback.dart'; import 'package:rhythm_box/providers/endless_playback.dart';
import 'package:rhythm_box/providers/error_notifier.dart';
import 'package:rhythm_box/providers/history.dart'; import 'package:rhythm_box/providers/history.dart';
import 'package:rhythm_box/providers/palette.dart'; import 'package:rhythm_box/providers/palette.dart';
import 'package:rhythm_box/providers/recent_played.dart'; import 'package:rhythm_box/providers/recent_played.dart';
@ -84,8 +85,8 @@ class RhythmApp extends StatelessWidget {
translations: AppTranslations(), translations: AppTranslations(),
onInit: () => _initializeProviders(context), onInit: () => _initializeProviders(context),
builder: (context, child) { builder: (context, child) {
return SystemShell( return ScaffoldMessenger(
child: ScaffoldMessenger( child: SystemShell(
child: child ?? const SizedBox(), child: child ?? const SizedBox(),
), ),
); );
@ -97,6 +98,8 @@ class RhythmApp extends StatelessWidget {
Get.lazyPut(() => SpotifyProvider()); Get.lazyPut(() => SpotifyProvider());
Get.lazyPut(() => SyncedLyricsProvider()); Get.lazyPut(() => SyncedLyricsProvider());
Get.put(ErrorNotifier());
Get.put(DatabaseProvider()); Get.put(DatabaseProvider());
Get.put(AuthenticationProvider()); Get.put(AuthenticationProvider());

View File

@ -3,6 +3,7 @@ import 'dart:developer';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:palette_generator/palette_generator.dart'; import 'package:palette_generator/palette_generator.dart';
import 'package:rhythm_box/providers/audio_player.dart'; import 'package:rhythm_box/providers/audio_player.dart';
import 'package:rhythm_box/providers/error_notifier.dart';
import 'package:rhythm_box/providers/history.dart'; import 'package:rhythm_box/providers/history.dart';
import 'package:rhythm_box/providers/palette.dart'; import 'package:rhythm_box/providers/palette.dart';
import 'package:rhythm_box/providers/scrobbler.dart'; import 'package:rhythm_box/providers/scrobbler.dart';
@ -126,7 +127,8 @@ class AudioPlayerStreamProvider extends GetxController {
.addTrack(playback.state.value.activeTrack!); .addTrack(playback.state.value.activeTrack!);
lastScrobbled = uid; lastScrobbled = uid;
} catch (e, stack) { } catch (e, stack) {
log('[Scrobbler] Error: $e; Trace:\n$stack'); Get.find<ErrorNotifier>()
.logError('[Scrobbler] Error: $e', trace: stack);
} }
}); });
} }

View File

@ -1,5 +1,4 @@
import 'dart:async'; import 'dart:async';
import 'dart:developer';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:rhythm_box/providers/audio_player.dart'; import 'package:rhythm_box/providers/audio_player.dart';
@ -9,6 +8,8 @@ import 'package:rhythm_box/providers/user_preferences.dart';
import 'package:rhythm_box/services/audio_player/audio_player.dart'; import 'package:rhythm_box/services/audio_player/audio_player.dart';
import 'package:spotify/spotify.dart'; import 'package:spotify/spotify.dart';
import 'error_notifier.dart';
class EndlessPlaybackProvider extends GetxController { class EndlessPlaybackProvider extends GetxController {
late final _auth = Get.find<AuthenticationProvider>(); late final _auth = Get.find<AuthenticationProvider>();
late final _playback = Get.find<AudioPlayerProvider>(); late final _playback = Get.find<AudioPlayerProvider>();
@ -88,7 +89,8 @@ class EndlessPlaybackProvider extends GetxController {
}), }),
); );
} catch (e, stack) { } catch (e, stack) {
log('[EndlessPlayback] Error: $e; Trace:\n$stack'); Get.find<ErrorNotifier>()
.logError('[EndlessPlayback] Error: $e', trace: stack);
} }
} }

View File

@ -0,0 +1,37 @@
import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class ErrorNotifier extends GetxController {
Rx<MaterialBanner?> showing = Rx(null);
void logError(String msg, {StackTrace? trace}) {
log('$msg${trace != null ? '\nTrace:\ntrace' : ''}');
showError(msg);
}
void showError(String msg) {
showing.value = MaterialBanner(
leading: const Icon(Icons.error),
content: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Something went wrong...',
style: TextStyle(fontWeight: FontWeight.bold),
),
Text(msg),
],
),
actions: [
TextButton(
onPressed: () {
showing.value = null;
},
child: const Text('Dismiss'),
),
],
);
}
}

View File

@ -1,8 +1,8 @@
import 'dart:async'; import 'dart:async';
import 'dart:developer';
import 'package:drift/drift.dart'; import 'package:drift/drift.dart';
import 'package:get/get.dart' hide Value; import 'package:get/get.dart' hide Value;
import 'package:rhythm_box/providers/database.dart'; import 'package:rhythm_box/providers/database.dart';
import 'package:rhythm_box/providers/error_notifier.dart';
import 'package:rhythm_box/services/artist.dart'; import 'package:rhythm_box/services/artist.dart';
import 'package:rhythm_box/services/database/database.dart'; import 'package:rhythm_box/services/database/database.dart';
import 'package:scrobblenaut/scrobblenaut.dart'; import 'package:scrobblenaut/scrobblenaut.dart';
@ -44,7 +44,8 @@ class ScrobblerProvider extends GetxController {
), ),
); );
} catch (e, stack) { } catch (e, stack) {
log('[Scrobble] Error: $e; Trace:\n$stack'); Get.find<ErrorNotifier>()
.logError('[Scrobbler] Error: $e', trace: stack);
scrobbler.value = null; scrobbler.value = null;
} }
} else { } else {
@ -63,8 +64,9 @@ class ScrobblerProvider extends GetxController {
timestamp: DateTime.now().toUtc(), timestamp: DateTime.now().toUtc(),
trackNumber: track.trackNumber, trackNumber: track.trackNumber,
); );
} catch (e, stackTrace) { } catch (e, stack) {
log('[Scrobble] Error: $e; Trace:\n$stackTrace'); Get.find<ErrorNotifier>()
.logError('[Scrobbler] Error: $e', trace: stack);
} }
}); });

View File

@ -1,8 +1,7 @@
import 'dart:developer';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:rhythm_box/providers/database.dart'; import 'package:rhythm_box/providers/database.dart';
import 'package:rhythm_box/providers/error_notifier.dart';
import 'package:rhythm_box/providers/user_preferences.dart'; import 'package:rhythm_box/providers/user_preferences.dart';
import 'package:rhythm_box/services/database/database.dart'; import 'package:rhythm_box/services/database/database.dart';
import 'package:rhythm_box/services/server/active_sourced_track.dart'; import 'package:rhythm_box/services/server/active_sourced_track.dart';
@ -72,7 +71,7 @@ Future<List<SkipSegmentTableData>> getAndCacheSkipSegments(String id) async {
..where((s) => s.trackId.equals(id))) ..where((s) => s.trackId.equals(id)))
.get(); .get();
} catch (e, stack) { } catch (e, stack) {
log('[SkipSegment] Error: $e; Trace:\n$stack'); Get.find<ErrorNotifier>().logError('[SkipSegment] Error: $e', trace: stack);
return List.castFrom<dynamic, SkipSegmentTableData>([]); return List.castFrom<dynamic, SkipSegmentTableData>([]);
} }
} }

View File

@ -102,11 +102,11 @@ class _PlayerScreenState extends State<PlayerScreen> {
maxWidth: maxAlbumSize, maxWidth: maxAlbumSize,
child: Hero( child: Hero(
tag: const Key('current-active-track-album-art'), tag: const Key('current-active-track-album-art'),
child: ClipRRect( child: AspectRatio(
borderRadius: aspectRatio: 1,
const BorderRadius.all(Radius.circular(16)), child: ClipRRect(
child: AspectRatio( borderRadius:
aspectRatio: 1, const BorderRadius.all(Radius.circular(16)),
child: _albumArt != null child: _albumArt != null
? AutoCacheImage( ? AutoCacheImage(
_albumArt!, _albumArt!,
@ -230,21 +230,23 @@ class _PlayerScreenState extends State<PlayerScreen> {
stream: audioPlayer.shuffledStream, stream: audioPlayer.shuffledStream,
builder: (context, snapshot) { builder: (context, snapshot) {
final shuffled = snapshot.data ?? false; final shuffled = snapshot.data ?? false;
return IconButton( return Obx(
icon: Icon( () => IconButton(
shuffled icon: Icon(
? Icons.shuffle_on_outlined shuffled
: Icons.shuffle, ? Icons.shuffle_on_outlined
: Icons.shuffle,
),
onPressed: _isFetchingActiveTrack
? null
: () {
if (shuffled) {
audioPlayer.setShuffle(false);
} else {
audioPlayer.setShuffle(true);
}
},
), ),
onPressed: _isFetchingActiveTrack
? null
: () {
if (shuffled) {
audioPlayer.setShuffle(false);
} else {
audioPlayer.setShuffle(true);
}
},
); );
}, },
), ),

View File

@ -1,10 +1,11 @@
import 'dart:developer';
import 'dart:io'; import 'dart:io';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:media_kit/media_kit.dart' hide Track; import 'package:media_kit/media_kit.dart' hide Track;
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:rhythm_box/platform.dart'; import 'package:rhythm_box/platform.dart';
import 'package:rhythm_box/providers/audio_player.dart';
import 'package:rhythm_box/providers/error_notifier.dart';
import 'package:rhythm_box/services/local_track.dart'; import 'package:rhythm_box/services/local_track.dart';
import 'package:rhythm_box/services/server/server.dart'; import 'package:rhythm_box/services/server/server.dart';
import 'package:rhythm_box/widgets/tracks/querying_track_info.dart'; import 'package:rhythm_box/widgets/tracks/querying_track_info.dart';
@ -93,7 +94,7 @@ abstract class AudioPlayerInterface {
), ),
) { ) {
_mkPlayer.stream.error.listen((event) { _mkPlayer.stream.error.listen((event) {
log('[Playback] Error: $event'); Get.find<ErrorNotifier>().logError('[Playback] Error: $event');
}); });
} }

View File

@ -90,16 +90,28 @@ class RhythmAudioPlayer extends AudioPlayerInterface
Future<void> skipToNext() async { Future<void> skipToNext() async {
Get.find<QueryingTrackInfoProvider>().isQueryingTrackInfo.value = true; Get.find<QueryingTrackInfoProvider>().isQueryingTrackInfo.value = true;
Get.find<AudioPlayerProvider>().durationBuffered.value =
const Duration(seconds: 0);
Get.find<AudioPlayerProvider>().durationCurrent.value =
const Duration(seconds: 0);
await _mkPlayer.next(); await _mkPlayer.next();
} }
Future<void> skipToPrevious() async { Future<void> skipToPrevious() async {
Get.find<QueryingTrackInfoProvider>().isQueryingTrackInfo.value = true; Get.find<QueryingTrackInfoProvider>().isQueryingTrackInfo.value = true;
Get.find<AudioPlayerProvider>().durationBuffered.value =
const Duration(seconds: 0);
Get.find<AudioPlayerProvider>().durationCurrent.value =
const Duration(seconds: 0);
await _mkPlayer.previous(); await _mkPlayer.previous();
} }
Future<void> jumpTo(int index) async { Future<void> jumpTo(int index) async {
Get.find<QueryingTrackInfoProvider>().isQueryingTrackInfo.value = true; Get.find<QueryingTrackInfoProvider>().isQueryingTrackInfo.value = true;
Get.find<AudioPlayerProvider>().durationBuffered.value =
const Duration(seconds: 0);
Get.find<AudioPlayerProvider>().durationCurrent.value =
const Duration(seconds: 0);
await _mkPlayer.jump(index); await _mkPlayer.jump(index);
} }

View File

@ -1,10 +1,11 @@
import 'dart:async'; import 'dart:async';
import 'dart:developer'; import 'package:get/get.dart';
import 'package:media_kit/media_kit.dart'; import 'package:media_kit/media_kit.dart';
import 'package:flutter_broadcasts/flutter_broadcasts.dart'; import 'package:flutter_broadcasts/flutter_broadcasts.dart';
import 'package:package_info_plus/package_info_plus.dart'; import 'package:package_info_plus/package_info_plus.dart';
import 'package:audio_session/audio_session.dart'; import 'package:audio_session/audio_session.dart';
import 'package:rhythm_box/platform.dart'; import 'package:rhythm_box/platform.dart';
import 'package:rhythm_box/providers/error_notifier.dart';
// ignore: implementation_imports // ignore: implementation_imports
import 'package:rhythm_box/services/audio_player/playback_state.dart'; import 'package:rhythm_box/services/audio_player/playback_state.dart';
@ -49,7 +50,7 @@ class CustomPlayer extends Player {
} }
}), }),
stream.error.listen((event) { stream.error.listen((event) {
log('[MediaKitError] $event'); Get.find<ErrorNotifier>().logError('[Playback] Error: $event');
}), }),
]; ];
PackageInfo.fromPlatform().then((packageInfo) { PackageInfo.fromPlatform().then((packageInfo) {

View File

@ -1,11 +1,10 @@
import 'dart:developer';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:drift/drift.dart'; import 'package:drift/drift.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:lrc/lrc.dart'; import 'package:lrc/lrc.dart';
import 'package:package_info_plus/package_info_plus.dart'; import 'package:package_info_plus/package_info_plus.dart';
import 'package:rhythm_box/providers/database.dart'; import 'package:rhythm_box/providers/database.dart';
import 'package:rhythm_box/providers/error_notifier.dart';
import 'package:rhythm_box/providers/spotify.dart'; import 'package:rhythm_box/providers/spotify.dart';
import 'package:rhythm_box/services/database/database.dart'; import 'package:rhythm_box/services/database/database.dart';
import 'package:rhythm_box/services/lyrics/model.dart'; import 'package:rhythm_box/services/lyrics/model.dart';
@ -164,8 +163,8 @@ class SyncedLyricsProvider extends GetxController {
} }
return lyrics; return lyrics;
} catch (e, stackTrace) { } catch (e, stack) {
log('[Lyrics] Error: $e; Trace:\n$stackTrace'); Get.find<ErrorNotifier>().logError('[Lyrics] Error: $e', trace: stack);
return SubtitleSimple( return SubtitleSimple(
uri: Uri.parse('https://example.com/not-found'), uri: Uri.parse('https://example.com/not-found'),
name: 'Lyrics Not Found', name: 'Lyrics Not Found',

View File

@ -1,9 +1,10 @@
import 'dart:developer';
import 'dart:io'; import 'dart:io';
import 'package:get/get.dart';
import 'package:media_kit/media_kit.dart' hide Track; import 'package:media_kit/media_kit.dart' hide Track;
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:rhythm_box/platform.dart'; import 'package:rhythm_box/platform.dart';
import 'package:rhythm_box/providers/error_notifier.dart';
import 'package:rhythm_box/services/audio_player/custom_player.dart'; import 'package:rhythm_box/services/audio_player/custom_player.dart';
import 'package:rhythm_box/services/local_track.dart'; import 'package:rhythm_box/services/local_track.dart';
import 'package:rhythm_box/services/sourced_track/sourced_track.dart'; import 'package:rhythm_box/services/sourced_track/sourced_track.dart';
@ -85,7 +86,7 @@ abstract class AudioPlayerInterface {
), ),
) { ) {
_mkPlayer.stream.error.listen((event) { _mkPlayer.stream.error.listen((event) {
log('[Playback] Error: $event'); Get.find<ErrorNotifier>().logError('[Playback] Error: $event');
}); });
} }

View File

@ -1,7 +1,6 @@
import 'dart:developer';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:rhythm_box/providers/audio_player.dart'; import 'package:rhythm_box/providers/audio_player.dart';
import 'package:rhythm_box/providers/error_notifier.dart';
import 'package:rhythm_box/services/audio_player/audio_player.dart'; import 'package:rhythm_box/services/audio_player/audio_player.dart';
import 'package:rhythm_box/services/sourced_track/models/source_info.dart'; import 'package:rhythm_box/services/sourced_track/models/source_info.dart';
import 'package:rhythm_box/services/sourced_track/sourced_track.dart'; import 'package:rhythm_box/services/sourced_track/sourced_track.dart';
@ -41,7 +40,9 @@ class ActiveSourcedTrackProvider extends GetxController {
await audioPlayer.removeTrack(oldActiveIndex); await audioPlayer.removeTrack(oldActiveIndex);
await playback.jumpToTrack(newTrack); await playback.jumpToTrack(newTrack);
} catch (e, stack) { } catch (e, stack) {
log('[Playback] Failed to swap with siblings. Error: $e; Trace:\n$stack'); Get.find<ErrorNotifier>().logError(
'[Playback] Failed to swap with siblings. Error: $e',
trace: stack);
} finally { } finally {
query.isQueryingTrackInfo.value = false; query.isQueryingTrackInfo.value = false;
await audioPlayer.resume(); await audioPlayer.resume();

View File

@ -1,9 +1,8 @@
import 'dart:developer';
import 'package:dio/dio.dart' hide Response; import 'package:dio/dio.dart' hide Response;
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:get/get.dart' hide Response; import 'package:get/get.dart' hide Response;
import 'package:rhythm_box/providers/audio_player.dart'; import 'package:rhythm_box/providers/audio_player.dart';
import 'package:rhythm_box/providers/error_notifier.dart';
import 'package:rhythm_box/services/audio_player/audio_player.dart'; import 'package:rhythm_box/services/audio_player/audio_player.dart';
import 'package:rhythm_box/services/server/active_sourced_track.dart'; import 'package:rhythm_box/services/server/active_sourced_track.dart';
import 'package:rhythm_box/services/server/sourced_track.dart'; import 'package:rhythm_box/services/server/sourced_track.dart';
@ -57,8 +56,9 @@ class ServerPlaybackRoutesProvider {
}, },
headers: res.headers.map, headers: res.headers.map,
); );
} catch (e, stackTrace) { } catch (e, stack) {
log('[PlaybackSever] Error: $e; Trace:\n $stackTrace'); Get.find<ErrorNotifier>()
.logError('[PlaybackSever] Error: $e', trace: stack);
return Response.internalServerError(); return Response.internalServerError();
} }
} }

View File

@ -1,10 +1,9 @@
import 'dart:developer';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:drift/drift.dart'; import 'package:drift/drift.dart';
import 'package:get/get.dart' hide Value; import 'package:get/get.dart' hide Value;
import 'package:http/http.dart'; import 'package:http/http.dart';
import 'package:rhythm_box/providers/database.dart'; import 'package:rhythm_box/providers/database.dart';
import 'package:rhythm_box/providers/error_notifier.dart';
import 'package:rhythm_box/services/database/database.dart'; import 'package:rhythm_box/services/database/database.dart';
import 'package:rhythm_box/services/utils.dart'; import 'package:rhythm_box/services/utils.dart';
import 'package:spotify/spotify.dart'; import 'package:spotify/spotify.dart';
@ -242,7 +241,8 @@ class YoutubeSourcedTrack extends SourcedTrack {
]; ];
} on VideoUnplayableException catch (e) { } on VideoUnplayableException catch (e) {
// Ignore this error and continue with the search // Ignore this error and continue with the search
log('[Source][YoutubeMusic] Unable to search data: $e'); Get.find<ErrorNotifier>().logError(
'[Source][YoutubeMusic] Unable to play stream on youtube: $e');
} }
} }
@ -250,7 +250,7 @@ class YoutubeSourcedTrack extends SourcedTrack {
final searchResults = await youtubeClient.search.search( final searchResults = await youtubeClient.search.search(
query, query,
filter: const SearchFilter('CAMSAhAB'), filter: TypeFilters.video,
); );
if (ServiceUtils.onlyContainsEnglish(query)) { if (ServiceUtils.onlyContainsEnglish(query)) {

View File

@ -1,12 +1,43 @@
import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:rhythm_box/platform.dart'; import 'package:rhythm_box/platform.dart';
import 'package:rhythm_box/providers/error_notifier.dart';
import 'package:window_manager/window_manager.dart'; import 'package:window_manager/window_manager.dart';
class SystemShell extends StatelessWidget { class SystemShell extends StatefulWidget {
final Widget child; final Widget child;
const SystemShell({super.key, required this.child}); const SystemShell({super.key, required this.child});
@override
State<SystemShell> createState() => _SystemShellState();
}
class _SystemShellState extends State<SystemShell> {
late final ErrorNotifier _errorNotifier = Get.find();
StreamSubscription? _subscription;
@override
void initState() {
super.initState();
_subscription = _errorNotifier.showing.listen((value) {
if (value == null) {
ScaffoldMessenger.of(context).clearMaterialBanners();
} else {
ScaffoldMessenger.of(context).showMaterialBanner(value);
}
});
}
@override
void dispose() {
_subscription?.cancel();
super.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (PlatformInfo.isMacOS) { if (PlatformInfo.isMacOS) {
@ -21,12 +52,12 @@ class SystemShell extends StatelessWidget {
thickness: 0.3, thickness: 0.3,
height: 0.3, height: 0.3,
), ),
Expanded(child: child), Expanded(child: widget.child),
], ],
), ),
); );
} }
return child; return widget.child;
} }
} }

View File

@ -50,25 +50,27 @@ class _SyncedLyricsState extends State<SyncedLyrics> {
Theme.of(context).colorScheme.onSurface.withOpacity(0.5); Theme.of(context).colorScheme.onSurface.withOpacity(0.5);
void _syncLyricsProgress() { void _syncLyricsProgress() {
for (var idx = 0; idx < _lyric!.lyrics.length; idx++) { if (_isLyricSynced) {
final lyricSlice = _lyric!.lyrics[idx]; for (var idx = 0; idx < _lyric!.lyrics.length; idx++) {
final lyricNextSlice = final lyricSlice = _lyric!.lyrics[idx];
idx + 1 < _lyric!.lyrics.length ? _lyric!.lyrics[idx + 1] : null; final lyricNextSlice =
final isActive = _playback.durationCurrent.value.inSeconds >= idx + 1 < _lyric!.lyrics.length ? _lyric!.lyrics[idx + 1] : null;
lyricSlice.time.inSeconds && final isActive = _playback.durationCurrent.value.inSeconds >=
(lyricNextSlice == null || lyricSlice.time.inSeconds &&
lyricNextSlice.time.inSeconds > (lyricNextSlice == null ||
_playback.durationCurrent.value.inSeconds); lyricNextSlice.time.inSeconds >
if (isActive) { _playback.durationCurrent.value.inSeconds);
_autoScrollController.scrollToIndex( if (isActive) {
idx, _autoScrollController.scrollToIndex(
preferPosition: AutoScrollPosition.middle, idx,
); preferPosition: AutoScrollPosition.middle,
return; );
return;
}
} }
} }
if (_lyric!.lyrics.isNotEmpty) { if (_lyric!.lyrics.isNotEmpty || !_isLyricSynced) {
_autoScrollController.scrollToIndex( _autoScrollController.scrollToIndex(
0, 0,
preferPosition: AutoScrollPosition.begin, preferPosition: AutoScrollPosition.begin,
@ -120,6 +122,18 @@ class _SyncedLyricsState extends State<SyncedLyrics> {
child: CircularProgressIndicator(), child: CircularProgressIndicator(),
), ),
), ),
if (_lyric != null && _lyric!.lyrics.isNotEmpty && !_isLyricSynced)
SliverToBoxAdapter(
child: Text(
'Lyrics isn\'t synced',
textAlign: MediaQuery.of(context).size.width >= 720
? TextAlign.center
: TextAlign.left,
).paddingSymmetric(
horizontal: 24,
vertical: 8,
),
),
if (_lyric != null && _lyric!.lyrics.isNotEmpty) if (_lyric != null && _lyric!.lyrics.isNotEmpty)
SliverList.builder( SliverList.builder(
itemCount: _lyric!.lyrics.length, itemCount: _lyric!.lyrics.length,
@ -132,7 +146,8 @@ class _SyncedLyricsState extends State<SyncedLyrics> {
lyricSlice.time.inSeconds && lyricSlice.time.inSeconds &&
(lyricNextSlice == null || (lyricNextSlice == null ||
lyricNextSlice.time.inSeconds > lyricNextSlice.time.inSeconds >
_playback.durationCurrent.value.inSeconds); _playback.durationCurrent.value.inSeconds) &&
_isLyricSynced;
if (_playback.durationCurrent.value.inSeconds == if (_playback.durationCurrent.value.inSeconds ==
lyricSlice.time.inSeconds && lyricSlice.time.inSeconds &&
@ -142,6 +157,7 @@ class _SyncedLyricsState extends State<SyncedLyrics> {
preferPosition: AutoScrollPosition.middle, preferPosition: AutoScrollPosition.middle,
); );
} }
return AutoScrollTag( return AutoScrollTag(
key: ValueKey(idx), key: ValueKey(idx),
index: idx, index: idx,
@ -215,6 +231,7 @@ class _SyncedLyricsState extends State<SyncedLyrics> {
children: [ children: [
Text( Text(
'Lyrics Not Found', 'Lyrics Not Found',
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.bodyLarge, style: Theme.of(context).textTheme.bodyLarge,
), ),
const Text( const Text(

View File

@ -16,7 +16,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts # In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix. # of the product and file versions while build-number is used as the build suffix.
version: 1.0.0+6 version: 1.0.0+7
environment: environment:
sdk: ^3.5.0 sdk: ^3.5.0