From e2ddcf1ffe4f53fd910389e6a8ac6b895592093f Mon Sep 17 00:00:00 2001 From: Schnitzel5 Date: Wed, 27 Aug 2025 00:01:18 +0200 Subject: [PATCH] add option to download online subtitles - button appears for downloaded and local entries - added app logging class --- lib/l10n/app_en.arb | 1 + lib/l10n/generated/app_localizations.dart | 6 ++ lib/l10n/generated/app_localizations_ar.dart | 3 + lib/l10n/generated/app_localizations_as.dart | 3 + lib/l10n/generated/app_localizations_de.dart | 3 + lib/l10n/generated/app_localizations_en.dart | 3 + lib/l10n/generated/app_localizations_es.dart | 3 + lib/l10n/generated/app_localizations_fr.dart | 3 + lib/l10n/generated/app_localizations_hi.dart | 3 + lib/l10n/generated/app_localizations_id.dart | 3 + lib/l10n/generated/app_localizations_it.dart | 3 + lib/l10n/generated/app_localizations_pt.dart | 3 + lib/l10n/generated/app_localizations_ru.dart | 3 + lib/l10n/generated/app_localizations_th.dart | 3 + lib/l10n/generated/app_localizations_tr.dart | 3 + lib/l10n/generated/app_localizations_zh.dart | 3 + lib/modules/anime/anime_player_view.dart | 1 + .../anime/widgets/search_subtitles.dart | 83 ++++++++++++++++++- 18 files changed, 131 insertions(+), 2 deletions(-) diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index c49e1d8c..776ed6a5 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -145,6 +145,7 @@ "check_for_update": "Check for update", "share_app_logs": "Share app logs", "no_app_logs": "No log.txt available!", + "failed": "Failed!", "n_days_ago": "{days} days ago", "today": "Today", "yesterday": "Yesterday", diff --git a/lib/l10n/generated/app_localizations.dart b/lib/l10n/generated/app_localizations.dart index 74b42a4d..dd7e21c1 100644 --- a/lib/l10n/generated/app_localizations.dart +++ b/lib/l10n/generated/app_localizations.dart @@ -945,6 +945,12 @@ abstract class AppLocalizations { /// **'No log.txt available!'** String get no_app_logs; + /// No description provided for @failed. + /// + /// In en, this message translates to: + /// **'Failed!'** + String get failed; + /// No description provided for @n_days_ago. /// /// In en, this message translates to: diff --git a/lib/l10n/generated/app_localizations_ar.dart b/lib/l10n/generated/app_localizations_ar.dart index bebde3e5..6975ef34 100644 --- a/lib/l10n/generated/app_localizations_ar.dart +++ b/lib/l10n/generated/app_localizations_ar.dart @@ -440,6 +440,9 @@ class AppLocalizationsAr extends AppLocalizations { @override String get no_app_logs => 'No log.txt available!'; + @override + String get failed => 'Failed!'; + @override String n_days_ago(Object days) { return 'منذ $days أيام'; diff --git a/lib/l10n/generated/app_localizations_as.dart b/lib/l10n/generated/app_localizations_as.dart index c5fbff38..633e73cc 100644 --- a/lib/l10n/generated/app_localizations_as.dart +++ b/lib/l10n/generated/app_localizations_as.dart @@ -442,6 +442,9 @@ class AppLocalizationsAs extends AppLocalizations { @override String get no_app_logs => 'No log.txt available!'; + @override + String get failed => 'Failed!'; + @override String n_days_ago(Object days) { return '$days দিনৰ আগতে'; diff --git a/lib/l10n/generated/app_localizations_de.dart b/lib/l10n/generated/app_localizations_de.dart index dc71f563..f17f44fd 100644 --- a/lib/l10n/generated/app_localizations_de.dart +++ b/lib/l10n/generated/app_localizations_de.dart @@ -444,6 +444,9 @@ class AppLocalizationsDe extends AppLocalizations { @override String get no_app_logs => 'No log.txt available!'; + @override + String get failed => 'Failed!'; + @override String n_days_ago(Object days) { return 'Vor $days Tagen'; diff --git a/lib/l10n/generated/app_localizations_en.dart b/lib/l10n/generated/app_localizations_en.dart index 7b8f26a3..4345ede8 100644 --- a/lib/l10n/generated/app_localizations_en.dart +++ b/lib/l10n/generated/app_localizations_en.dart @@ -442,6 +442,9 @@ class AppLocalizationsEn extends AppLocalizations { @override String get no_app_logs => 'No log.txt available!'; + @override + String get failed => 'Failed!'; + @override String n_days_ago(Object days) { return '$days days ago'; diff --git a/lib/l10n/generated/app_localizations_es.dart b/lib/l10n/generated/app_localizations_es.dart index 26d92e82..9e52974c 100644 --- a/lib/l10n/generated/app_localizations_es.dart +++ b/lib/l10n/generated/app_localizations_es.dart @@ -446,6 +446,9 @@ class AppLocalizationsEs extends AppLocalizations { @override String get no_app_logs => 'No log.txt available!'; + @override + String get failed => 'Failed!'; + @override String n_days_ago(Object days) { return 'hace $days días'; diff --git a/lib/l10n/generated/app_localizations_fr.dart b/lib/l10n/generated/app_localizations_fr.dart index 5e70df1b..b0e1bc7d 100644 --- a/lib/l10n/generated/app_localizations_fr.dart +++ b/lib/l10n/generated/app_localizations_fr.dart @@ -448,6 +448,9 @@ class AppLocalizationsFr extends AppLocalizations { @override String get no_app_logs => 'No log.txt available!'; + @override + String get failed => 'Failed!'; + @override String n_days_ago(Object days) { return 'Il y a $days jours'; diff --git a/lib/l10n/generated/app_localizations_hi.dart b/lib/l10n/generated/app_localizations_hi.dart index 13e2fbd9..8858ec0a 100644 --- a/lib/l10n/generated/app_localizations_hi.dart +++ b/lib/l10n/generated/app_localizations_hi.dart @@ -442,6 +442,9 @@ class AppLocalizationsHi extends AppLocalizations { @override String get no_app_logs => 'No log.txt available!'; + @override + String get failed => 'Failed!'; + @override String n_days_ago(Object days) { return '$days दिन पहले'; diff --git a/lib/l10n/generated/app_localizations_id.dart b/lib/l10n/generated/app_localizations_id.dart index 82dcec91..3da5e0ce 100644 --- a/lib/l10n/generated/app_localizations_id.dart +++ b/lib/l10n/generated/app_localizations_id.dart @@ -446,6 +446,9 @@ class AppLocalizationsId extends AppLocalizations { @override String get no_app_logs => 'No log.txt available!'; + @override + String get failed => 'Failed!'; + @override String n_days_ago(Object days) { return '$days Hari yang Lalu'; diff --git a/lib/l10n/generated/app_localizations_it.dart b/lib/l10n/generated/app_localizations_it.dart index 6607c76d..88d30d55 100644 --- a/lib/l10n/generated/app_localizations_it.dart +++ b/lib/l10n/generated/app_localizations_it.dart @@ -446,6 +446,9 @@ class AppLocalizationsIt extends AppLocalizations { @override String get no_app_logs => 'No log.txt available!'; + @override + String get failed => 'Failed!'; + @override String n_days_ago(Object days) { return '$days giorni fa'; diff --git a/lib/l10n/generated/app_localizations_pt.dart b/lib/l10n/generated/app_localizations_pt.dart index caa47153..47d4042c 100644 --- a/lib/l10n/generated/app_localizations_pt.dart +++ b/lib/l10n/generated/app_localizations_pt.dart @@ -446,6 +446,9 @@ class AppLocalizationsPt extends AppLocalizations { @override String get no_app_logs => 'No log.txt available!'; + @override + String get failed => 'Failed!'; + @override String n_days_ago(Object days) { return '$days dias atrás'; diff --git a/lib/l10n/generated/app_localizations_ru.dart b/lib/l10n/generated/app_localizations_ru.dart index f926133d..03db5780 100644 --- a/lib/l10n/generated/app_localizations_ru.dart +++ b/lib/l10n/generated/app_localizations_ru.dart @@ -447,6 +447,9 @@ class AppLocalizationsRu extends AppLocalizations { @override String get no_app_logs => 'No log.txt available!'; + @override + String get failed => 'Failed!'; + @override String n_days_ago(Object days) { return '$days дней назад'; diff --git a/lib/l10n/generated/app_localizations_th.dart b/lib/l10n/generated/app_localizations_th.dart index 9253d21a..b459c2eb 100644 --- a/lib/l10n/generated/app_localizations_th.dart +++ b/lib/l10n/generated/app_localizations_th.dart @@ -442,6 +442,9 @@ class AppLocalizationsTh extends AppLocalizations { @override String get no_app_logs => 'No log.txt available!'; + @override + String get failed => 'Failed!'; + @override String n_days_ago(Object days) { return '$days วันที่แล้ว'; diff --git a/lib/l10n/generated/app_localizations_tr.dart b/lib/l10n/generated/app_localizations_tr.dart index ee42bf30..4b1e09bf 100644 --- a/lib/l10n/generated/app_localizations_tr.dart +++ b/lib/l10n/generated/app_localizations_tr.dart @@ -442,6 +442,9 @@ class AppLocalizationsTr extends AppLocalizations { @override String get no_app_logs => 'No log.txt available!'; + @override + String get failed => 'Failed!'; + @override String n_days_ago(Object days) { return '$days gün önce'; diff --git a/lib/l10n/generated/app_localizations_zh.dart b/lib/l10n/generated/app_localizations_zh.dart index 6669c85d..a8929b72 100644 --- a/lib/l10n/generated/app_localizations_zh.dart +++ b/lib/l10n/generated/app_localizations_zh.dart @@ -434,6 +434,9 @@ class AppLocalizationsZh extends AppLocalizations { @override String get no_app_logs => 'No log.txt available!'; + @override + String get failed => 'Failed!'; + @override String n_days_ago(Object days) { return '$days天前'; diff --git a/lib/modules/anime/anime_player_view.dart b/lib/modules/anime/anime_player_view.dart index fa7ed3ad..330a1955 100644 --- a/lib/modules/anime/anime_player_view.dart +++ b/lib/modules/anime/anime_player_view.dart @@ -1355,6 +1355,7 @@ mp.register_script_message('call_button_${button.id}_long', button${button.id}lo await subtitlesSearchraggableMenu( context, chapter: widget.episode, + isLocal: widget.isLocal, ) as ImdbSubtitle?; if (subtitle != null && context.mounted) { diff --git a/lib/modules/anime/widgets/search_subtitles.dart b/lib/modules/anime/widgets/search_subtitles.dart index 307b695a..44552ced 100644 --- a/lib/modules/anime/widgets/search_subtitles.dart +++ b/lib/modules/anime/widgets/search_subtitles.dart @@ -2,17 +2,30 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:mangayomi/eval/model/m_bridge.dart'; import 'package:mangayomi/models/chapter.dart'; import 'package:mangayomi/modules/widgets/custom_extended_image_provider.dart'; import 'package:mangayomi/modules/widgets/error_text.dart'; import 'package:mangayomi/modules/widgets/progress_center.dart'; +import 'package:mangayomi/providers/l10n_providers.dart'; +import 'package:mangayomi/providers/storage_provider.dart'; import 'package:mangayomi/services/fetch_subtitles.dart'; +import 'package:mangayomi/services/http/m_client.dart'; +import 'package:mangayomi/services/http/rhttp/src/model/settings.dart'; import 'package:mangayomi/utils/extensions/build_context_extensions.dart'; +import 'package:mangayomi/utils/extensions/string_extensions.dart'; +import 'package:mangayomi/utils/log/logger.dart'; +import 'package:path/path.dart' as path; import 'package:super_sliver_list/super_sliver_list.dart'; class SubtitlesWidgetSearch extends ConsumerStatefulWidget { final Chapter chapter; - const SubtitlesWidgetSearch({required this.chapter, super.key}); + final bool isLocal; + const SubtitlesWidgetSearch({ + required this.chapter, + required this.isLocal, + super.key, + }); @override ConsumerState createState() => @@ -296,6 +309,12 @@ class _SubtitlesWidgetSearchState extends ConsumerState { ), ], ), + if (isSubtitles && widget.isLocal) + OutlinedButton.icon( + onPressed: () async => _downloadSubtitle(index), + label: Text(context.l10n.download), + icon: Icon(Icons.download_outlined), + ), ], ), ], @@ -308,11 +327,71 @@ class _SubtitlesWidgetSearchState extends ConsumerState { }, ); } + + Future _downloadSubtitle(int index) async { + botToast(context.l10n.started); + try { + final subtitle = subtitles![index]; + final storageProvider = StorageProvider(); + final chapterDirectory = (await storageProvider.getMangaChapterDirectory( + widget.chapter, + ))!; + final subtitleFile = File( + path.join( + '${chapterDirectory.path}_subtitles', + '${subtitle.language}.srt', + ), + ); + final client = MClient.httpClient( + settings: const ClientSettings( + throwOnStatusCode: false, + tlsSettings: TlsSettings(verifyCertificates: false), + ), + ); + await subtitleFile.create(recursive: true); + final response = await _withRetry( + () => client.get(Uri.parse(subtitle.url ?? '')), + ); + if (response.statusCode != 200) { + AppLogger.log( + 'Warning: Failed to download subtitle file: ${subtitle.language}', + ); + return; + } + AppLogger.log('Subtitle file downloaded: ${subtitle.language}'); + await subtitleFile.writeAsBytes(response.bodyBytes); + if (context.mounted) { + botToast(context.l10n.finished("")); + } + } catch (e) { + AppLogger.log("Failed to download subtitle:", logLevel: LogLevel.error); + AppLogger.log(e.toString(), logLevel: LogLevel.error); + if (context.mounted) { + botToast(context.l10n.failed); + } + rethrow; + } + } + + Future _withRetry(Future Function() operation) async { + int attempts = 0; + while (true) { + try { + attempts++; + return await operation(); + } catch (e) { + if (attempts >= 3) { + AppLogger.log("Request retries failed", logLevel: LogLevel.error); + } + } + } + } } subtitlesSearchraggableMenu( BuildContext context, { required Chapter chapter, + required bool isLocal, }) async { var padding = MediaQuery.of(context).padding; return await showDialog( @@ -352,7 +431,7 @@ subtitlesSearchraggableMenu( ], ), ), - SubtitlesWidgetSearch(chapter: chapter), + SubtitlesWidgetSearch(chapter: chapter, isLocal: isLocal), ], ), ),