mirror of
https://github.com/kodjodevf/mangayomi.git
synced 2026-01-11 22:40:36 +00:00
Merge pull request #567 from Schnitzel5/logger
add option to download online subtitles
This commit is contained in:
commit
40a0e080f9
27 changed files with 413 additions and 26 deletions
|
|
@ -143,6 +143,9 @@
|
|||
"nsfw_sources_info": "This does not prevent unofficial or potentially incorrectly flagged extensions from surfacing NSFW (18+) content within the app",
|
||||
"version": "Version",
|
||||
"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",
|
||||
|
|
|
|||
|
|
@ -933,6 +933,24 @@ abstract class AppLocalizations {
|
|||
/// **'Check for update'**
|
||||
String get check_for_update;
|
||||
|
||||
/// No description provided for @share_app_logs.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Share app logs'**
|
||||
String get share_app_logs;
|
||||
|
||||
/// No description provided for @no_app_logs.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'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:
|
||||
|
|
|
|||
|
|
@ -434,6 +434,15 @@ class AppLocalizationsAr extends AppLocalizations {
|
|||
@override
|
||||
String get check_for_update => 'التحقق من التحديثات';
|
||||
|
||||
@override
|
||||
String get share_app_logs => 'Share app logs';
|
||||
|
||||
@override
|
||||
String get no_app_logs => 'No log.txt available!';
|
||||
|
||||
@override
|
||||
String get failed => 'Failed!';
|
||||
|
||||
@override
|
||||
String n_days_ago(Object days) {
|
||||
return 'منذ $days أيام';
|
||||
|
|
|
|||
|
|
@ -436,6 +436,15 @@ class AppLocalizationsAs extends AppLocalizations {
|
|||
@override
|
||||
String get check_for_update => 'আপডেটৰ বাবে পৰীক্ষা কৰক';
|
||||
|
||||
@override
|
||||
String get share_app_logs => 'Share app logs';
|
||||
|
||||
@override
|
||||
String get no_app_logs => 'No log.txt available!';
|
||||
|
||||
@override
|
||||
String get failed => 'Failed!';
|
||||
|
||||
@override
|
||||
String n_days_ago(Object days) {
|
||||
return '$days দিনৰ আগতে';
|
||||
|
|
|
|||
|
|
@ -438,6 +438,15 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
@override
|
||||
String get check_for_update => 'Auf Aktualisierung prüfen';
|
||||
|
||||
@override
|
||||
String get share_app_logs => 'Share app logs';
|
||||
|
||||
@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';
|
||||
|
|
|
|||
|
|
@ -436,6 +436,15 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
@override
|
||||
String get check_for_update => 'Check for update';
|
||||
|
||||
@override
|
||||
String get share_app_logs => 'Share app logs';
|
||||
|
||||
@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';
|
||||
|
|
|
|||
|
|
@ -440,6 +440,15 @@ class AppLocalizationsEs extends AppLocalizations {
|
|||
@override
|
||||
String get check_for_update => 'Buscar actualizaciones';
|
||||
|
||||
@override
|
||||
String get share_app_logs => 'Share app logs';
|
||||
|
||||
@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';
|
||||
|
|
|
|||
|
|
@ -442,6 +442,15 @@ class AppLocalizationsFr extends AppLocalizations {
|
|||
@override
|
||||
String get check_for_update => 'Rechercher des mises à jour';
|
||||
|
||||
@override
|
||||
String get share_app_logs => 'Share app logs';
|
||||
|
||||
@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';
|
||||
|
|
|
|||
|
|
@ -436,6 +436,15 @@ class AppLocalizationsHi extends AppLocalizations {
|
|||
@override
|
||||
String get check_for_update => 'अपडेट के लिए जांचें';
|
||||
|
||||
@override
|
||||
String get share_app_logs => 'Share app logs';
|
||||
|
||||
@override
|
||||
String get no_app_logs => 'No log.txt available!';
|
||||
|
||||
@override
|
||||
String get failed => 'Failed!';
|
||||
|
||||
@override
|
||||
String n_days_ago(Object days) {
|
||||
return '$days दिन पहले';
|
||||
|
|
|
|||
|
|
@ -440,6 +440,15 @@ class AppLocalizationsId extends AppLocalizations {
|
|||
@override
|
||||
String get check_for_update => 'Periksa Pembaruan';
|
||||
|
||||
@override
|
||||
String get share_app_logs => 'Share app logs';
|
||||
|
||||
@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';
|
||||
|
|
|
|||
|
|
@ -440,6 +440,15 @@ class AppLocalizationsIt extends AppLocalizations {
|
|||
@override
|
||||
String get check_for_update => 'Controlla aggiornamenti';
|
||||
|
||||
@override
|
||||
String get share_app_logs => 'Share app logs';
|
||||
|
||||
@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';
|
||||
|
|
|
|||
|
|
@ -440,6 +440,15 @@ class AppLocalizationsPt extends AppLocalizations {
|
|||
@override
|
||||
String get check_for_update => 'Verificar atualização';
|
||||
|
||||
@override
|
||||
String get share_app_logs => 'Share app logs';
|
||||
|
||||
@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';
|
||||
|
|
|
|||
|
|
@ -441,6 +441,15 @@ class AppLocalizationsRu extends AppLocalizations {
|
|||
@override
|
||||
String get check_for_update => 'Проверить обновления';
|
||||
|
||||
@override
|
||||
String get share_app_logs => 'Share app logs';
|
||||
|
||||
@override
|
||||
String get no_app_logs => 'No log.txt available!';
|
||||
|
||||
@override
|
||||
String get failed => 'Failed!';
|
||||
|
||||
@override
|
||||
String n_days_ago(Object days) {
|
||||
return '$days дней назад';
|
||||
|
|
|
|||
|
|
@ -436,6 +436,15 @@ class AppLocalizationsTh extends AppLocalizations {
|
|||
@override
|
||||
String get check_for_update => 'ตรวจสอบการอัพเดท';
|
||||
|
||||
@override
|
||||
String get share_app_logs => 'Share app logs';
|
||||
|
||||
@override
|
||||
String get no_app_logs => 'No log.txt available!';
|
||||
|
||||
@override
|
||||
String get failed => 'Failed!';
|
||||
|
||||
@override
|
||||
String n_days_ago(Object days) {
|
||||
return '$days วันที่แล้ว';
|
||||
|
|
|
|||
|
|
@ -436,6 +436,15 @@ class AppLocalizationsTr extends AppLocalizations {
|
|||
@override
|
||||
String get check_for_update => 'Güncelleme Kontrol Et';
|
||||
|
||||
@override
|
||||
String get share_app_logs => 'Share app logs';
|
||||
|
||||
@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';
|
||||
|
|
|
|||
|
|
@ -428,6 +428,15 @@ class AppLocalizationsZh extends AppLocalizations {
|
|||
@override
|
||||
String get check_for_update => '检查更新';
|
||||
|
||||
@override
|
||||
String get share_app_logs => 'Share app logs';
|
||||
|
||||
@override
|
||||
String get no_app_logs => 'No log.txt available!';
|
||||
|
||||
@override
|
||||
String get failed => 'Failed!';
|
||||
|
||||
@override
|
||||
String n_days_ago(Object days) {
|
||||
return '$days天前';
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ import 'package:mangayomi/l10n/generated/app_localizations.dart';
|
|||
import 'package:mangayomi/services/http/m_client.dart';
|
||||
import 'package:mangayomi/src/rust/frb_generated.dart';
|
||||
import 'package:mangayomi/utils/discord_rpc.dart';
|
||||
import 'package:mangayomi/utils/log/logger.dart';
|
||||
import 'package:mangayomi/utils/url_protocol/api.dart';
|
||||
import 'package:mangayomi/modules/more/settings/appearance/providers/theme_provider.dart';
|
||||
import 'package:mangayomi/modules/library/providers/file_scanner.dart';
|
||||
|
|
@ -66,6 +67,7 @@ void main(List<String> args) async {
|
|||
);
|
||||
}
|
||||
}
|
||||
await AppLogger.init();
|
||||
isar = await StorageProvider().initDB(null, inspector: kDebugMode);
|
||||
await Hive.initFlutter();
|
||||
Hive.registerAdapter(TrackSearchAdapter());
|
||||
|
|
@ -144,6 +146,7 @@ class _MyAppState extends ConsumerState<MyApp> {
|
|||
void dispose() {
|
||||
_linkSubscription?.cancel();
|
||||
discordRpc?.destroy();
|
||||
AppLogger.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
@ -2254,13 +2255,22 @@ mp.register_script_message('call_button_${button.id}_long', button${button.id}lo
|
|||
format: "image/png",
|
||||
includeLibassSubtitles: _includeSubtitles,
|
||||
);
|
||||
await Share.shareXFiles([
|
||||
XFile.fromData(
|
||||
imageBytes!,
|
||||
name: name,
|
||||
mimeType: 'image/png',
|
||||
),
|
||||
]);
|
||||
if (context.mounted) {
|
||||
final box =
|
||||
context.findRenderObject() as RenderBox?;
|
||||
await Share.shareXFiles(
|
||||
[
|
||||
XFile.fromData(
|
||||
imageBytes!,
|
||||
name: name,
|
||||
mimeType: 'image/png',
|
||||
),
|
||||
],
|
||||
sharePositionOrigin:
|
||||
box!.localToGlobal(Offset.zero) &
|
||||
box.size,
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
button(
|
||||
|
|
|
|||
|
|
@ -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<SubtitlesWidgetSearch> createState() =>
|
||||
|
|
@ -296,6 +309,12 @@ class _SubtitlesWidgetSearchState extends ConsumerState<SubtitlesWidgetSearch> {
|
|||
),
|
||||
],
|
||||
),
|
||||
if (isSubtitles && widget.isLocal)
|
||||
OutlinedButton.icon(
|
||||
onPressed: () async => _downloadSubtitle(index),
|
||||
label: Text(context.l10n.download),
|
||||
icon: Icon(Icons.download_outlined),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
|
|
@ -308,11 +327,70 @@ class _SubtitlesWidgetSearchState extends ConsumerState<SubtitlesWidgetSearch> {
|
|||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<T> _withRetry<T>(Future<T> 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 +430,7 @@ subtitlesSearchraggableMenu(
|
|||
],
|
||||
),
|
||||
),
|
||||
SubtitlesWidgetSearch(chapter: chapter),
|
||||
SubtitlesWidgetSearch(chapter: chapter, isLocal: isLocal),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -654,7 +654,14 @@ class _MangaDetailViewState extends ConsumerState<MangaDetailView>
|
|||
);
|
||||
final url =
|
||||
"${source!.baseUrl}${widget.manga!.link!.getUrlWithoutDomain}";
|
||||
Share.share(url);
|
||||
final box =
|
||||
context.findRenderObject() as RenderBox?;
|
||||
Share.share(
|
||||
url,
|
||||
sharePositionOrigin:
|
||||
box!.localToGlobal(Offset.zero) &
|
||||
box.size,
|
||||
);
|
||||
break;
|
||||
case 3:
|
||||
context.push("/migrate", extra: widget.manga);
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ class ChapterPageDownload extends ConsumerWidget {
|
|||
ref.read(downloadChapterProvider(chapter: chapter, useWifi: useWifi));
|
||||
}
|
||||
|
||||
void _sendFile() async {
|
||||
void _sendFile(BuildContext context) async {
|
||||
final storageProvider = StorageProvider();
|
||||
final mangaDir = await storageProvider.getMangaMainDirectory(chapter);
|
||||
final path = await storageProvider.getMangaChapterDirectory(
|
||||
|
|
@ -52,8 +52,13 @@ class ChapterPageDownload extends ConsumerWidget {
|
|||
} else {
|
||||
files = path!.listSync().map((e) => XFile(e.path)).toList();
|
||||
}
|
||||
if (files.isNotEmpty) {
|
||||
Share.shareXFiles(files, text: chapter.name);
|
||||
if (files.isNotEmpty && context.mounted) {
|
||||
final box = context.findRenderObject() as RenderBox?;
|
||||
Share.shareXFiles(
|
||||
files,
|
||||
text: chapter.name,
|
||||
sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -123,7 +128,7 @@ class ChapterPageDownload extends ConsumerWidget {
|
|||
),
|
||||
onSelected: (value) {
|
||||
if (value == 0) {
|
||||
_sendFile();
|
||||
_sendFile(context);
|
||||
} else if (value == 1) {
|
||||
_deleteFile(download.id!);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -401,13 +401,21 @@ class _MangaChapterPageGalleryState
|
|||
context.l10n.share,
|
||||
Icons.share_outlined,
|
||||
() async {
|
||||
await Share.shareXFiles([
|
||||
XFile.fromData(
|
||||
imageBytes,
|
||||
name: name,
|
||||
mimeType: 'image/png',
|
||||
),
|
||||
]);
|
||||
if (context.mounted) {
|
||||
final box =
|
||||
context.findRenderObject() as RenderBox?;
|
||||
await Share.shareXFiles(
|
||||
[
|
||||
XFile.fromData(
|
||||
imageBytes,
|
||||
name: name,
|
||||
mimeType: 'image/png',
|
||||
),
|
||||
],
|
||||
sharePositionOrigin:
|
||||
box!.localToGlobal(Offset.zero) & box.size,
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
button(
|
||||
|
|
|
|||
|
|
@ -1,12 +1,19 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:mangayomi/eval/model/m_bridge.dart';
|
||||
import 'package:mangayomi/main.dart';
|
||||
import 'package:mangayomi/models/settings.dart';
|
||||
import 'package:mangayomi/modules/more/about/providers/check_for_update.dart';
|
||||
import 'package:mangayomi/modules/more/about/providers/get_package_info.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:path/path.dart' as path;
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
class AboutScreen extends ConsumerWidget {
|
||||
|
|
@ -73,6 +80,35 @@ class AboutScreen extends ConsumerWidget {
|
|||
},
|
||||
title: Text(l10n.check_for_update),
|
||||
),
|
||||
ListTile(
|
||||
onTap: () async {
|
||||
final storage = StorageProvider();
|
||||
final directory = await storage.getDefaultDirectory();
|
||||
final file = File(
|
||||
path.join(directory!.path, 'logs.txt'),
|
||||
);
|
||||
if (await file.exists()) {
|
||||
if (Platform.isLinux) {
|
||||
await Clipboard.setData(
|
||||
ClipboardData(text: file.path),
|
||||
);
|
||||
}
|
||||
if (context.mounted) {
|
||||
final box =
|
||||
context.findRenderObject() as RenderBox?;
|
||||
Share.shareXFiles(
|
||||
[XFile(file.path)],
|
||||
text: "log.txt",
|
||||
sharePositionOrigin:
|
||||
box!.localToGlobal(Offset.zero) & box.size,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
botToast(l10n.no_app_logs);
|
||||
}
|
||||
},
|
||||
title: Text(l10n.share_app_logs),
|
||||
),
|
||||
// ListTile(
|
||||
// onTap: () {},
|
||||
// title: const Text("What's news"),
|
||||
|
|
|
|||
|
|
@ -176,9 +176,13 @@ Future<void> doBackUp(
|
|||
alignment: Alignment.topLeft,
|
||||
child: ElevatedButton(
|
||||
onPressed: () {
|
||||
Share.shareXFiles([
|
||||
XFile(p.join(path, "$name.backup")),
|
||||
], text: "$name.backup");
|
||||
final box = context.findRenderObject() as RenderBox?;
|
||||
Share.shareXFiles(
|
||||
[XFile(p.join(path, "$name.backup"))],
|
||||
text: "$name.backup",
|
||||
sharePositionOrigin:
|
||||
box!.localToGlobal(Offset.zero) & box.size,
|
||||
);
|
||||
},
|
||||
child: Text(context.l10n.share),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -239,7 +239,14 @@ class _MangaWebViewState extends ConsumerState<MangaWebView> {
|
|||
if (value == 0) {
|
||||
_webViewController?.reload();
|
||||
} else if (value == 1) {
|
||||
Share.share(_url);
|
||||
final box =
|
||||
context.findRenderObject() as RenderBox?;
|
||||
Share.share(
|
||||
_url,
|
||||
sharePositionOrigin:
|
||||
box!.localToGlobal(Offset.zero) &
|
||||
box.size,
|
||||
);
|
||||
} else if (value == 2) {
|
||||
await InAppBrowser.openWithSystemBrowser(
|
||||
url: WebUri(_url),
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import 'package:mangayomi/services/download_manager/m3u8/models/download.dart';
|
|||
import 'package:mangayomi/services/download_manager/m3u8/models/ts_info.dart';
|
||||
import 'package:mangayomi/src/rust/frb_generated.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:encrypt/encrypt.dart' as encrypt;
|
||||
import 'package:convert/convert.dart';
|
||||
|
|
@ -49,6 +50,7 @@ class M3u8Downloader {
|
|||
if (kDebugMode) {
|
||||
log('[M3u8Downloader] $message');
|
||||
}
|
||||
AppLogger.log(message);
|
||||
}
|
||||
|
||||
void close() {
|
||||
|
|
@ -151,11 +153,11 @@ class M3u8Downloader {
|
|||
continue;
|
||||
}
|
||||
_log('Downloading subtitle file: ${element.label}');
|
||||
subtitleFile.createSync(recursive: true);
|
||||
if (element.file == null || element.file!.trim().isEmpty) {
|
||||
_log('Warning: No subtitle file: ${element.label}');
|
||||
continue;
|
||||
}
|
||||
subtitleFile.createSync(recursive: true);
|
||||
if (element.file!.startsWith("http")) {
|
||||
final response = await _withRetry(
|
||||
() =>
|
||||
|
|
@ -168,10 +170,13 @@ class M3u8Downloader {
|
|||
_log('Subtitle file downloaded: ${element.label}');
|
||||
await subtitleFile.writeAsBytes(response.bodyBytes);
|
||||
} else {
|
||||
_log('Subtitle file written: ${element.label}');
|
||||
await subtitleFile.writeAsString(element.file!);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
AppLogger.log("Download failed", logLevel: LogLevel.error);
|
||||
AppLogger.log(e.toString(), logLevel: LogLevel.error);
|
||||
throw M3u8DownloaderException('Download failed', e);
|
||||
} finally {
|
||||
close();
|
||||
|
|
|
|||
77
lib/utils/log/logger.dart
Normal file
77
lib/utils/log/logger.dart
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:mangayomi/providers/storage_provider.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
|
||||
class AppLogger {
|
||||
static final _logQueue = StreamController<String>();
|
||||
static late File _logFile;
|
||||
static late IOSink _sink;
|
||||
static bool _initialized = false;
|
||||
|
||||
/// Initialize the logger
|
||||
static Future<void> init() async {
|
||||
final storage = StorageProvider();
|
||||
final directory = await storage.getDefaultDirectory();
|
||||
_logFile = File(path.join(directory!.path, 'logs.txt'));
|
||||
|
||||
if (await _logFile.exists() && await _logFile.length() > 100 * 1024) {
|
||||
await _logFile.delete();
|
||||
}
|
||||
|
||||
if (!await _logFile.exists()) {
|
||||
await _logFile.create(recursive: true);
|
||||
}
|
||||
|
||||
_sink = _logFile.openWrite(mode: FileMode.append);
|
||||
_initialized = true;
|
||||
|
||||
_logQueue.stream.listen((log) {
|
||||
_sink.writeln(log);
|
||||
});
|
||||
|
||||
log('\n\nLogger initialized\n\n');
|
||||
}
|
||||
|
||||
static void log(String message, {LogLevel logLevel = LogLevel.info}) {
|
||||
if (!_initialized) return;
|
||||
|
||||
final now = DateTime.now();
|
||||
final timestamp =
|
||||
'${now.day.toString().padLeft(2, '0')}/${now.month.toString().padLeft(2, '0')}/${now.year.toString().padLeft(4, '0')} '
|
||||
'${now.hour.toString().padLeft(2, '0')}:${now.minute.toString().padLeft(2, '0')}:${now.second.toString().padLeft(2, '0')}';
|
||||
|
||||
final logMessage = '[$timestamp][${logLevel.toString()}] $message';
|
||||
_logQueue.add(logMessage);
|
||||
}
|
||||
|
||||
static Future<void> dispose() async {
|
||||
if (!_initialized) return;
|
||||
await _logQueue.close();
|
||||
await _sink.flush();
|
||||
await _sink.close();
|
||||
_initialized = false;
|
||||
}
|
||||
}
|
||||
|
||||
enum LogLevel {
|
||||
debug,
|
||||
info,
|
||||
warning,
|
||||
error;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
switch (this) {
|
||||
case LogLevel.debug:
|
||||
return 'DEBUG';
|
||||
case LogLevel.info:
|
||||
return 'INFO';
|
||||
case LogLevel.warning:
|
||||
return 'WARNING';
|
||||
case LogLevel.error:
|
||||
return 'ERROR';
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue