✨ Error notifier
This commit is contained in:
parent
ddeda2ce23
commit
ee2633db52
@ -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());
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
37
lib/providers/error_notifier.dart
Normal file
37
lib/providers/error_notifier.dart
Normal 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'),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -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>([]);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
|
@ -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');
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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',
|
||||
|
@ -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');
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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)) {
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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(
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user