mirror of
https://github.com/venera-app/venera.git
synced 2025-12-16 23:11:15 +00:00
Compare commits
4 Commits
b08f11f6ac
...
feat/js-di
| Author | SHA1 | Date | |
|---|---|---|---|
| e8d98e8274 | |||
| 09a1d2821c | |||
|
|
7842b5a1ac | ||
|
|
079f574e2f |
@@ -1334,7 +1334,7 @@ let UI = {
|
|||||||
* Show an input dialog
|
* Show an input dialog
|
||||||
* @param title {string}
|
* @param title {string}
|
||||||
* @param validator {(string) => string | null | undefined} - A function that validates the input. If the function returns a string, the dialog will show the error message.
|
* @param validator {(string) => string | null | undefined} - A function that validates the input. If the function returns a string, the dialog will show the error message.
|
||||||
* @param image {string?} - Available since 1.4.6. An optional image to show in the dialog. You can use this to show a captcha.
|
* @param image {string | ArrayBuffer | null | undefined} - Since 1.4.6, you can pass an image url to show an image in the dialog. Since 1.5.3, you can also pass an ArrayBuffer to show a custom image.
|
||||||
* @returns {Promise<string | null>} - The input value. If the dialog is canceled, return null.
|
* @returns {Promise<string | null>} - The input value. If the dialog is canceled, return null.
|
||||||
*/
|
*/
|
||||||
showInputDialog: (title, validator, image) => {
|
showInputDialog: (title, validator, image) => {
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_qjs/flutter_qjs.dart';
|
import 'package:flutter_qjs/flutter_qjs.dart';
|
||||||
import 'package:url_launcher/url_launcher_string.dart';
|
import 'package:url_launcher/url_launcher_string.dart';
|
||||||
@@ -40,7 +42,6 @@ mixin class JsUiApi {
|
|||||||
var image = message['image'];
|
var image = message['image'];
|
||||||
if (title is! String) return;
|
if (title is! String) return;
|
||||||
if (validator != null && validator is! JSInvokable) return;
|
if (validator != null && validator is! JSInvokable) return;
|
||||||
if (image != null && image is! String) return;
|
|
||||||
return _showInputDialog(title, validator, image);
|
return _showInputDialog(title, validator, image);
|
||||||
case 'showSelectDialog':
|
case 'showSelectDialog':
|
||||||
var title = message['title'];
|
var title = message['title'];
|
||||||
@@ -126,13 +127,25 @@ mixin class JsUiApi {
|
|||||||
controller?.close();
|
controller?.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<String?> _showInputDialog(String title, JSInvokable? validator, String? image) async {
|
Future<String?> _showInputDialog(String title, JSInvokable? validator, dynamic image) async {
|
||||||
String? result;
|
String? result;
|
||||||
var func = validator == null ? null : JSAutoFreeFunction(validator);
|
var func = validator == null ? null : JSAutoFreeFunction(validator);
|
||||||
|
String? imageUrl;
|
||||||
|
Uint8List? imageData;
|
||||||
|
if (image != null) {
|
||||||
|
if (image is String) {
|
||||||
|
imageUrl = image;
|
||||||
|
} else if (image is Uint8List) {
|
||||||
|
imageData = image;
|
||||||
|
} else if (image is List<int>) {
|
||||||
|
imageData = Uint8List.fromList(image);
|
||||||
|
}
|
||||||
|
}
|
||||||
await showInputDialog(
|
await showInputDialog(
|
||||||
context: App.rootContext,
|
context: App.rootContext,
|
||||||
title: title,
|
title: title,
|
||||||
image: image,
|
image: imageUrl,
|
||||||
|
imageData: imageData,
|
||||||
onConfirm: (v) {
|
onConfirm: (v) {
|
||||||
if (func != null) {
|
if (func != null) {
|
||||||
var res = func.call([v]);
|
var res = func.call([v]);
|
||||||
|
|||||||
@@ -360,6 +360,7 @@ Future<void> showInputDialog({
|
|||||||
String cancelText = "Cancel",
|
String cancelText = "Cancel",
|
||||||
RegExp? inputValidator,
|
RegExp? inputValidator,
|
||||||
String? image,
|
String? image,
|
||||||
|
Uint8List? imageData,
|
||||||
}) {
|
}) {
|
||||||
var controller = TextEditingController(text: initialValue);
|
var controller = TextEditingController(text: initialValue);
|
||||||
bool isLoading = false;
|
bool isLoading = false;
|
||||||
@@ -379,6 +380,11 @@ Future<void> showInputDialog({
|
|||||||
height: 108,
|
height: 108,
|
||||||
child: Image.network(image, fit: BoxFit.none),
|
child: Image.network(image, fit: BoxFit.none),
|
||||||
).paddingBottom(8),
|
).paddingBottom(8),
|
||||||
|
if (image == null && imageData != null)
|
||||||
|
SizedBox(
|
||||||
|
height: 108,
|
||||||
|
child: Image.memory(imageData, fit: BoxFit.none),
|
||||||
|
).paddingBottom(8),
|
||||||
TextField(
|
TextField(
|
||||||
controller: controller,
|
controller: controller,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
|
|||||||
@@ -181,7 +181,15 @@ abstract class ImageDownloader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (configs['onResponse'] is JSInvokable) {
|
if (configs['onResponse'] is JSInvokable) {
|
||||||
buffer = (configs['onResponse'] as JSInvokable)([Uint8List.fromList(buffer)]);
|
dynamic result = (configs['onResponse'] as JSInvokable)([Uint8List.fromList(buffer)]);
|
||||||
|
if (result is Future) {
|
||||||
|
result = await result;
|
||||||
|
}
|
||||||
|
if (result is List<int>) {
|
||||||
|
buffer = result;
|
||||||
|
} else {
|
||||||
|
throw "Error: Invalid onResponse result.";
|
||||||
|
}
|
||||||
(configs['onResponse'] as JSInvokable).free();
|
(configs['onResponse'] as JSInvokable).free();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -197,11 +197,12 @@ class _NetworkSectionState extends State<_NetworkSection> {
|
|||||||
if (res.subData is List) {
|
if (res.subData is List) {
|
||||||
final list = List<String>.from(res.subData);
|
final list = List<String>.from(res.subData);
|
||||||
if (list.isNotEmpty) {
|
if (list.isNotEmpty) {
|
||||||
addedFolders = {list.first};
|
addedFolders = list.toSet();
|
||||||
|
localIsFavorite = true;
|
||||||
} else {
|
} else {
|
||||||
addedFolders.clear();
|
addedFolders.clear();
|
||||||
|
localIsFavorite = false;
|
||||||
}
|
}
|
||||||
localIsFavorite = addedFolders.isNotEmpty;
|
|
||||||
} else {
|
} else {
|
||||||
addedFolders.clear();
|
addedFolders.clear();
|
||||||
localIsFavorite = false;
|
localIsFavorite = false;
|
||||||
@@ -352,62 +353,6 @@ class _NetworkSectionState extends State<_NetworkSection> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildMultiFolder() {
|
Widget _buildMultiFolder() {
|
||||||
if (localIsFavorite == true &&
|
|
||||||
widget.comicSource.favoriteData!.singleFolderForSingleComic) {
|
|
||||||
return ListTile(
|
|
||||||
title: Row(
|
|
||||||
children: [
|
|
||||||
Text("Network Favorites".tl),
|
|
||||||
const SizedBox(width: 8),
|
|
||||||
Container(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: context.colorScheme.primaryContainer,
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
),
|
|
||||||
child: Text("Added".tl, style: ts.s12),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
trailing: isLoading
|
|
||||||
? const SizedBox(
|
|
||||||
width: 20,
|
|
||||||
height: 20,
|
|
||||||
child: CircularProgressIndicator(strokeWidth: 2),
|
|
||||||
)
|
|
||||||
: _HoverButton(
|
|
||||||
isFavorite: true,
|
|
||||||
onTap: () async {
|
|
||||||
setState(() {
|
|
||||||
isLoading = true;
|
|
||||||
});
|
|
||||||
|
|
||||||
var res = await widget
|
|
||||||
.comicSource
|
|
||||||
.favoriteData!
|
|
||||||
.addOrDelFavorite!(widget.cid, '', false, null);
|
|
||||||
if (res.success) {
|
|
||||||
// Invalidate network cache so subsequent loads see latest
|
|
||||||
NetworkCacheManager().clear();
|
|
||||||
setState(() {
|
|
||||||
localIsFavorite = false;
|
|
||||||
});
|
|
||||||
widget.onFavorite(false);
|
|
||||||
App.rootContext.showMessage(message: "Removed".tl);
|
|
||||||
if (appdata.settings['autoCloseFavoritePanel'] ?? false) {
|
|
||||||
context.pop();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
context.showMessage(message: res.errorMessage!);
|
|
||||||
}
|
|
||||||
setState(() {
|
|
||||||
isLoading = false;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
@@ -425,8 +370,10 @@ class _NetworkSectionState extends State<_NetworkSection> {
|
|||||||
var name = entry.value;
|
var name = entry.value;
|
||||||
var id = entry.key;
|
var id = entry.key;
|
||||||
var isAdded = addedFolders.contains(id);
|
var isAdded = addedFolders.contains(id);
|
||||||
var hasSelection = addedFolders.isNotEmpty;
|
// When `singleFolderForSingleComic` is `false`, all add and remove buttons are clickable.
|
||||||
var enabled = !hasSelection || isAdded;
|
// When `singleFolderForSingleComic` is `true`, the remove button is always clickable,
|
||||||
|
// while the add button is only clickable if the comic has not been added to any list.
|
||||||
|
var enabled = !(widget.comicSource.favoriteData!.singleFolderForSingleComic && addedFolders.isNotEmpty && !isAdded);
|
||||||
|
|
||||||
return ListTile(
|
return ListTile(
|
||||||
title: Row(
|
title: Row(
|
||||||
@@ -469,11 +416,9 @@ class _NetworkSectionState extends State<_NetworkSection> {
|
|||||||
NetworkCacheManager().clear();
|
NetworkCacheManager().clear();
|
||||||
setState(() {
|
setState(() {
|
||||||
if (isAdded) {
|
if (isAdded) {
|
||||||
addedFolders.clear();
|
addedFolders.remove(id);
|
||||||
} else {
|
} else {
|
||||||
addedFolders
|
addedFolders.add(id);
|
||||||
..clear()
|
|
||||||
..add(id);
|
|
||||||
}
|
}
|
||||||
// sync local flag for single-folder-per-comic logic and parent
|
// sync local flag for single-folder-per-comic logic and parent
|
||||||
localIsFavorite = addedFolders.isNotEmpty;
|
localIsFavorite = addedFolders.isNotEmpty;
|
||||||
|
|||||||
Reference in New Issue
Block a user