Initial commit
This commit is contained in:
170
lib/widgets/future_loading_dialog.dart
Normal file
170
lib/widgets/future_loading_dialog.dart
Normal file
@@ -0,0 +1,170 @@
|
||||
// SPDX-FileCopyrightText: 2019-Present Christian Kußowski
|
||||
// SPDX-FileCopyrightText: 2019-Present Contributors to FluffyChat
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:async/async.dart';
|
||||
import 'package:tatchat/l10n/l10n.dart';
|
||||
import 'package:tatchat/utils/localized_exception_extension.dart';
|
||||
import 'package:tatchat/widgets/adaptive_dialogs/adaptive_dialog_action.dart';
|
||||
import 'package:material_ui/material_ui.dart';
|
||||
import 'package:matrix/matrix_api_lite/utils/logs.dart';
|
||||
|
||||
/// Displays a loading dialog which reacts to the given [future]. The dialog
|
||||
/// will be dismissed and the value will be returned when the future completes.
|
||||
/// If an error occured, then [onError] will be called and this method returns
|
||||
/// null.
|
||||
Future<Result<T>> showFutureLoadingDialog<T>({
|
||||
required BuildContext context,
|
||||
Future<T> Function()? future,
|
||||
Future<T> Function(void Function(double?) setProgress)? futureWithProgress,
|
||||
String? title,
|
||||
String? backLabel,
|
||||
bool barrierDismissible = false,
|
||||
bool delay = true,
|
||||
ExceptionContext? exceptionContext,
|
||||
bool ignoreError = false,
|
||||
}) async {
|
||||
assert(future != null || futureWithProgress != null);
|
||||
final onProgressStream = StreamController<double?>();
|
||||
final futureExec =
|
||||
futureWithProgress?.call(onProgressStream.add) ?? future!();
|
||||
final resultFuture = ResultFuture(futureExec);
|
||||
|
||||
if (delay) {
|
||||
var i = 3;
|
||||
while (i > 0) {
|
||||
final result = resultFuture.result;
|
||||
if (result != null) {
|
||||
if (result.isError) break;
|
||||
return result;
|
||||
}
|
||||
await Future.delayed(const Duration(milliseconds: 100));
|
||||
i--;
|
||||
}
|
||||
}
|
||||
|
||||
if (!context.mounted) {
|
||||
Logs().e(
|
||||
'Unable to show loading dialog!',
|
||||
Exception('The BuildContext is not mounted!'),
|
||||
StackTrace.current,
|
||||
);
|
||||
return Result.capture(futureExec);
|
||||
}
|
||||
|
||||
final result = await showAdaptiveDialog<Result<T>>(
|
||||
context: context,
|
||||
barrierDismissible: barrierDismissible,
|
||||
builder: (BuildContext context) => LoadingDialog<T>(
|
||||
future: futureExec,
|
||||
title: title,
|
||||
backLabel: backLabel,
|
||||
exceptionContext: exceptionContext,
|
||||
onProgressStream: onProgressStream.stream,
|
||||
),
|
||||
);
|
||||
return result ??
|
||||
Result.error(Exception('FutureDialog canceled'), StackTrace.current);
|
||||
}
|
||||
|
||||
class LoadingDialog<T> extends StatefulWidget {
|
||||
final String? title;
|
||||
final String? backLabel;
|
||||
final Future<T> future;
|
||||
final ExceptionContext? exceptionContext;
|
||||
final Stream<double?> onProgressStream;
|
||||
|
||||
const LoadingDialog({
|
||||
super.key,
|
||||
required this.future,
|
||||
this.title,
|
||||
this.backLabel,
|
||||
this.exceptionContext,
|
||||
required this.onProgressStream,
|
||||
});
|
||||
|
||||
@override
|
||||
LoadingDialogState<T> createState() => LoadingDialogState<T>();
|
||||
}
|
||||
|
||||
class LoadingDialogState<T> extends State<LoadingDialog> {
|
||||
Object? exception;
|
||||
StackTrace? stackTrace;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
widget.future.then(
|
||||
(result) {
|
||||
if (!mounted) return;
|
||||
if (!Navigator.of(context).canPop()) return;
|
||||
Navigator.of(context).pop<Result<T>>(Result.value(result));
|
||||
},
|
||||
onError: (e, s) => setState(() {
|
||||
exception = e;
|
||||
stackTrace = s;
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final exception = this.exception;
|
||||
final titleLabel = exception != null
|
||||
? exception.toLocalizedString(context, widget.exceptionContext)
|
||||
: widget.title ?? L10n.of(context).loadingPleaseWait;
|
||||
|
||||
return AlertDialog.adaptive(
|
||||
title: exception == null
|
||||
? null
|
||||
: Icon(
|
||||
Icons.error_outline_outlined,
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
size: 48,
|
||||
),
|
||||
content: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 256),
|
||||
child: Row(
|
||||
crossAxisAlignment: .center,
|
||||
children: [
|
||||
if (exception == null) ...[
|
||||
StreamBuilder(
|
||||
stream: widget.onProgressStream,
|
||||
builder: (context, snapshot) =>
|
||||
CircularProgressIndicator.adaptive(value: snapshot.data),
|
||||
),
|
||||
const SizedBox(width: 20),
|
||||
],
|
||||
Expanded(
|
||||
child: Text(
|
||||
titleLabel,
|
||||
maxLines: 4,
|
||||
textAlign: exception == null ? TextAlign.left : null,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
actions: exception == null
|
||||
? null
|
||||
: [
|
||||
AdaptiveDialogAction(
|
||||
onPressed: () => Navigator.of(
|
||||
context,
|
||||
).pop<Result<T>>(Result.error(exception, stackTrace)),
|
||||
child: Text(widget.backLabel ?? L10n.of(context).close),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
extension DeprecatedApiAccessExtension<T> on Result<T> {
|
||||
T? get result => asValue?.value;
|
||||
|
||||
Object? get error => asError?.error;
|
||||
}
|
||||
Reference in New Issue
Block a user