From c69f81ec6f01f0f67ad06446e890aa1351516626 Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Tue, 15 Aug 2023 13:00:38 +0600 Subject: [PATCH] feat: show error dialog on piped API 500 error --- .../shared/dialogs/piped_down_dialog.dart | 46 ++++++++++++ lib/l10n/app_en.arb | 4 +- lib/models/spotube_track.dart | 6 +- lib/services/youtube/youtube.dart | 73 +++++++++++++------ untranslated_messages.json | 43 ++++++++++- 5 files changed, 143 insertions(+), 29 deletions(-) create mode 100644 lib/components/shared/dialogs/piped_down_dialog.dart diff --git a/lib/components/shared/dialogs/piped_down_dialog.dart b/lib/components/shared/dialogs/piped_down_dialog.dart new file mode 100644 index 000000000..03362ed48 --- /dev/null +++ b/lib/components/shared/dialogs/piped_down_dialog.dart @@ -0,0 +1,46 @@ +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:spotube/collections/spotube_icons.dart'; +import 'package:spotube/extensions/context.dart'; +import 'package:spotube/provider/user_preferences_provider.dart'; + +class PipedDownDialog extends HookConsumerWidget { + const PipedDownDialog({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context, ref) { + final pipedInstance = + ref.watch(userPreferencesProvider.select((s) => s.pipedInstance)); + final ThemeData(:colorScheme) = Theme.of(context); + + return AlertDialog( + insetPadding: const EdgeInsets.all(6), + contentPadding: const EdgeInsets.all(6), + icon: Icon( + SpotubeIcons.error, + color: colorScheme.error, + ), + title: Text( + context.l10n.piped_api_down, + style: TextStyle(color: colorScheme.error), + ), + content: Card( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: + Text(context.l10n.piped_down_error_instructions(pipedInstance)), + ), + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: Text(context.l10n.ok), + ), + FilledButton( + onPressed: () => Navigator.pop(context), + child: Text(context.l10n.settings), + ), + ], + ); + } +} diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 15d9b4fe2..d1d723bd1 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -254,5 +254,7 @@ "ok": "Ok", "failed_to_encrypt": "Failed to encrypt", "encryption_failed_warning": "Spotube uses encryption to securely store your data. But failed to do so. So it'll fallback to insecure storage\nIf you're using linux, please make sure you've any secret-service (gnome-keyring, kde-wallet, keepassxc etc) installed", - "querying_info": "Querying info..." + "querying_info": "Querying info...", + "piped_api_down": "Piped API is down", + "piped_down_error_instructions": "The Piped instance {pipedInstance} is currently down\n\nEither change the instance or change the 'API type' to official YouTube API\n\nMake sure to restart the app after change" } \ No newline at end of file diff --git a/lib/models/spotube_track.dart b/lib/models/spotube_track.dart index 32b16533d..7ec9d4460 100644 --- a/lib/models/spotube_track.dart +++ b/lib/models/spotube_track.dart @@ -66,19 +66,17 @@ class SpotubeTrack extends Track { await client.search("$title - ${artists.join(", ")}").then( (res) { final siblings = res + .sorted((a, b) => a.views.compareTo(b.views)) .where((item) { return artists.any( (artist) => + client.preferences.searchMode == SearchMode.youtube || artist.toLowerCase() == item.channelName.toLowerCase(), ); }) .take(10) .toList(); - if (siblings.isEmpty) { - return res.take(10).toList(); - } - return siblings; }, ); diff --git a/lib/services/youtube/youtube.dart b/lib/services/youtube/youtube.dart index 1922faa2b..fbf559d46 100644 --- a/lib/services/youtube/youtube.dart +++ b/lib/services/youtube/youtube.dart @@ -1,5 +1,8 @@ -import 'package:flutter_desktop_tools/flutter_desktop_tools.dart'; +import 'package:dio/dio.dart'; +import 'package:flutter/material.dart'; import 'package:piped_client/piped_client.dart'; +import 'package:spotube/collections/routes.dart'; +import 'package:spotube/components/shared/dialogs/piped_down_dialog.dart'; import 'package:spotube/models/matched_track.dart'; import 'package:spotube/provider/user_preferences_provider.dart'; import 'package:spotube/utils/primitive_utils.dart'; @@ -133,6 +136,18 @@ class YoutubeEndpoints { } } + Future showPipedErrorDialog(Exception e) async { + if (e is DioException && (e.response?.statusCode ?? 0) >= 500) { + final context = rootNavigatorKey?.currentContext; + if (context != null) { + await showDialog( + context: context, + builder: (context) => const PipedDownDialog(), + ); + } + } + } + Future> search(String query) async { if (youtube != null) { final res = await youtube!.search( @@ -142,22 +157,27 @@ class YoutubeEndpoints { return res.map(YoutubeVideoInfo.fromVideo).toList(); } else { - final res = await piped!.search( - query, - switch (preferences.searchMode) { - SearchMode.youtube => PipedFilter.video, - SearchMode.youtubeMusic => PipedFilter.musicSongs, - }, - ); - return res.items - .whereType() - .map( - (e) => YoutubeVideoInfo.fromSearchItemStream( - e, - preferences.searchMode, - ), - ) - .toList(); + try { + final res = await piped!.search( + query, + switch (preferences.searchMode) { + SearchMode.youtube => PipedFilter.video, + SearchMode.youtubeMusic => PipedFilter.musicSongs, + }, + ); + return res.items + .whereType() + .map( + (e) => YoutubeVideoInfo.fromSearchItemStream( + e, + preferences.searchMode, + ), + ) + .toList(); + } on Exception catch (e) { + await showPipedErrorDialog(e); + rethrow; + } } } @@ -193,7 +213,9 @@ class YoutubeEndpoints { } Future<(YoutubeVideoInfo info, String streamingUrl)> video( - String id, SearchMode searchMode) async { + String id, + SearchMode searchMode, + ) async { if (youtube != null) { final res = await youtube!.videos.get(id); return ( @@ -201,11 +223,16 @@ class YoutubeEndpoints { await streamingUrl(id), ); } else { - final res = await piped!.streams(id); - return ( - YoutubeVideoInfo.fromStreamResponse(res, searchMode), - _pipedStreamResponseToStreamUrl(res), - ); + try { + final res = await piped!.streams(id); + return ( + YoutubeVideoInfo.fromStreamResponse(res, searchMode), + _pipedStreamResponseToStreamUrl(res), + ); + } on Exception catch (e) { + await showPipedErrorDialog(e); + rethrow; + } } } } diff --git a/untranslated_messages.json b/untranslated_messages.json index 9e26dfeeb..0647a58ba 100644 --- a/untranslated_messages.json +++ b/untranslated_messages.json @@ -1 +1,42 @@ -{} \ No newline at end of file +{ + "bn": [ + "piped_api_down", + "piped_down_error_instructions" + ], + + "ca": [ + "querying_info", + "piped_api_down", + "piped_down_error_instructions" + ], + + "de": [ + "piped_api_down", + "piped_down_error_instructions" + ], + + "es": [ + "piped_api_down", + "piped_down_error_instructions" + ], + + "fr": [ + "piped_api_down", + "piped_down_error_instructions" + ], + + "hi": [ + "piped_api_down", + "piped_down_error_instructions" + ], + + "ja": [ + "piped_api_down", + "piped_down_error_instructions" + ], + + "zh": [ + "piped_api_down", + "piped_down_error_instructions" + ] +}