mirror of
https://github.com/kodjodevf/mangayomi.git
synced 2026-03-11 17:25:32 +00:00
added custom buttons
This commit is contained in:
parent
1f0938fab9
commit
4682ab3577
26 changed files with 926 additions and 349 deletions
BIN
assets/mangayomi_mpv.zip
Normal file
BIN
assets/mangayomi_mpv.zip
Normal file
Binary file not shown.
|
|
@ -481,10 +481,12 @@
|
|||
"custom_buttons_info": "Execute Javascript code with custom buttons",
|
||||
"custom_buttons_edit": "Edit custom buttons",
|
||||
"custom_buttons_add": "Add custom button",
|
||||
"custom_buttons_edit": "Add custom button",
|
||||
"custom_buttons_delete": "Delete custom button",
|
||||
"custom_buttons_text": "Button text",
|
||||
"custom_buttons_text_req": "Button text required",
|
||||
"custom_buttons_js_code": "Javascript code",
|
||||
"custom_buttons_js_code_req": "Javascript code required",
|
||||
"custom_buttons_js_code_long": "Javascript code (on long press)",
|
||||
"custom_buttons_startup": "On startup",
|
||||
"custom_buttons_startup": "Javascript code (on startup)",
|
||||
"n_days": "{n} days"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2948,7 +2948,7 @@ abstract class AppLocalizations {
|
|||
/// No description provided for @custom_buttons_edit.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Add custom button'**
|
||||
/// **'Edit custom buttons'**
|
||||
String get custom_buttons_edit;
|
||||
|
||||
/// No description provided for @custom_buttons_add.
|
||||
|
|
@ -2957,18 +2957,36 @@ abstract class AppLocalizations {
|
|||
/// **'Add custom button'**
|
||||
String get custom_buttons_add;
|
||||
|
||||
/// No description provided for @custom_buttons_delete.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Delete custom button'**
|
||||
String get custom_buttons_delete;
|
||||
|
||||
/// No description provided for @custom_buttons_text.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Button text'**
|
||||
String get custom_buttons_text;
|
||||
|
||||
/// No description provided for @custom_buttons_text_req.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Button text required'**
|
||||
String get custom_buttons_text_req;
|
||||
|
||||
/// No description provided for @custom_buttons_js_code.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Javascript code'**
|
||||
String get custom_buttons_js_code;
|
||||
|
||||
/// No description provided for @custom_buttons_js_code_req.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Javascript code required'**
|
||||
String get custom_buttons_js_code_req;
|
||||
|
||||
/// No description provided for @custom_buttons_js_code_long.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
|
|
@ -2978,7 +2996,7 @@ abstract class AppLocalizations {
|
|||
/// No description provided for @custom_buttons_startup.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'On startup'**
|
||||
/// **'Javascript code (on startup)'**
|
||||
String get custom_buttons_startup;
|
||||
|
||||
/// No description provided for @n_days.
|
||||
|
|
|
|||
|
|
@ -1518,22 +1518,31 @@ class AppLocalizationsAr extends AppLocalizations {
|
|||
'Execute Javascript code with custom buttons';
|
||||
|
||||
@override
|
||||
String get custom_buttons_edit => 'Add custom button';
|
||||
String get custom_buttons_edit => 'Edit custom buttons';
|
||||
|
||||
@override
|
||||
String get custom_buttons_add => 'Add custom button';
|
||||
|
||||
@override
|
||||
String get custom_buttons_delete => 'Delete custom button';
|
||||
|
||||
@override
|
||||
String get custom_buttons_text => 'Button text';
|
||||
|
||||
@override
|
||||
String get custom_buttons_text_req => 'Button text required';
|
||||
|
||||
@override
|
||||
String get custom_buttons_js_code => 'Javascript code';
|
||||
|
||||
@override
|
||||
String get custom_buttons_js_code_req => 'Javascript code required';
|
||||
|
||||
@override
|
||||
String get custom_buttons_js_code_long => 'Javascript code (on long press)';
|
||||
|
||||
@override
|
||||
String get custom_buttons_startup => 'On startup';
|
||||
String get custom_buttons_startup => 'Javascript code (on startup)';
|
||||
|
||||
@override
|
||||
String n_days(Object n) {
|
||||
|
|
|
|||
|
|
@ -1531,22 +1531,31 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
'Execute Javascript code with custom buttons';
|
||||
|
||||
@override
|
||||
String get custom_buttons_edit => 'Add custom button';
|
||||
String get custom_buttons_edit => 'Edit custom buttons';
|
||||
|
||||
@override
|
||||
String get custom_buttons_add => 'Add custom button';
|
||||
|
||||
@override
|
||||
String get custom_buttons_delete => 'Delete custom button';
|
||||
|
||||
@override
|
||||
String get custom_buttons_text => 'Button text';
|
||||
|
||||
@override
|
||||
String get custom_buttons_text_req => 'Button text required';
|
||||
|
||||
@override
|
||||
String get custom_buttons_js_code => 'Javascript code';
|
||||
|
||||
@override
|
||||
String get custom_buttons_js_code_req => 'Javascript code required';
|
||||
|
||||
@override
|
||||
String get custom_buttons_js_code_long => 'Javascript code (on long press)';
|
||||
|
||||
@override
|
||||
String get custom_buttons_startup => 'On startup';
|
||||
String get custom_buttons_startup => 'Javascript code (on startup)';
|
||||
|
||||
@override
|
||||
String n_days(Object n) {
|
||||
|
|
|
|||
|
|
@ -1519,22 +1519,31 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
'Execute Javascript code with custom buttons';
|
||||
|
||||
@override
|
||||
String get custom_buttons_edit => 'Add custom button';
|
||||
String get custom_buttons_edit => 'Edit custom buttons';
|
||||
|
||||
@override
|
||||
String get custom_buttons_add => 'Add custom button';
|
||||
|
||||
@override
|
||||
String get custom_buttons_delete => 'Delete custom button';
|
||||
|
||||
@override
|
||||
String get custom_buttons_text => 'Button text';
|
||||
|
||||
@override
|
||||
String get custom_buttons_text_req => 'Button text required';
|
||||
|
||||
@override
|
||||
String get custom_buttons_js_code => 'Javascript code';
|
||||
|
||||
@override
|
||||
String get custom_buttons_js_code_req => 'Javascript code required';
|
||||
|
||||
@override
|
||||
String get custom_buttons_js_code_long => 'Javascript code (on long press)';
|
||||
|
||||
@override
|
||||
String get custom_buttons_startup => 'On startup';
|
||||
String get custom_buttons_startup => 'Javascript code (on startup)';
|
||||
|
||||
@override
|
||||
String n_days(Object n) {
|
||||
|
|
|
|||
|
|
@ -1536,22 +1536,31 @@ class AppLocalizationsEs extends AppLocalizations {
|
|||
'Execute Javascript code with custom buttons';
|
||||
|
||||
@override
|
||||
String get custom_buttons_edit => 'Add custom button';
|
||||
String get custom_buttons_edit => 'Edit custom buttons';
|
||||
|
||||
@override
|
||||
String get custom_buttons_add => 'Add custom button';
|
||||
|
||||
@override
|
||||
String get custom_buttons_delete => 'Delete custom button';
|
||||
|
||||
@override
|
||||
String get custom_buttons_text => 'Button text';
|
||||
|
||||
@override
|
||||
String get custom_buttons_text_req => 'Button text required';
|
||||
|
||||
@override
|
||||
String get custom_buttons_js_code => 'Javascript code';
|
||||
|
||||
@override
|
||||
String get custom_buttons_js_code_req => 'Javascript code required';
|
||||
|
||||
@override
|
||||
String get custom_buttons_js_code_long => 'Javascript code (on long press)';
|
||||
|
||||
@override
|
||||
String get custom_buttons_startup => 'On startup';
|
||||
String get custom_buttons_startup => 'Javascript code (on startup)';
|
||||
|
||||
@override
|
||||
String n_days(Object n) {
|
||||
|
|
|
|||
|
|
@ -1537,22 +1537,31 @@ class AppLocalizationsFr extends AppLocalizations {
|
|||
'Execute Javascript code with custom buttons';
|
||||
|
||||
@override
|
||||
String get custom_buttons_edit => 'Add custom button';
|
||||
String get custom_buttons_edit => 'Edit custom buttons';
|
||||
|
||||
@override
|
||||
String get custom_buttons_add => 'Add custom button';
|
||||
|
||||
@override
|
||||
String get custom_buttons_delete => 'Delete custom button';
|
||||
|
||||
@override
|
||||
String get custom_buttons_text => 'Button text';
|
||||
|
||||
@override
|
||||
String get custom_buttons_text_req => 'Button text required';
|
||||
|
||||
@override
|
||||
String get custom_buttons_js_code => 'Javascript code';
|
||||
|
||||
@override
|
||||
String get custom_buttons_js_code_req => 'Javascript code required';
|
||||
|
||||
@override
|
||||
String get custom_buttons_js_code_long => 'Javascript code (on long press)';
|
||||
|
||||
@override
|
||||
String get custom_buttons_startup => 'On startup';
|
||||
String get custom_buttons_startup => 'Javascript code (on startup)';
|
||||
|
||||
@override
|
||||
String n_days(Object n) {
|
||||
|
|
|
|||
|
|
@ -1525,22 +1525,31 @@ class AppLocalizationsId extends AppLocalizations {
|
|||
'Execute Javascript code with custom buttons';
|
||||
|
||||
@override
|
||||
String get custom_buttons_edit => 'Add custom button';
|
||||
String get custom_buttons_edit => 'Edit custom buttons';
|
||||
|
||||
@override
|
||||
String get custom_buttons_add => 'Add custom button';
|
||||
|
||||
@override
|
||||
String get custom_buttons_delete => 'Delete custom button';
|
||||
|
||||
@override
|
||||
String get custom_buttons_text => 'Button text';
|
||||
|
||||
@override
|
||||
String get custom_buttons_text_req => 'Button text required';
|
||||
|
||||
@override
|
||||
String get custom_buttons_js_code => 'Javascript code';
|
||||
|
||||
@override
|
||||
String get custom_buttons_js_code_req => 'Javascript code required';
|
||||
|
||||
@override
|
||||
String get custom_buttons_js_code_long => 'Javascript code (on long press)';
|
||||
|
||||
@override
|
||||
String get custom_buttons_startup => 'On startup';
|
||||
String get custom_buttons_startup => 'Javascript code (on startup)';
|
||||
|
||||
@override
|
||||
String n_days(Object n) {
|
||||
|
|
|
|||
|
|
@ -1534,22 +1534,31 @@ class AppLocalizationsIt extends AppLocalizations {
|
|||
'Execute Javascript code with custom buttons';
|
||||
|
||||
@override
|
||||
String get custom_buttons_edit => 'Add custom button';
|
||||
String get custom_buttons_edit => 'Edit custom buttons';
|
||||
|
||||
@override
|
||||
String get custom_buttons_add => 'Add custom button';
|
||||
|
||||
@override
|
||||
String get custom_buttons_delete => 'Delete custom button';
|
||||
|
||||
@override
|
||||
String get custom_buttons_text => 'Button text';
|
||||
|
||||
@override
|
||||
String get custom_buttons_text_req => 'Button text required';
|
||||
|
||||
@override
|
||||
String get custom_buttons_js_code => 'Javascript code';
|
||||
|
||||
@override
|
||||
String get custom_buttons_js_code_req => 'Javascript code required';
|
||||
|
||||
@override
|
||||
String get custom_buttons_js_code_long => 'Javascript code (on long press)';
|
||||
|
||||
@override
|
||||
String get custom_buttons_startup => 'On startup';
|
||||
String get custom_buttons_startup => 'Javascript code (on startup)';
|
||||
|
||||
@override
|
||||
String n_days(Object n) {
|
||||
|
|
|
|||
|
|
@ -1533,22 +1533,31 @@ class AppLocalizationsPt extends AppLocalizations {
|
|||
'Execute Javascript code with custom buttons';
|
||||
|
||||
@override
|
||||
String get custom_buttons_edit => 'Add custom button';
|
||||
String get custom_buttons_edit => 'Edit custom buttons';
|
||||
|
||||
@override
|
||||
String get custom_buttons_add => 'Add custom button';
|
||||
|
||||
@override
|
||||
String get custom_buttons_delete => 'Delete custom button';
|
||||
|
||||
@override
|
||||
String get custom_buttons_text => 'Button text';
|
||||
|
||||
@override
|
||||
String get custom_buttons_text_req => 'Button text required';
|
||||
|
||||
@override
|
||||
String get custom_buttons_js_code => 'Javascript code';
|
||||
|
||||
@override
|
||||
String get custom_buttons_js_code_req => 'Javascript code required';
|
||||
|
||||
@override
|
||||
String get custom_buttons_js_code_long => 'Javascript code (on long press)';
|
||||
|
||||
@override
|
||||
String get custom_buttons_startup => 'On startup';
|
||||
String get custom_buttons_startup => 'Javascript code (on startup)';
|
||||
|
||||
@override
|
||||
String n_days(Object n) {
|
||||
|
|
|
|||
|
|
@ -1535,22 +1535,31 @@ class AppLocalizationsRu extends AppLocalizations {
|
|||
'Execute Javascript code with custom buttons';
|
||||
|
||||
@override
|
||||
String get custom_buttons_edit => 'Add custom button';
|
||||
String get custom_buttons_edit => 'Edit custom buttons';
|
||||
|
||||
@override
|
||||
String get custom_buttons_add => 'Add custom button';
|
||||
|
||||
@override
|
||||
String get custom_buttons_delete => 'Delete custom button';
|
||||
|
||||
@override
|
||||
String get custom_buttons_text => 'Button text';
|
||||
|
||||
@override
|
||||
String get custom_buttons_text_req => 'Button text required';
|
||||
|
||||
@override
|
||||
String get custom_buttons_js_code => 'Javascript code';
|
||||
|
||||
@override
|
||||
String get custom_buttons_js_code_req => 'Javascript code required';
|
||||
|
||||
@override
|
||||
String get custom_buttons_js_code_long => 'Javascript code (on long press)';
|
||||
|
||||
@override
|
||||
String get custom_buttons_startup => 'On startup';
|
||||
String get custom_buttons_startup => 'Javascript code (on startup)';
|
||||
|
||||
@override
|
||||
String n_days(Object n) {
|
||||
|
|
|
|||
|
|
@ -1519,22 +1519,31 @@ class AppLocalizationsTh extends AppLocalizations {
|
|||
'Execute Javascript code with custom buttons';
|
||||
|
||||
@override
|
||||
String get custom_buttons_edit => 'Add custom button';
|
||||
String get custom_buttons_edit => 'Edit custom buttons';
|
||||
|
||||
@override
|
||||
String get custom_buttons_add => 'Add custom button';
|
||||
|
||||
@override
|
||||
String get custom_buttons_delete => 'Delete custom button';
|
||||
|
||||
@override
|
||||
String get custom_buttons_text => 'Button text';
|
||||
|
||||
@override
|
||||
String get custom_buttons_text_req => 'Button text required';
|
||||
|
||||
@override
|
||||
String get custom_buttons_js_code => 'Javascript code';
|
||||
|
||||
@override
|
||||
String get custom_buttons_js_code_req => 'Javascript code required';
|
||||
|
||||
@override
|
||||
String get custom_buttons_js_code_long => 'Javascript code (on long press)';
|
||||
|
||||
@override
|
||||
String get custom_buttons_startup => 'On startup';
|
||||
String get custom_buttons_startup => 'Javascript code (on startup)';
|
||||
|
||||
@override
|
||||
String n_days(Object n) {
|
||||
|
|
|
|||
|
|
@ -1525,22 +1525,31 @@ class AppLocalizationsTr extends AppLocalizations {
|
|||
'Execute Javascript code with custom buttons';
|
||||
|
||||
@override
|
||||
String get custom_buttons_edit => 'Add custom button';
|
||||
String get custom_buttons_edit => 'Edit custom buttons';
|
||||
|
||||
@override
|
||||
String get custom_buttons_add => 'Add custom button';
|
||||
|
||||
@override
|
||||
String get custom_buttons_delete => 'Delete custom button';
|
||||
|
||||
@override
|
||||
String get custom_buttons_text => 'Button text';
|
||||
|
||||
@override
|
||||
String get custom_buttons_text_req => 'Button text required';
|
||||
|
||||
@override
|
||||
String get custom_buttons_js_code => 'Javascript code';
|
||||
|
||||
@override
|
||||
String get custom_buttons_js_code_req => 'Javascript code required';
|
||||
|
||||
@override
|
||||
String get custom_buttons_js_code_long => 'Javascript code (on long press)';
|
||||
|
||||
@override
|
||||
String get custom_buttons_startup => 'On startup';
|
||||
String get custom_buttons_startup => 'Javascript code (on startup)';
|
||||
|
||||
@override
|
||||
String n_days(Object n) {
|
||||
|
|
|
|||
|
|
@ -1490,22 +1490,31 @@ class AppLocalizationsZh extends AppLocalizations {
|
|||
'Execute Javascript code with custom buttons';
|
||||
|
||||
@override
|
||||
String get custom_buttons_edit => 'Add custom button';
|
||||
String get custom_buttons_edit => 'Edit custom buttons';
|
||||
|
||||
@override
|
||||
String get custom_buttons_add => 'Add custom button';
|
||||
|
||||
@override
|
||||
String get custom_buttons_delete => 'Delete custom button';
|
||||
|
||||
@override
|
||||
String get custom_buttons_text => 'Button text';
|
||||
|
||||
@override
|
||||
String get custom_buttons_text_req => 'Button text required';
|
||||
|
||||
@override
|
||||
String get custom_buttons_js_code => 'Javascript code';
|
||||
|
||||
@override
|
||||
String get custom_buttons_js_code_req => 'Javascript code required';
|
||||
|
||||
@override
|
||||
String get custom_buttons_js_code_long => 'Javascript code (on long press)';
|
||||
|
||||
@override
|
||||
String get custom_buttons_startup => 'On startup';
|
||||
String get custom_buttons_startup => 'Javascript code (on startup)';
|
||||
|
||||
@override
|
||||
String n_days(Object n) {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import 'dart:async';
|
|||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'package:app_links/app_links.dart';
|
||||
import 'package:archive/archive.dart';
|
||||
import 'package:bot_toast/bot_toast.dart';
|
||||
import 'package:desktop_webview_window/desktop_webview_window.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
|
@ -31,9 +32,11 @@ 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';
|
||||
import 'package:media_kit/media_kit.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:window_manager/window_manager.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:flutter/services.dart' show rootBundle;
|
||||
|
||||
late Isar isar;
|
||||
DiscordRPC? discordRpc;
|
||||
|
|
@ -94,6 +97,7 @@ class _MyAppState extends ConsumerState<MyApp> {
|
|||
super.initState();
|
||||
initializeDateFormatting();
|
||||
_initDeepLinks();
|
||||
_setupMpvConfig();
|
||||
unawaited(ref.read(scanLocalLibraryProvider.future));
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
|
|
@ -242,6 +246,38 @@ class _MyAppState extends ConsumerState<MyApp> {
|
|||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Future<void> _setupMpvConfig() async {
|
||||
final provider = StorageProvider();
|
||||
final dir = await provider.getMpvDirectory();
|
||||
final mpvFile = File('${dir!.path}/mpv.conf');
|
||||
final inputFile = File('${dir.path}/input.conf');
|
||||
final filesMissing =
|
||||
!(await mpvFile.exists()) && !(await inputFile.exists());
|
||||
if (filesMissing) {
|
||||
final bytes = await rootBundle.load("assets/mangayomi_mpv.zip");
|
||||
final archive = ZipDecoder().decodeBytes(bytes.buffer.asUint8List());
|
||||
String shadersDir = path.join(dir.path, 'shaders');
|
||||
await Directory(shadersDir).create(recursive: true);
|
||||
String scriptsDir = path.join(dir.path, 'scripts');
|
||||
await Directory(scriptsDir).create(recursive: true);
|
||||
for (final file in archive.files) {
|
||||
if (file.name == "mpv.conf") {
|
||||
await mpvFile.writeAsBytes(file.content);
|
||||
} else if (file.name == "input.conf") {
|
||||
await inputFile.writeAsBytes(file.content);
|
||||
} else if (file.name.startsWith("shaders/") &&
|
||||
file.name.endsWith(".glsl")) {
|
||||
final shaderFile = File('$shadersDir/${file.name.split("/").last}');
|
||||
await shaderFile.writeAsBytes(file.content);
|
||||
} else if (file.name.startsWith("scripts/") &&
|
||||
file.name.endsWith(".js")) {
|
||||
final scriptFile = File('$scriptsDir/${file.name.split("/").last}');
|
||||
await scriptFile.writeAsBytes(file.content);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class AllowScrollBehavior extends MaterialScrollBehavior {
|
||||
|
|
|
|||
|
|
@ -31,6 +31,30 @@ class CustomButton {
|
|||
this.updatedAt = 0,
|
||||
});
|
||||
|
||||
String getButtonStartup(int primaryId) {
|
||||
final isPrimary = primaryId == id ? "true" : "false";
|
||||
return codeStartup
|
||||
?.replaceAll("\$id", "$id")
|
||||
.replaceAll("\$isPrimary", isPrimary) ??
|
||||
"";
|
||||
}
|
||||
|
||||
String getButtonPress(int primaryId) {
|
||||
final isPrimary = primaryId == id ? "true" : "false";
|
||||
return codePress
|
||||
?.replaceAll("\$id", "$id")
|
||||
.replaceAll("\$isPrimary", isPrimary) ??
|
||||
"";
|
||||
}
|
||||
|
||||
String getButtonLongPress(int primaryId) {
|
||||
final isPrimary = primaryId == id ? "true" : "false";
|
||||
return codeLongPress
|
||||
?.replaceAll("\$id", "$id")
|
||||
.replaceAll("\$isPrimary", isPrimary) ??
|
||||
"";
|
||||
}
|
||||
|
||||
CustomButton.fromJson(Map<String, dynamic> json) {
|
||||
id = json['id'];
|
||||
title = json['title'];
|
||||
|
|
@ -53,3 +77,19 @@ class CustomButton {
|
|||
'updatedAt': updatedAt ?? 0,
|
||||
};
|
||||
}
|
||||
|
||||
class ActiveCustomButton {
|
||||
String currentTitle;
|
||||
bool visible;
|
||||
CustomButton button;
|
||||
Function() onPress;
|
||||
Function() onLongPress;
|
||||
|
||||
ActiveCustomButton({
|
||||
required this.currentTitle,
|
||||
required this.visible,
|
||||
required this.button,
|
||||
required this.onPress,
|
||||
required this.onLongPress,
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -375,7 +375,7 @@ class Settings {
|
|||
this.rpcShowReadingWatchingProgress = true,
|
||||
this.rpcShowTitle = true,
|
||||
this.rpcShowCoverImage = true,
|
||||
this.useMpvConfig = false,
|
||||
this.useMpvConfig = true,
|
||||
});
|
||||
|
||||
Settings.fromJson(Map<String, dynamic> json) {
|
||||
|
|
|
|||
|
|
@ -9,11 +9,13 @@ import 'package:file_picker/file_picker.dart';
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_qjs/quickjs/ffi.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart' as riv;
|
||||
import 'package:mangayomi/eval/model/m_bridge.dart';
|
||||
import 'package:mangayomi/main.dart';
|
||||
import 'package:mangayomi/models/chapter.dart';
|
||||
import 'package:mangayomi/models/custom_button.dart';
|
||||
import 'package:mangayomi/models/manga.dart';
|
||||
import 'package:mangayomi/models/video.dart' as vid;
|
||||
import 'package:mangayomi/modules/anime/providers/anime_player_controller_provider.dart';
|
||||
|
|
@ -25,6 +27,7 @@ import 'package:mangayomi/modules/anime/widgets/mobile.dart';
|
|||
import 'package:mangayomi/modules/anime/widgets/subtitle_view.dart';
|
||||
import 'package:mangayomi/modules/anime/widgets/subtitle_setting_widget.dart';
|
||||
import 'package:mangayomi/modules/manga/reader/providers/push_router.dart';
|
||||
import 'package:mangayomi/modules/more/settings/player/providers/custom_buttons_provider.dart';
|
||||
import 'package:mangayomi/modules/more/settings/player/providers/player_state_provider.dart';
|
||||
import 'package:mangayomi/modules/widgets/custom_draggable_tabbar.dart';
|
||||
import 'package:mangayomi/modules/widgets/progress_center.dart';
|
||||
|
|
@ -39,8 +42,11 @@ import 'package:media_kit/media_kit.dart';
|
|||
import 'package:media_kit/generated/libmpv/bindings.dart' as generated;
|
||||
import 'package:media_kit_video/media_kit_video.dart';
|
||||
import 'package:media_kit_video/media_kit_video_controls/src/controls/extensions/duration.dart';
|
||||
import 'package:numberpicker/numberpicker.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:path/path.dart' as path;
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
import 'package:super_sliver_list/super_sliver_list.dart';
|
||||
|
||||
|
|
@ -212,12 +218,12 @@ class _AnimeStreamPageState extends riv.ConsumerState<AnimeStreamPage>
|
|||
generated.mpv_format.MPV_FORMAT_NODE,
|
||||
"user-data/aniyomi/launch_int_picker":
|
||||
generated.mpv_format.MPV_FORMAT_NODE,
|
||||
"user-data/current-anime/intro-length":
|
||||
generated.mpv_format.MPV_FORMAT_INT64,
|
||||
"user-data/mangayomi/chapter_titles":
|
||||
generated.mpv_format.MPV_FORMAT_NODE,
|
||||
"user-data/mangayomi/current_chapter":
|
||||
generated.mpv_format.MPV_FORMAT_INT64,
|
||||
"user-data/mangayomi/selected_shader":
|
||||
generated.mpv_format.MPV_FORMAT_NODE,
|
||||
},
|
||||
eventHandler: _handleMpvEvents,
|
||||
),
|
||||
|
|
@ -253,6 +259,8 @@ class _AnimeStreamPageState extends riv.ConsumerState<AnimeStreamPage>
|
|||
final ValueNotifier<BoxFit> _fit = ValueNotifier(BoxFit.contain);
|
||||
final ValueNotifier<List<(String, int)>> _chapterMarks = ValueNotifier([]);
|
||||
final ValueNotifier<int?> _currentChapterMark = ValueNotifier(null);
|
||||
final ValueNotifier<String> _selectedShader = ValueNotifier("");
|
||||
final ValueNotifier<ActiveCustomButton?> _customButton = ValueNotifier(null);
|
||||
late final ValueNotifier<_AniSkipPhase> _skipPhase = ValueNotifier(
|
||||
_AniSkipPhase.none,
|
||||
);
|
||||
|
|
@ -301,6 +309,9 @@ class _AnimeStreamPageState extends riv.ConsumerState<AnimeStreamPage>
|
|||
generated.mpv_event_id.MPV_EVENT_PROPERTY_CHANGE) {
|
||||
final prop = event.ref.data.cast<generated.mpv_event_property>();
|
||||
final propName = prop.ref.name.cast<Utf8>().toDartString();
|
||||
if (propName.startsWith("user-data/")) {
|
||||
print("DEBUG 00: $propName - ${prop.ref.format}");
|
||||
}
|
||||
if (propName.startsWith("user-data/") &&
|
||||
prop.ref.format == generated.mpv_format.MPV_FORMAT_NODE) {
|
||||
final value = prop.ref.data.cast<generated.mpv_node>();
|
||||
|
|
@ -322,76 +333,269 @@ class _AnimeStreamPageState extends riv.ConsumerState<AnimeStreamPage>
|
|||
String propName,
|
||||
Pointer<generated.mpv_node> value,
|
||||
) async {
|
||||
final nativePlayer = _player.platform as NativePlayer;
|
||||
switch (propName.substring(10)) {
|
||||
case "aniyomi/show_text":
|
||||
if (value.ref.format == generated.mpv_format.MPV_FORMAT_STRING) {
|
||||
final text = value.ref.u.string.cast<Utf8>().toDartString();
|
||||
if (text.isEmpty) break;
|
||||
botToast(text);
|
||||
nativePlayer.setProperty("user-data/aniyomi/show_text", "");
|
||||
}
|
||||
break;
|
||||
case "user-data/aniyomi/toggle_ui":
|
||||
case "aniyomi/toggle_ui":
|
||||
if (value.ref.format == generated.mpv_format.MPV_FORMAT_STRING) {
|
||||
final text = value.ref.u.string.cast<Utf8>().toDartString();
|
||||
if (text.isEmpty) break;
|
||||
switch (text) {
|
||||
// WIP
|
||||
case "show":
|
||||
break;
|
||||
case "hide":
|
||||
break;
|
||||
case "toggle":
|
||||
break;
|
||||
}
|
||||
nativePlayer.setProperty("user-data/aniyomi/toggle_ui", "");
|
||||
}
|
||||
break;
|
||||
case "user-data/aniyomi/show_panel":
|
||||
case "aniyomi/show_panel":
|
||||
if (value.ref.format == generated.mpv_format.MPV_FORMAT_STRING) {
|
||||
final text = value.ref.u.string.cast<Utf8>().toDartString();
|
||||
if (text.isEmpty) break;
|
||||
switch (text) {
|
||||
// WIP
|
||||
case "subtitle_settings":
|
||||
break;
|
||||
case "subtitle_delay":
|
||||
break;
|
||||
case "audio_delay":
|
||||
break;
|
||||
case "video_filters":
|
||||
break;
|
||||
}
|
||||
nativePlayer.setProperty("user-data/aniyomi/show_panel", "");
|
||||
}
|
||||
break;
|
||||
case "user-data/aniyomi/software_keyboard":
|
||||
case "aniyomi/software_keyboard":
|
||||
if (value.ref.format == generated.mpv_format.MPV_FORMAT_STRING) {
|
||||
final text = value.ref.u.string.cast<Utf8>().toDartString();
|
||||
if (text.isEmpty) break;
|
||||
switch (text) {
|
||||
// WIP
|
||||
case "show":
|
||||
break;
|
||||
case "hide":
|
||||
break;
|
||||
case "toggle":
|
||||
break;
|
||||
}
|
||||
nativePlayer.setProperty("user-data/aniyomi/software_keyboard", "");
|
||||
}
|
||||
break;
|
||||
case "user-data/aniyomi/set_button_title":
|
||||
case "aniyomi/set_button_title":
|
||||
if (value.ref.format == generated.mpv_format.MPV_FORMAT_STRING) {
|
||||
final text = value.ref.u.string.cast<Utf8>().toDartString();
|
||||
print("DEBUG SET BUTTON TITLE: $text");
|
||||
if (text.isEmpty) break;
|
||||
final temp = _customButton.value;
|
||||
if (temp == null) break;
|
||||
_customButton.value = temp..currentTitle = text;
|
||||
nativePlayer.setProperty("user-data/aniyomi/set_button_title", "");
|
||||
}
|
||||
break;
|
||||
case "user-data/aniyomi/reset_button_title":
|
||||
case "aniyomi/reset_button_title":
|
||||
if (value.ref.format == generated.mpv_format.MPV_FORMAT_STRING) {
|
||||
final text = value.ref.u.string.cast<Utf8>().toDartString();
|
||||
if (text.isEmpty) break;
|
||||
final temp = _customButton.value;
|
||||
if (temp == null) break;
|
||||
_customButton.value = temp..currentTitle = temp.button.title ?? "";
|
||||
nativePlayer.setProperty("user-data/aniyomi/reset_button_title", "");
|
||||
}
|
||||
break;
|
||||
case "user-data/aniyomi/toggle_button":
|
||||
case "aniyomi/toggle_button":
|
||||
if (value.ref.format == generated.mpv_format.MPV_FORMAT_STRING) {
|
||||
final text = value.ref.u.string.cast<Utf8>().toDartString();
|
||||
if (text.isEmpty) break;
|
||||
final temp = _customButton.value;
|
||||
if (temp == null) break;
|
||||
switch (text) {
|
||||
case "show":
|
||||
_customButton.value = temp..visible = true;
|
||||
break;
|
||||
case "hide":
|
||||
_customButton.value = temp..visible = false;
|
||||
break;
|
||||
case "toggle":
|
||||
_customButton.value = temp..visible = !temp.visible;
|
||||
break;
|
||||
}
|
||||
nativePlayer.setProperty("user-data/aniyomi/toggle_button", "");
|
||||
}
|
||||
break;
|
||||
case "user-data/aniyomi/switch_episode":
|
||||
case "aniyomi/switch_episode":
|
||||
if (value.ref.format == generated.mpv_format.MPV_FORMAT_STRING) {
|
||||
final text = value.ref.u.string.cast<Utf8>().toDartString();
|
||||
if (text.isEmpty) break;
|
||||
switch (text) {
|
||||
case "n":
|
||||
pushToNewEpisode(context, _streamController.getNextEpisode());
|
||||
break;
|
||||
case "p":
|
||||
pushToNewEpisode(context, _streamController.getPrevEpisode());
|
||||
break;
|
||||
}
|
||||
nativePlayer.setProperty("user-data/aniyomi/switch_episode", "");
|
||||
}
|
||||
break;
|
||||
case "user-data/aniyomi/pause":
|
||||
case "aniyomi/pause":
|
||||
if (value.ref.format == generated.mpv_format.MPV_FORMAT_STRING) {
|
||||
final text = value.ref.u.string.cast<Utf8>().toDartString();
|
||||
if (text.isEmpty) break;
|
||||
switch (text) {
|
||||
case "pause":
|
||||
await _player.pause();
|
||||
break;
|
||||
case "unpause":
|
||||
await _player.play();
|
||||
break;
|
||||
case "pauseunpause":
|
||||
await _player.playOrPause();
|
||||
break;
|
||||
}
|
||||
nativePlayer.setProperty("user-data/aniyomi/pause", "");
|
||||
}
|
||||
break;
|
||||
case "user-data/aniyomi/seek_by":
|
||||
case "aniyomi/seek_by":
|
||||
if (value.ref.format == generated.mpv_format.MPV_FORMAT_STRING) {
|
||||
final text = value.ref.u.string.cast<Utf8>().toDartString();
|
||||
final tt = await nativePlayer.getProperty(
|
||||
"user-data/current-anime/intro-length",
|
||||
);
|
||||
if (text.isEmpty) break;
|
||||
final data = int.parse(text.replaceAll("\"", ""));
|
||||
final pos = _currentPosition.value.inSeconds + data;
|
||||
_tempPosition.value = Duration(seconds: pos);
|
||||
await _player.seek(Duration(seconds: pos));
|
||||
_tempPosition.value = null;
|
||||
nativePlayer.setProperty("user-data/aniyomi/seek_by", "");
|
||||
}
|
||||
break;
|
||||
case "user-data/aniyomi/seek_to":
|
||||
case "aniyomi/seek_to":
|
||||
if (value.ref.format == generated.mpv_format.MPV_FORMAT_STRING) {
|
||||
final text = value.ref.u.string.cast<Utf8>().toDartString();
|
||||
if (text.isEmpty) break;
|
||||
final data = int.parse(text.replaceAll("\"", ""));
|
||||
_tempPosition.value = Duration(seconds: data);
|
||||
await _player.seek(Duration(seconds: data));
|
||||
_tempPosition.value = null;
|
||||
nativePlayer.setProperty("user-data/aniyomi/seek_to", "");
|
||||
}
|
||||
break;
|
||||
case "user-data/aniyomi/seek_by_with_text":
|
||||
case "aniyomi/seek_by_with_text":
|
||||
if (value.ref.format == generated.mpv_format.MPV_FORMAT_STRING) {
|
||||
final text = value.ref.u.string.cast<Utf8>().toDartString();
|
||||
if (text.isEmpty) break;
|
||||
final data = text.split("|");
|
||||
final pos =
|
||||
_currentPosition.value.inSeconds +
|
||||
int.parse(data[0].replaceAll("\"", ""));
|
||||
_tempPosition.value = Duration(seconds: pos);
|
||||
await _player.seek(Duration(seconds: pos));
|
||||
_tempPosition.value = null;
|
||||
(_player.platform as NativePlayer).command(["show-text", data[1]]);
|
||||
nativePlayer.setProperty("user-data/aniyomi/seek_by_with_text", "");
|
||||
}
|
||||
break;
|
||||
case "user-data/aniyomi/seek_to_with_text":
|
||||
case "aniyomi/seek_to_with_text":
|
||||
if (value.ref.format == generated.mpv_format.MPV_FORMAT_STRING) {
|
||||
final text = value.ref.u.string.cast<Utf8>().toDartString();
|
||||
if (text.isEmpty) break;
|
||||
final data = text.split("|");
|
||||
final pos = int.parse(data[0].replaceAll("\"", ""));
|
||||
_tempPosition.value = Duration(seconds: pos);
|
||||
await _player.seek(Duration(seconds: pos));
|
||||
_tempPosition.value = null;
|
||||
(_player.platform as NativePlayer).command(["show-text", data[1]]);
|
||||
nativePlayer.setProperty("user-data/aniyomi/seek_to_with_text", "");
|
||||
}
|
||||
break;
|
||||
case "user-data/aniyomi/launch_int_picker":
|
||||
case "aniyomi/launch_int_picker":
|
||||
if (value.ref.format == generated.mpv_format.MPV_FORMAT_STRING) {
|
||||
final text = value.ref.u.string.cast<Utf8>().toDartString();
|
||||
if (text.isEmpty) break;
|
||||
final data = text.split("|");
|
||||
final start = int.parse(data[2]);
|
||||
final stop = int.parse(data[3]);
|
||||
final step = int.parse(data[4]);
|
||||
int currentValue = start;
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
title: Text(data[0]),
|
||||
content: StatefulBuilder(
|
||||
builder: (context, setState) => SizedBox(
|
||||
height: 200,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
NumberPicker(
|
||||
value: currentValue,
|
||||
minValue: start,
|
||||
maxValue: stop,
|
||||
step: step,
|
||||
haptics: true,
|
||||
textMapper: (numberText) =>
|
||||
data[1].replaceAll("%d", numberText),
|
||||
onChanged: (value) =>
|
||||
setState(() => currentValue = value),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: Text(
|
||||
context.l10n.cancel,
|
||||
style: TextStyle(color: context.primaryColor),
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
final namePtr = data[5].toNativeUtf8();
|
||||
final valuePtr = calloc<Int64>(1)
|
||||
..value = currentValue;
|
||||
nativePlayer.mpv.mpv_set_property(
|
||||
nativePlayer.ctx,
|
||||
namePtr.cast(),
|
||||
generated.mpv_format.MPV_FORMAT_INT64,
|
||||
valuePtr.cast(),
|
||||
);
|
||||
malloc.free(namePtr);
|
||||
malloc.free(valuePtr);
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: Text(
|
||||
context.l10n.ok,
|
||||
style: TextStyle(color: context.primaryColor),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
nativePlayer.setProperty("user-data/aniyomi/launch_int_picker", "");
|
||||
}
|
||||
break;
|
||||
case "mangayomi/chapter_titles":
|
||||
|
|
@ -410,6 +614,12 @@ class _AnimeStreamPageState extends riv.ConsumerState<AnimeStreamPage>
|
|||
.toList();
|
||||
}
|
||||
break;
|
||||
case "mangayomi/selected_shader":
|
||||
if (value.ref.format == generated.mpv_format.MPV_FORMAT_STRING) {
|
||||
final text = value.ref.u.string.cast<Utf8>().toDartString();
|
||||
_selectedShader.value = text;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -421,6 +631,57 @@ class _AnimeStreamPageState extends riv.ConsumerState<AnimeStreamPage>
|
|||
}
|
||||
}
|
||||
|
||||
Future<void> _initCustomButton() async {
|
||||
if (!useMpvConfig) return;
|
||||
final customButtons = await ref.read(getCustomButtonsStreamProvider.future);
|
||||
if (customButtons.isEmpty) return;
|
||||
final primaryButton =
|
||||
customButtons.firstWhereOrNull((e) => e.isFavourite ?? false) ??
|
||||
customButtons.first;
|
||||
var status = await Permission.storage.status;
|
||||
if (!status.isGranted) {
|
||||
await Permission.storage.request();
|
||||
}
|
||||
final provider = StorageProvider();
|
||||
final dir = await provider.getMpvDirectory();
|
||||
String scriptsDir = path.join(dir!.path, 'scripts');
|
||||
final mpvFile = File('$scriptsDir/init_custom_buttons.js');
|
||||
final content = StringBuffer();
|
||||
content.write("var aniyomi = require('./init_aniyomi_functions');");
|
||||
for (final button in customButtons) {
|
||||
content.write(
|
||||
"""
|
||||
${button.getButtonStartup(primaryButton.id!).trim()}
|
||||
function button${button.id}() {
|
||||
${button.getButtonPress(primaryButton.id!).trim()}
|
||||
}
|
||||
mp.register_script_message('call_button_${button.id}', button${button.id})
|
||||
function button${button.id}long() {
|
||||
${button.getButtonLongPress(primaryButton.id!).trim()}
|
||||
}
|
||||
mp.register_script_message('call_button_${button.id}_long', button${button.id}long)""",
|
||||
);
|
||||
}
|
||||
await mpvFile.writeAsString(content.toString());
|
||||
await (_player.platform as NativePlayer).command([
|
||||
"load-script",
|
||||
mpvFile.path,
|
||||
]);
|
||||
_customButton.value = ActiveCustomButton(
|
||||
currentTitle: primaryButton.title!,
|
||||
visible: true,
|
||||
button: primaryButton,
|
||||
onPress: () => (_player.platform as NativePlayer).command([
|
||||
"script-message",
|
||||
"call_button_${primaryButton.id}",
|
||||
]),
|
||||
onLongPress: () => (_player.platform as NativePlayer).command([
|
||||
"script-message",
|
||||
"call_button_${primaryButton.id}_long",
|
||||
]),
|
||||
);
|
||||
}
|
||||
|
||||
void pushToNewEpisode(BuildContext context, Chapter episode) {
|
||||
widget.desktopFullScreenPlayer.call(ref.read(fullscreenProvider));
|
||||
if (context.mounted) {
|
||||
|
|
@ -526,6 +787,22 @@ class _AnimeStreamPageState extends riv.ConsumerState<AnimeStreamPage>
|
|||
_setPlaybackSpeed(ref.read(defaultPlayBackSpeedStateProvider));
|
||||
if (ref.read(enableAniSkipStateProvider)) _initAniSkip();
|
||||
});
|
||||
final defaultSkipIntroLength = ref.read(
|
||||
defaultSkipIntroLengthStateProvider,
|
||||
);
|
||||
(_player.platform as NativePlayer).setProperty(
|
||||
"user-data/current-anime/intro-length",
|
||||
"$defaultSkipIntroLength",
|
||||
);
|
||||
(_player.platform as NativePlayer).command([
|
||||
"script-binding",
|
||||
"stats/display-stats-toggle",
|
||||
]);
|
||||
(_player.platform as NativePlayer).command([
|
||||
"script-binding",
|
||||
"stats/display-page-1",
|
||||
]);
|
||||
_initCustomButton();
|
||||
discordRpc?.showChapterDetails(ref, widget.episode);
|
||||
_currentPosition.addListener(_updateRpcTimestamp);
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
|
|
@ -1006,27 +1283,39 @@ class _AnimeStreamPageState extends riv.ConsumerState<AnimeStreamPage>
|
|||
padding: const EdgeInsets.symmetric(vertical: 5),
|
||||
child: SizedBox(
|
||||
height: 35,
|
||||
child: ElevatedButton(
|
||||
onPressed: () async {
|
||||
_tempPosition.value = Duration(
|
||||
seconds:
|
||||
defaultSkipIntroLength + _currentPosition.value.inSeconds,
|
||||
);
|
||||
await _player.seek(
|
||||
Duration(
|
||||
seconds:
|
||||
_currentPosition.value.inSeconds + defaultSkipIntroLength,
|
||||
),
|
||||
);
|
||||
_tempPosition.value = null;
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Text(
|
||||
"+$defaultSkipIntroLength",
|
||||
style: const TextStyle(fontWeight: FontWeight.w100),
|
||||
),
|
||||
),
|
||||
child: ValueListenableBuilder(
|
||||
valueListenable: _customButton,
|
||||
builder: (context, value, child) => (value?.visible ?? true)
|
||||
? ElevatedButton(
|
||||
onPressed:
|
||||
value?.onPress ??
|
||||
() async {
|
||||
_tempPosition.value = Duration(
|
||||
seconds:
|
||||
defaultSkipIntroLength +
|
||||
_currentPosition.value.inSeconds,
|
||||
);
|
||||
await _player.seek(
|
||||
Duration(
|
||||
seconds:
|
||||
_currentPosition.value.inSeconds +
|
||||
defaultSkipIntroLength,
|
||||
),
|
||||
);
|
||||
_tempPosition.value = null;
|
||||
},
|
||||
onLongPress: value?.onLongPress,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Text(
|
||||
value != null
|
||||
? value.currentTitle
|
||||
: "+$defaultSkipIntroLength",
|
||||
style: const TextStyle(fontWeight: FontWeight.w100),
|
||||
),
|
||||
),
|
||||
)
|
||||
: Container(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
@ -1061,6 +1350,7 @@ class _AnimeStreamPageState extends riv.ConsumerState<AnimeStreamPage>
|
|||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
@ -1276,9 +1566,16 @@ class _AnimeStreamPageState extends riv.ConsumerState<AnimeStreamPage>
|
|||
.map(
|
||||
(mode) => PopupMenuItem<String>(
|
||||
value: mode.$1,
|
||||
child: Text(mode.$1),
|
||||
child: Text(
|
||||
mode.$1,
|
||||
style: TextStyle(
|
||||
fontWeight: _selectedShader.value == mode.$1
|
||||
? FontWeight.w900
|
||||
: FontWeight.normal,
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
(_player.platform as dynamic).command([
|
||||
(_player.platform as NativePlayer).command([
|
||||
"script-message",
|
||||
mode.$2,
|
||||
]);
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import 'package:mangayomi/modules/anime/providers/anime_player_controller_provid
|
|||
import 'package:mangayomi/modules/anime/widgets/custom_seekbar.dart';
|
||||
import 'package:mangayomi/modules/anime/widgets/subtitle_view.dart';
|
||||
import 'package:mangayomi/modules/more/settings/player/providers/player_state_provider.dart';
|
||||
import 'package:media_kit/media_kit.dart';
|
||||
import 'package:media_kit_video/media_kit_video.dart';
|
||||
import 'package:media_kit_video/media_kit_video_controls/src/controls/extensions/duration.dart';
|
||||
import 'package:window_manager/window_manager.dart';
|
||||
|
|
@ -218,43 +219,43 @@ class _DesktopControllerWidgetState
|
|||
await _changeFullScreen(ref, desktopFullScreenPlayer, value: false);
|
||||
},
|
||||
const SingleActivator(LogicalKeyboardKey.digit0, control: true): () {
|
||||
(widget.videoController.player.platform as dynamic).command([
|
||||
(widget.videoController.player.platform as NativePlayer).command([
|
||||
"script-message",
|
||||
"clear_anime",
|
||||
]);
|
||||
},
|
||||
const SingleActivator(LogicalKeyboardKey.digit1, control: true): () {
|
||||
(widget.videoController.player.platform as dynamic).command([
|
||||
(widget.videoController.player.platform as NativePlayer).command([
|
||||
"script-message",
|
||||
"set_anime_a",
|
||||
]);
|
||||
},
|
||||
const SingleActivator(LogicalKeyboardKey.digit2, control: true): () {
|
||||
(widget.videoController.player.platform as dynamic).command([
|
||||
(widget.videoController.player.platform as NativePlayer).command([
|
||||
"script-message",
|
||||
"set_anime_b",
|
||||
]);
|
||||
},
|
||||
const SingleActivator(LogicalKeyboardKey.digit3, control: true): () {
|
||||
(widget.videoController.player.platform as dynamic).command([
|
||||
(widget.videoController.player.platform as NativePlayer).command([
|
||||
"script-message",
|
||||
"set_anime_c",
|
||||
]);
|
||||
},
|
||||
const SingleActivator(LogicalKeyboardKey.digit4, control: true): () {
|
||||
(widget.videoController.player.platform as dynamic).command([
|
||||
(widget.videoController.player.platform as NativePlayer).command([
|
||||
"script-message",
|
||||
"set_anime_aa",
|
||||
]);
|
||||
},
|
||||
const SingleActivator(LogicalKeyboardKey.digit5, control: true): () {
|
||||
(widget.videoController.player.platform as dynamic).command([
|
||||
(widget.videoController.player.platform as NativePlayer).command([
|
||||
"script-message",
|
||||
"set_anime_bb",
|
||||
]);
|
||||
},
|
||||
const SingleActivator(LogicalKeyboardKey.digit6, control: true): () {
|
||||
(widget.videoController.player.platform as dynamic).command([
|
||||
(widget.videoController.player.platform as NativePlayer).command([
|
||||
"script-message",
|
||||
"set_anime_ca",
|
||||
]);
|
||||
|
|
|
|||
|
|
@ -1,9 +1,12 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:isar/isar.dart';
|
||||
import 'package:mangayomi/main.dart';
|
||||
import 'package:mangayomi/models/custom_button.dart';
|
||||
import 'package:mangayomi/modules/more/settings/player/providers/custom_buttons_provider.dart';
|
||||
import 'package:mangayomi/modules/widgets/progress_center.dart';
|
||||
import 'package:mangayomi/providers/l10n_providers.dart';
|
||||
import 'package:mangayomi/utils/extensions/build_context_extensions.dart';
|
||||
|
||||
class CustomButtonScreen extends ConsumerStatefulWidget {
|
||||
const CustomButtonScreen({super.key});
|
||||
|
|
@ -13,7 +16,6 @@ class CustomButtonScreen extends ConsumerStatefulWidget {
|
|||
}
|
||||
|
||||
class _CustomButtonScreenState extends ConsumerState<CustomButtonScreen> {
|
||||
List<CustomButton> _entries = [];
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = context.l10n;
|
||||
|
|
@ -23,7 +25,6 @@ class _CustomButtonScreenState extends ConsumerState<CustomButtonScreen> {
|
|||
body: customButtons.when(
|
||||
data: (data) {
|
||||
if (data.isEmpty) {
|
||||
_entries = [];
|
||||
return Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
|
|
@ -34,71 +35,107 @@ class _CustomButtonScreenState extends ConsumerState<CustomButtonScreen> {
|
|||
),
|
||||
);
|
||||
}
|
||||
data.sort((a, b) => (a.pos ?? 0).compareTo(b.pos ?? 0));
|
||||
_entries = data;
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 15),
|
||||
child: ReorderableListView.builder(
|
||||
buildDefaultDragHandles: false,
|
||||
itemCount: _entries.length,
|
||||
itemCount: data.length,
|
||||
itemBuilder: (context, index) {
|
||||
final customButton = _entries[index];
|
||||
return Row(
|
||||
key: Key('custom_btn_${customButton.id}'),
|
||||
final customButton = data[index];
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
key: Key("custom_btn_col_${customButton.id}"),
|
||||
children: [
|
||||
ReorderableDragStartListener(
|
||||
index: index,
|
||||
child: const Icon(Icons.drag_handle),
|
||||
Row(
|
||||
key: Key("custom_btn_row_${customButton.id}"),
|
||||
children: [
|
||||
ReorderableDragStartListener(
|
||||
index: index,
|
||||
child: const Icon(Icons.drag_handle),
|
||||
),
|
||||
Expanded(
|
||||
child: Row(
|
||||
key: Key("custom_btn_row1_${customButton.id}"),
|
||||
crossAxisAlignment: CrossAxisAlignment.baseline,
|
||||
textBaseline: TextBaseline.alphabetic,
|
||||
children: [
|
||||
Expanded(
|
||||
child: ListTile(
|
||||
key: Key(
|
||||
"custom_btn_tile_${customButton.id}",
|
||||
),
|
||||
dense: true,
|
||||
title: Text(
|
||||
customButton.title!,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () async {
|
||||
for (final button in data) {
|
||||
button.isFavourite =
|
||||
button.id == customButton.id;
|
||||
}
|
||||
await isar.writeTxn(
|
||||
() async =>
|
||||
await isar.customButtons.putAll(data),
|
||||
);
|
||||
},
|
||||
icon: Icon(
|
||||
(customButton.isFavourite ?? false)
|
||||
? Icons.star
|
||||
: Icons.star_border,
|
||||
color: context.primaryColor,
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () async {
|
||||
await _showEditForm(customButton);
|
||||
},
|
||||
icon: Icon(Icons.mode_edit_outlined),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () async {
|
||||
await _showDeleteButton(customButton);
|
||||
},
|
||||
icon: Icon(Icons.delete_outline),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Expanded(
|
||||
child: Row(
|
||||
children: [
|
||||
IconButton(
|
||||
onPressed: () {},
|
||||
icon: Icon(
|
||||
(customButton.isFavourite ?? false)
|
||||
? Icons.star
|
||||
: Icons.star_border,
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () {},
|
||||
icon: Icon(Icons.mode_edit_outlined),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () {},
|
||||
icon: Icon(Icons.delete_outline),
|
||||
),
|
||||
],
|
||||
),
|
||||
Text(
|
||||
customButton.codePress ?? "",
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
onReorder: (oldIndex, newIndex) {
|
||||
/*if (oldIndex < newIndex) {
|
||||
final draggedItem = navigationOrder[oldIndex];
|
||||
onReorder: (oldIndex, newIndex) async {
|
||||
if (oldIndex < newIndex) {
|
||||
final draggedItemPos = data[oldIndex].pos;
|
||||
for (var i = oldIndex; i < newIndex - 1; i++) {
|
||||
navigationOrder[i] = navigationOrder[i + 1];
|
||||
data[i].pos = data[i + 1].pos;
|
||||
}
|
||||
navigationOrder[newIndex - 1] = draggedItem;
|
||||
data[newIndex - 1].pos = draggedItemPos;
|
||||
} else {
|
||||
final draggedItem = navigationOrder[oldIndex];
|
||||
final draggedItemPos = data[oldIndex].pos;
|
||||
for (var i = oldIndex; i > newIndex; i--) {
|
||||
navigationOrder[i] = navigationOrder[i - 1];
|
||||
data[i].pos = data[i - 1].pos;
|
||||
}
|
||||
navigationOrder[newIndex] = draggedItem;
|
||||
data[newIndex].pos = draggedItemPos;
|
||||
}
|
||||
ref
|
||||
.read(navigationOrderStateProvider.notifier)
|
||||
.set(navigationOrder);*/
|
||||
await isar.writeTxn(
|
||||
() async => await isar.customButtons.putAll(data),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
error: (Object error, StackTrace stackTrace) {
|
||||
_entries = [];
|
||||
return Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
|
|
@ -114,89 +151,8 @@ class _CustomButtonScreenState extends ConsumerState<CustomButtonScreen> {
|
|||
},
|
||||
),
|
||||
floatingActionButton: FloatingActionButton.extended(
|
||||
onPressed: () {
|
||||
bool isExist = false;
|
||||
final controller = TextEditingController();
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return SizedBox(
|
||||
child: StatefulBuilder(
|
||||
builder: (context, setState) {
|
||||
return AlertDialog(
|
||||
title: Text(l10n.add_category),
|
||||
content: CustomTextFormField(
|
||||
controller: controller,
|
||||
entries: _entries,
|
||||
context: context,
|
||||
exist: (value) {
|
||||
setState(() {
|
||||
isExist = value;
|
||||
});
|
||||
},
|
||||
isExist: isExist,
|
||||
val: (val) {},
|
||||
),
|
||||
actions: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: Text(l10n.cancel),
|
||||
),
|
||||
const SizedBox(width: 15),
|
||||
TextButton(
|
||||
onPressed: controller.text.isEmpty || isExist
|
||||
? null
|
||||
: () async {
|
||||
/*final category = Category(
|
||||
forItemType: widget.itemType,
|
||||
name: controller.text,
|
||||
updatedAt: DateTime.now()
|
||||
.millisecondsSinceEpoch,
|
||||
);
|
||||
isar.writeTxnSync(() {
|
||||
isar.categorys.putSync(
|
||||
category..pos = category.id,
|
||||
);
|
||||
final categories = isar.categorys
|
||||
.filter()
|
||||
.posIsNull()
|
||||
.findAllSync();
|
||||
for (var category in categories) {
|
||||
isar.categorys.putSync(
|
||||
category..pos = category.id,
|
||||
);
|
||||
}
|
||||
});*/
|
||||
|
||||
if (context.mounted) {
|
||||
Navigator.pop(context);
|
||||
}
|
||||
},
|
||||
child: Text(
|
||||
l10n.add,
|
||||
style: TextStyle(
|
||||
color: controller.text.isEmpty || isExist
|
||||
? Theme.of(
|
||||
context,
|
||||
).primaryColor.withValues(alpha: 0.2)
|
||||
: null,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
onPressed: () async {
|
||||
await _showEditForm(null);
|
||||
},
|
||||
label: Row(
|
||||
children: [
|
||||
|
|
@ -208,77 +164,250 @@ class _CustomButtonScreenState extends ConsumerState<CustomButtonScreen> {
|
|||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _showEditForm(CustomButton? customButton) async {
|
||||
bool isTitleMissing = customButton == null;
|
||||
bool isCodePressMissing = customButton == null;
|
||||
final titleController = TextEditingController(
|
||||
text: customButton?.title ?? "",
|
||||
);
|
||||
final codePressController = TextEditingController(
|
||||
text: customButton?.codePress ?? "",
|
||||
);
|
||||
final codeLongPressController = TextEditingController(
|
||||
text: customButton?.codeLongPress ?? "",
|
||||
);
|
||||
final codeStartupController = TextEditingController(
|
||||
text: customButton?.codeStartup ?? "",
|
||||
);
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return SizedBox(
|
||||
child: StatefulBuilder(
|
||||
builder: (context, setState) {
|
||||
return AlertDialog(
|
||||
title: Text(
|
||||
"${context.l10n.custom_buttons_add}${customButton != null ? " (ID: ${customButton.id})" : ""}",
|
||||
),
|
||||
scrollable: true,
|
||||
content: Column(
|
||||
children: [
|
||||
const SizedBox(height: 20),
|
||||
CustomTextFormField(
|
||||
name: context.l10n.custom_buttons_text,
|
||||
helperText: context.l10n.custom_buttons_text_req,
|
||||
allowEnterNewLine: false,
|
||||
controller: titleController,
|
||||
context: context,
|
||||
missing: (value) {
|
||||
setState(() {
|
||||
isTitleMissing = value;
|
||||
});
|
||||
},
|
||||
isMissing: isTitleMissing,
|
||||
val: (val) {},
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
CustomTextFormField(
|
||||
name: context.l10n.custom_buttons_js_code,
|
||||
helperText: context.l10n.custom_buttons_js_code_req,
|
||||
minLines: 4,
|
||||
controller: codePressController,
|
||||
context: context,
|
||||
missing: (value) {
|
||||
setState(() {
|
||||
isCodePressMissing = value;
|
||||
});
|
||||
},
|
||||
isMissing: isCodePressMissing,
|
||||
val: (val) {},
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
CustomTextFormField(
|
||||
name: context.l10n.custom_buttons_js_code_long,
|
||||
minLines: 4,
|
||||
controller: codeLongPressController,
|
||||
context: context,
|
||||
missing: (value) {},
|
||||
isMissing: false,
|
||||
val: (val) {},
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
CustomTextFormField(
|
||||
name: context.l10n.custom_buttons_startup,
|
||||
minLines: 4,
|
||||
controller: codeStartupController,
|
||||
context: context,
|
||||
missing: (value) {},
|
||||
isMissing: false,
|
||||
val: (val) {},
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: Text(context.l10n.cancel),
|
||||
),
|
||||
const SizedBox(width: 15),
|
||||
TextButton(
|
||||
onPressed: isTitleMissing || isCodePressMissing
|
||||
? null
|
||||
: () async {
|
||||
final temp = await isar.customButtons
|
||||
.filter()
|
||||
.idEqualTo(customButton?.id)
|
||||
.findFirst();
|
||||
final button =
|
||||
temp ??
|
||||
CustomButton(
|
||||
title: "",
|
||||
codePress: "",
|
||||
codeLongPress: "",
|
||||
codeStartup: "",
|
||||
pos: await isar.customButtons.count(),
|
||||
);
|
||||
await isar.writeTxn(() async {
|
||||
await isar.customButtons.put(
|
||||
button
|
||||
..title = titleController.text
|
||||
..codePress = codePressController.text
|
||||
..codeLongPress =
|
||||
codeLongPressController.text
|
||||
..codeStartup =
|
||||
codeStartupController.text,
|
||||
);
|
||||
});
|
||||
if (context.mounted) {
|
||||
Navigator.pop(context);
|
||||
}
|
||||
},
|
||||
child: Text(
|
||||
customButton == null
|
||||
? context.l10n.add
|
||||
: context.l10n.edit,
|
||||
style: TextStyle(
|
||||
color: isTitleMissing || isCodePressMissing
|
||||
? Theme.of(
|
||||
context,
|
||||
).primaryColor.withValues(alpha: 0.2)
|
||||
: null,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _showDeleteButton(CustomButton customButton) async {
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
content: Text(context.l10n.custom_buttons_delete),
|
||||
actions: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text(context.l10n.cancel),
|
||||
),
|
||||
const SizedBox(width: 15),
|
||||
ElevatedButton(
|
||||
onPressed: () async {
|
||||
await isar.writeTxn(
|
||||
() async =>
|
||||
await isar.customButtons.delete(customButton.id!),
|
||||
);
|
||||
if (context.mounted) {
|
||||
Navigator.pop(context, "ok");
|
||||
}
|
||||
},
|
||||
child: Text(context.l10n.ok),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class CustomTextFormField extends StatelessWidget {
|
||||
final TextEditingController controller;
|
||||
final List<CustomButton> entries;
|
||||
final BuildContext context;
|
||||
final Function(bool) exist;
|
||||
final bool isExist;
|
||||
final Function(bool) missing;
|
||||
final bool isMissing;
|
||||
final String name;
|
||||
final String helperText;
|
||||
final int minLines;
|
||||
final bool allowEnterNewLine;
|
||||
final Function(String) val;
|
||||
const CustomTextFormField({
|
||||
super.key,
|
||||
required this.controller,
|
||||
required this.entries,
|
||||
required this.context,
|
||||
required this.exist,
|
||||
required this.isExist,
|
||||
required this.missing,
|
||||
required this.isMissing,
|
||||
this.name = "",
|
||||
this.helperText = "",
|
||||
this.minLines = 1,
|
||||
this.allowEnterNewLine = true,
|
||||
required this.val,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = l10nLocalizations(context);
|
||||
return TextFormField(
|
||||
autofocus: true,
|
||||
minLines: minLines,
|
||||
maxLines: null,
|
||||
controller: controller,
|
||||
keyboardType: TextInputType.text,
|
||||
keyboardType: allowEnterNewLine
|
||||
? TextInputType.multiline
|
||||
: TextInputType.text,
|
||||
onChanged: (value) {
|
||||
if (name != controller.text) {
|
||||
exist(
|
||||
entries
|
||||
.where((element) => element.title == controller.text)
|
||||
.toList()
|
||||
.isNotEmpty,
|
||||
);
|
||||
}
|
||||
missing(controller.text.isEmpty);
|
||||
val(value);
|
||||
},
|
||||
onFieldSubmitted: (s) {},
|
||||
decoration: InputDecoration(
|
||||
helperText: isExist == true
|
||||
? l10n!.add_category_error_exist
|
||||
: l10n!.category_name_required,
|
||||
helperStyle: TextStyle(color: isExist == true ? Colors.red : null),
|
||||
helperText: helperText,
|
||||
helperStyle: TextStyle(color: isMissing ? Colors.red : null),
|
||||
isDense: true,
|
||||
label: Text(
|
||||
l10n.name,
|
||||
style: TextStyle(color: isExist == true ? Colors.red : null),
|
||||
name,
|
||||
style: TextStyle(color: isMissing ? Colors.red : null),
|
||||
),
|
||||
filled: true,
|
||||
fillColor: Colors.transparent,
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: isExist == true
|
||||
? Colors.red
|
||||
: Theme.of(context).primaryColor,
|
||||
color: isMissing ? Colors.red : Theme.of(context).primaryColor,
|
||||
),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: isExist == true
|
||||
? Colors.red
|
||||
: Theme.of(context).primaryColor,
|
||||
color: isMissing ? Colors.red : Theme.of(context).primaryColor,
|
||||
),
|
||||
),
|
||||
border: OutlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: isExist == true
|
||||
? Colors.red
|
||||
: Theme.of(context).primaryColor,
|
||||
color: isMissing ? Colors.red : Theme.of(context).primaryColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -3,9 +3,9 @@ import 'dart:io';
|
|||
|
||||
import 'package:archive/archive.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart' show rootBundle;
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:mangayomi/modules/more/settings/player/providers/player_state_provider.dart';
|
||||
import 'package:mangayomi/modules/more/widgets/list_tile_widget.dart';
|
||||
import 'package:mangayomi/providers/l10n_providers.dart';
|
||||
|
|
@ -26,18 +26,6 @@ class PlayerScreen extends ConsumerStatefulWidget {
|
|||
}
|
||||
|
||||
class _PlayerScreenState extends ConsumerState<PlayerScreen> {
|
||||
int _total = 0;
|
||||
int _received = 0;
|
||||
http.StreamedResponse? _response;
|
||||
final List<int> _bytes = [];
|
||||
StreamSubscription<List<int>>? _subscription;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_subscription?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final defaultSubtitleLang = ref.watch(defaultSubtitleLangStateProvider);
|
||||
|
|
@ -631,110 +619,51 @@ class _PlayerScreenState extends ConsumerState<PlayerScreen> {
|
|||
context: context,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
content: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
Text(context.l10n.mpv_download),
|
||||
_total > 0
|
||||
? Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
Flexible(
|
||||
child: LinearProgressIndicator(
|
||||
value: _total > 0
|
||||
? (_received * 1.0) / _total
|
||||
: 0.0,
|
||||
),
|
||||
),
|
||||
Flexible(
|
||||
child: Text(
|
||||
'${(_received / 1048576.0).toStringAsFixed(2)}/${(_total / 1048576.0).toStringAsFixed(2)} MB',
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
: SizedBox.shrink(),
|
||||
],
|
||||
),
|
||||
),
|
||||
content: Text(context.l10n.mpv_download),
|
||||
actions: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
try {
|
||||
await _subscription?.cancel();
|
||||
} catch (_) {}
|
||||
if (context.mounted) {
|
||||
Navigator.pop(context);
|
||||
}
|
||||
},
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text(context.l10n.cancel),
|
||||
),
|
||||
const SizedBox(width: 15),
|
||||
ElevatedButton(
|
||||
onPressed: _total == 0
|
||||
? () async {
|
||||
_response = await http.Client().send(
|
||||
http.Request(
|
||||
'GET',
|
||||
Uri.parse(
|
||||
"https://github.com/Schnitzel5/mangayomi/releases/download/v0.6.3-anime4k/mangayomi_mpv.zip",
|
||||
),
|
||||
),
|
||||
);
|
||||
_total = _response?.contentLength ?? 0;
|
||||
_subscription = _response?.stream.listen((value) {
|
||||
setState(() {
|
||||
_bytes.addAll(value);
|
||||
_received += value.length;
|
||||
});
|
||||
});
|
||||
_subscription?.onDone(() async {
|
||||
final archive = ZipDecoder().decodeBytes(_bytes);
|
||||
String shadersDir = path.join(
|
||||
dir.path,
|
||||
'shaders',
|
||||
);
|
||||
await Directory(
|
||||
shadersDir,
|
||||
).create(recursive: true);
|
||||
String scriptsDir = path.join(
|
||||
dir.path,
|
||||
'scripts',
|
||||
);
|
||||
await Directory(
|
||||
scriptsDir,
|
||||
).create(recursive: true);
|
||||
for (final file in archive.files) {
|
||||
if (file.name == "mpv.conf") {
|
||||
await mpvFile.writeAsBytes(file.content);
|
||||
} else if (file.name == "input.conf") {
|
||||
await inputFile.writeAsBytes(file.content);
|
||||
} else if (file.name.startsWith("shaders/") &&
|
||||
file.name.endsWith(".glsl")) {
|
||||
final shaderFile = File(
|
||||
'$shadersDir/${file.name.split("/").last}',
|
||||
);
|
||||
await shaderFile.writeAsBytes(file.content);
|
||||
} else if (file.name.startsWith("scripts/") &&
|
||||
file.name.endsWith(".js")) {
|
||||
final scriptFile = File(
|
||||
'$scriptsDir/${file.name.split("/").last}',
|
||||
);
|
||||
await scriptFile.writeAsBytes(file.content);
|
||||
}
|
||||
}
|
||||
_total = 0;
|
||||
_received = 0;
|
||||
_bytes.clear();
|
||||
if (context.mounted) {
|
||||
Navigator.pop(context, "ok");
|
||||
}
|
||||
});
|
||||
}
|
||||
: null,
|
||||
onPressed: () async {
|
||||
final bytes = await rootBundle.load(
|
||||
"assets/mangayomi_mpv.zip",
|
||||
);
|
||||
final archive = ZipDecoder().decodeBytes(
|
||||
bytes.buffer.asUint8List(),
|
||||
);
|
||||
String shadersDir = path.join(dir.path, 'shaders');
|
||||
await Directory(shadersDir).create(recursive: true);
|
||||
String scriptsDir = path.join(dir.path, 'scripts');
|
||||
await Directory(scriptsDir).create(recursive: true);
|
||||
for (final file in archive.files) {
|
||||
if (file.name == "mpv.conf") {
|
||||
await mpvFile.writeAsBytes(file.content);
|
||||
} else if (file.name == "input.conf") {
|
||||
await inputFile.writeAsBytes(file.content);
|
||||
} else if (file.name.startsWith("shaders/") &&
|
||||
file.name.endsWith(".glsl")) {
|
||||
final shaderFile = File(
|
||||
'$shadersDir/${file.name.split("/").last}',
|
||||
);
|
||||
await shaderFile.writeAsBytes(file.content);
|
||||
} else if (file.name.startsWith("scripts/") &&
|
||||
file.name.endsWith(".js")) {
|
||||
final scriptFile = File(
|
||||
'$scriptsDir/${file.name.split("/").last}',
|
||||
);
|
||||
await scriptFile.writeAsBytes(file.content);
|
||||
}
|
||||
}
|
||||
if (context.mounted) {
|
||||
Navigator.pop(context, "ok");
|
||||
}
|
||||
},
|
||||
child: Text(context.l10n.download),
|
||||
),
|
||||
],
|
||||
|
|
|
|||
|
|
@ -7,5 +7,7 @@ part 'custom_buttons_provider.g.dart';
|
|||
|
||||
@riverpod
|
||||
Stream<List<CustomButton>> getCustomButtonsStream(Ref ref) async* {
|
||||
yield* isar.customButtons.filter().idIsNotNull().watch(fireImmediately: true);
|
||||
yield* isar.customButtons.filter().idIsNotNull().sortByPos().watch(
|
||||
fireImmediately: true,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ part of 'custom_buttons_provider.dart';
|
|||
// **************************************************************************
|
||||
|
||||
String _$getCustomButtonsStreamHash() =>
|
||||
r'463d2142793ffb5a905f6f90c3a756445be8b133';
|
||||
r'476c26eb3d20e9e9eed2e1d8bb15fa74ce357ba3';
|
||||
|
||||
/// See also [getCustomButtonsStream].
|
||||
@ProviderFor(getCustomButtonsStream)
|
||||
|
|
|
|||
|
|
@ -31,6 +31,12 @@ class ListTileWidget extends StatelessWidget {
|
|||
child: Icon(icon, color: context.primaryColor),
|
||||
),
|
||||
title: Text(title),
|
||||
subtitle: subtitle != null
|
||||
? Text(
|
||||
subtitle!,
|
||||
style: TextStyle(fontSize: 11, color: context.secondaryColor),
|
||||
)
|
||||
: null,
|
||||
trailing: trailing,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -203,19 +203,28 @@ class StorageProvider {
|
|||
|
||||
final customButton = await isar.customButtons
|
||||
.filter()
|
||||
.idEqualTo(1)
|
||||
.idIsNotNull()
|
||||
.findFirst();
|
||||
if (customButton == null) {
|
||||
await isar.writeTxn(() async {
|
||||
isar.customButtons.put(
|
||||
await isar.customButtons.put(
|
||||
CustomButton(
|
||||
title: "+85 s",
|
||||
codePress:
|
||||
"var intro_length = mp.get_property_number(\"user-data/current-anime/intro-length\")\naniyomi.right_seek_by(intro_length)",
|
||||
"""var intro_length = mp.get_property_number("user-data/current-anime/intro-length")
|
||||
aniyomi.right_seek_by(intro_length)""",
|
||||
codeLongPress:
|
||||
"aniyomi.int_picker(\"Change intro length\", \"%ds\", 0, 255, 1, \"user-data/current-anime/intro-length\")",
|
||||
codeStartup:
|
||||
"function update_button(_, length) {\n if (!length || length == 0) {\n aniyomi.hide_button()\n } else {\n aniyomi.show_button()\n }\n aniyomi.set_button_title(\"+\" + length + \" s\")",
|
||||
"""aniyomi.int_picker("Change intro length", "%ds", 0, 255, 1, "user-data/current-anime/intro-length")""",
|
||||
codeStartup: """function update_button(_, length) {
|
||||
if (length && length == 0) {
|
||||
aniyomi.hide_button()
|
||||
} else {
|
||||
aniyomi.show_button()
|
||||
}
|
||||
aniyomi.set_button_title("+" + length + " s")
|
||||
if (\$isPrimary) {
|
||||
mp.observe_property("user-data/current-anime/intro-length", "number", update_button)
|
||||
}""",
|
||||
isFavourite: true,
|
||||
pos: 0,
|
||||
updatedAt: DateTime.now().millisecondsSinceEpoch,
|
||||
|
|
|
|||
Loading…
Reference in a new issue