Add input dialog

This commit is contained in:
2025-01-19 20:55:53 +08:00
parent 51b7df02e7
commit 63346396e0
4 changed files with 56 additions and 15 deletions

View File

@@ -1276,5 +1276,20 @@ let UI = {
function: 'cancelLoading', function: 'cancelLoading',
id: id id: id
}) })
},
/**
* Show an input dialog
* @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.
* @returns {string | null} - The input value. If the dialog is canceled, return null.
*/
showInputDialog: (title, validator) => {
return sendMessage({
method: 'UI',
function: 'showInputDialog',
title: title,
validator: validator
})
} }
} }

View File

@@ -34,6 +34,12 @@ mixin class JsUiApi {
if (id is int) { if (id is int) {
cancelLoading(id); cancelLoading(id);
} }
case 'showInputDialog':
var title = message['title'];
var validator = message['validator'];
if (title is! String) return;
if (validator != null && validator is! JSInvokable) return;
return _showInputDialog(title, validator);
} }
} }
@@ -47,8 +53,6 @@ mixin class JsUiApi {
continue; continue;
} }
var callback = action['callback'] as JSInvokable; var callback = action['callback'] as JSInvokable;
// [message] will be released after the method call, causing the action to be invalid, so we need to duplicate it
callback.dup();
var text = action['text'].toString(); var text = action['text'].toString();
var style = (action['style'] ?? 'text').toString(); var style = (action['style'] ?? 'text').toString();
actions.add(_JSCallbackButton( actions.add(_JSCallbackButton(
@@ -84,13 +88,14 @@ mixin class JsUiApi {
} }
int showLoading(JSInvokable? onCancel) { int showLoading(JSInvokable? onCancel) {
onCancel?.dup();
var func = onCancel == null ? null : JSAutoFreeFunction(onCancel); var func = onCancel == null ? null : JSAutoFreeFunction(onCancel);
var controller = showLoadingDialog( var controller = showLoadingDialog(
App.rootContext, App.rootContext,
barrierDismissible: onCancel != null, barrierDismissible: onCancel != null,
allowCancel: onCancel != null, allowCancel: onCancel != null,
onCancel: onCancel == null ? null : () { onCancel: onCancel == null
? null
: () {
func?.call([]); func?.call([]);
}, },
); );
@@ -106,6 +111,29 @@ mixin class JsUiApi {
var controller = _loadingDialogControllers.remove(id); var controller = _loadingDialogControllers.remove(id);
controller?.close(); controller?.close();
} }
Future<String?> _showInputDialog(String title, JSInvokable? validator) async {
String? result;
var func = validator == null ? null : JSAutoFreeFunction(validator);
await showInputDialog(
context: App.rootContext,
title: title,
onConfirm: (v) {
if (func != null) {
var res = func.call([v]);
if (res != null) {
return res.toString();
} else {
result = v;
}
} else {
result = v;
}
return null;
},
);
return result;
}
} }
class _JSCallbackButton extends StatefulWidget { class _JSCallbackButton extends StatefulWidget {

View File

@@ -63,7 +63,8 @@ class ReaderImageProvider
})() })()
'''); ''');
if (func is JSInvokable) { if (func is JSInvokable) {
var result = func.invoke([imageBytes, cid, eid, page, sourceKey]); var autoFreeFunc = JSAutoFreeFunction(func);
var result = autoFreeFunc([imageBytes, cid, eid, page, sourceKey]);
if (result is Uint8List) { if (result is Uint8List) {
imageBytes = result; imageBytes = result;
} else if (result is Future) { } else if (result is Future) {
@@ -76,9 +77,9 @@ class ReaderImageProvider
if (image is Uint8List) { if (image is Uint8List) {
imageBytes = image; imageBytes = image;
} else if (image is Future) { } else if (image is Future) {
JSInvokable? onCancel; JSAutoFreeFunction? onCancel;
if (result['onCancel'] is JSInvokable) { if (result['onCancel'] is JSInvokable) {
onCancel = result['onCancel']; onCancel = JSAutoFreeFunction(result['onCancel']);
} }
if (onCancel == null) { if (onCancel == null) {
var futureImage = await image; var futureImage = await image;
@@ -96,9 +97,7 @@ class ReaderImageProvider
checkStop(); checkStop();
} }
catch(e) { catch(e) {
onCancel.invoke([]); onCancel([]);
onCancel.free();
func.free();
rethrow; rethrow;
} }
await Future.delayed(Duration(milliseconds: 50)); await Future.delayed(Duration(milliseconds: 50));
@@ -107,10 +106,8 @@ class ReaderImageProvider
imageBytes = futureImage; imageBytes = futureImage;
} }
} }
onCancel?.free();
} }
} }
func.free();
} }
} }
return imageBytes!; return imageBytes!;

View File

@@ -677,6 +677,7 @@ class JSAutoFreeFunction {
/// Automatically free the function when it's not used anymore /// Automatically free the function when it's not used anymore
JSAutoFreeFunction(this.func) { JSAutoFreeFunction(this.func) {
func.dup();
finalizer.attach(this, func); finalizer.attach(this, func);
} }
@@ -685,6 +686,6 @@ class JSAutoFreeFunction {
} }
static final finalizer = Finalizer<JSInvokable>((func) { static final finalizer = Finalizer<JSInvokable>((func) {
func.free(); func.destroy();
}); });
} }