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

View File

@ -3,6 +3,7 @@ import 'dart:developer';
import 'package:get/get.dart';
import 'package:palette_generator/palette_generator.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/palette.dart';
import 'package:rhythm_box/providers/scrobbler.dart';
@ -126,7 +127,8 @@ class AudioPlayerStreamProvider extends GetxController {
.addTrack(playback.state.value.activeTrack!);
lastScrobbled = uid;
} 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:developer';
import 'package:get/get.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:spotify/spotify.dart';
import 'error_notifier.dart';
class EndlessPlaybackProvider extends GetxController {
late final _auth = Get.find<AuthenticationProvider>();
late final _playback = Get.find<AudioPlayerProvider>();
@ -88,7 +89,8 @@ class EndlessPlaybackProvider extends GetxController {
}),
);
} 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:developer';
import 'package:drift/drift.dart';
import 'package:get/get.dart' hide Value;
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/database/database.dart';
import 'package:scrobblenaut/scrobblenaut.dart';
@ -44,7 +44,8 @@ class ScrobblerProvider extends GetxController {
),
);
} catch (e, stack) {
log('[Scrobble] Error: $e; Trace:\n$stack');
Get.find<ErrorNotifier>()
.logError('[Scrobbler] Error: $e', trace: stack);
scrobbler.value = null;
}
} else {
@ -63,8 +64,9 @@ class ScrobblerProvider extends GetxController {
timestamp: DateTime.now().toUtc(),
trackNumber: track.trackNumber,
);
} catch (e, stackTrace) {
log('[Scrobble] Error: $e; Trace:\n$stackTrace');
} catch (e, stack) {
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:get/get.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/services/database/database.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)))
.get();
} catch (e, stack) {
log('[SkipSegment] Error: $e; Trace:\n$stack');
Get.find<ErrorNotifier>().logError('[SkipSegment] Error: $e', trace: stack);
return List.castFrom<dynamic, SkipSegmentTableData>([]);
}
}

View File

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

View File

@ -1,10 +1,11 @@
import 'dart:developer';
import 'dart:io';
import 'package:get/get.dart';
import 'package:media_kit/media_kit.dart' hide Track;
import 'package:flutter/foundation.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/server/server.dart';
import 'package:rhythm_box/widgets/tracks/querying_track_info.dart';
@ -93,7 +94,7 @@ abstract class AudioPlayerInterface {
),
) {
_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 {
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();
}
Future<void> skipToPrevious() async {
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();
}
Future<void> jumpTo(int index) async {
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);
}

View File

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

View File

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

View File

@ -1,9 +1,10 @@
import 'dart:developer';
import 'dart:io';
import 'package:get/get.dart';
import 'package:media_kit/media_kit.dart' hide Track;
import 'package:flutter/foundation.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/local_track.dart';
import 'package:rhythm_box/services/sourced_track/sourced_track.dart';
@ -85,7 +86,7 @@ abstract class AudioPlayerInterface {
),
) {
_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: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/sourced_track/models/source_info.dart';
import 'package:rhythm_box/services/sourced_track/sourced_track.dart';
@ -41,7 +40,9 @@ class ActiveSourcedTrackProvider extends GetxController {
await audioPlayer.removeTrack(oldActiveIndex);
await playback.jumpToTrack(newTrack);
} 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 {
query.isQueryingTrackInfo.value = false;
await audioPlayer.resume();

View File

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

View File

@ -1,10 +1,9 @@
import 'dart:developer';
import 'package:collection/collection.dart';
import 'package:drift/drift.dart';
import 'package:get/get.dart' hide Value;
import 'package:http/http.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/utils.dart';
import 'package:spotify/spotify.dart';
@ -242,7 +241,8 @@ class YoutubeSourcedTrack extends SourcedTrack {
];
} on VideoUnplayableException catch (e) {
// 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(
query,
filter: const SearchFilter('CAMSAhAB'),
filter: TypeFilters.video,
);
if (ServiceUtils.onlyContainsEnglish(query)) {

View File

@ -1,12 +1,43 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:rhythm_box/platform.dart';
import 'package:rhythm_box/providers/error_notifier.dart';
import 'package:window_manager/window_manager.dart';
class SystemShell extends StatelessWidget {
class SystemShell extends StatefulWidget {
final Widget 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
Widget build(BuildContext context) {
if (PlatformInfo.isMacOS) {
@ -21,12 +52,12 @@ class SystemShell extends StatelessWidget {
thickness: 0.3,
height: 0.3,
),
Expanded(child: child),
Expanded(child: widget.child),
],
),
);
}
return child;
return widget.child;
}
}

View File

@ -50,6 +50,7 @@ class _SyncedLyricsState extends State<SyncedLyrics> {
Theme.of(context).colorScheme.onSurface.withOpacity(0.5);
void _syncLyricsProgress() {
if (_isLyricSynced) {
for (var idx = 0; idx < _lyric!.lyrics.length; idx++) {
final lyricSlice = _lyric!.lyrics[idx];
final lyricNextSlice =
@ -67,8 +68,9 @@ class _SyncedLyricsState extends State<SyncedLyrics> {
return;
}
}
}
if (_lyric!.lyrics.isNotEmpty) {
if (_lyric!.lyrics.isNotEmpty || !_isLyricSynced) {
_autoScrollController.scrollToIndex(
0,
preferPosition: AutoScrollPosition.begin,
@ -120,6 +122,18 @@ class _SyncedLyricsState extends State<SyncedLyrics> {
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)
SliverList.builder(
itemCount: _lyric!.lyrics.length,
@ -132,7 +146,8 @@ class _SyncedLyricsState extends State<SyncedLyrics> {
lyricSlice.time.inSeconds &&
(lyricNextSlice == null ||
lyricNextSlice.time.inSeconds >
_playback.durationCurrent.value.inSeconds);
_playback.durationCurrent.value.inSeconds) &&
_isLyricSynced;
if (_playback.durationCurrent.value.inSeconds ==
lyricSlice.time.inSeconds &&
@ -142,6 +157,7 @@ class _SyncedLyricsState extends State<SyncedLyrics> {
preferPosition: AutoScrollPosition.middle,
);
}
return AutoScrollTag(
key: ValueKey(idx),
index: idx,
@ -215,6 +231,7 @@ class _SyncedLyricsState extends State<SyncedLyrics> {
children: [
Text(
'Lyrics Not Found',
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.bodyLarge,
),
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
# 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.
version: 1.0.0+6
version: 1.0.0+7
environment:
sdk: ^3.5.0