Files
FlClash/lib/common/dialog.dart
T
chen08209 f14e6f1460 feat(ui): rework the profile, provider, and subscription surfaces
Refines the profile, provider, and configuration interactions, virtualizes
the provider lists so a large subscription no longer builds every row, syncs
updates through the path the list reads, and expands the subscription
details with full usage.

Paged sheets are scoped locally and sized to the page in view instead of the
tallest one, so a sheet no longer reserves space it never uses.

Changelog: Provider lists scroll smoothly, subscriptions show full usage details, and sheets size to the page in view
2026-08-28 10:07:17 +08:00

189 lines
5.2 KiB
Dart

import 'dart:async';
import 'package:animations/animations.dart';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/models.dart';
import 'package:fl_clash/widgets/widgets.dart';
import 'package:material_ui/material_ui.dart';
import 'package:url_launcher/url_launcher.dart';
class Dialogs {
Dialogs._();
BuildContext get _context => rootNavigatorKey.currentContext!;
Future<T?> showCommonDialog<T>({
required Widget child,
BuildContext? context,
bool? dismissible,
bool filter = true,
}) async {
return showModal<T>(
useRootNavigator: false,
context: context ?? _context,
configuration: FadeScaleTransitionConfiguration(
barrierColor: Colors.black38,
barrierDismissible: dismissible ?? true,
),
builder: (_) => child,
filter: filter ? commonFilter : null,
);
}
Future<bool?> showMessage({
required InlineSpan message,
BuildContext? context,
String? title,
String? confirmText,
String? cancelText,
bool cancelable = true,
bool? dismissible,
}) async {
return showCommonDialog<bool>(
context: context,
dismissible: dismissible,
child: Builder(
builder: (context) {
final appLocalizations = context.appLocalizations;
return CommonDialog(
title: title ?? appLocalizations.tip,
actions: [
if (cancelable)
TextButton(
onPressed: () {
Navigator.of(context).pop(false);
},
child: Text(cancelText ?? appLocalizations.cancel),
),
TextButton(
onPressed: () {
Navigator.of(context).pop(true);
},
child: Text(confirmText ?? appLocalizations.confirm),
),
],
child: Container(
width: 300,
constraints: const BoxConstraints(maxHeight: 200),
child: SingleChildScrollView(
child: SelectableText.rich(
TextSpan(
style: Theme.of(context).textTheme.labelLarge,
children: [message],
),
style: const TextStyle(overflow: TextOverflow.visible),
),
),
),
);
},
),
);
}
Future<bool?> showAllUpdatingMessagesDialog(
List<UpdatingMessage> messages,
) async {
return showCommonDialog<bool>(
child: Builder(
builder: (context) {
final appLocalizations = context.appLocalizations;
return CommonDialog(
backgroundColor: context.colorScheme.surfaceContainerLow,
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
title: appLocalizations.tip,
actions: [
TextButton(
onPressed: () {
Navigator.of(context).pop(true);
},
child: Text(appLocalizations.confirm),
),
],
child: generateSectionV3(
items: messages.map(
(message) => _UpdatingMessageItem(message: message),
),
),
);
},
),
);
}
Future<bool> showDisclaimer() async {
return await showCommonDialog<bool>(
dismissible: false,
child: CommonDialog(
title: currentAppLocalizations.disclaimer,
actions: [
TextButton(
onPressed: () {
Navigator.of(_context).pop<bool>(false);
},
child: Text(currentAppLocalizations.exit),
),
TextButton(
onPressed: () {
Navigator.of(_context).pop<bool>(true);
},
child: Text(currentAppLocalizations.agree),
),
],
child: Text(currentAppLocalizations.disclaimerDesc),
),
) ??
false;
}
void showNotifier(
String text, {
MessageLevel level = MessageLevel.info,
MessageActionState? actionState,
}) {
rootNavigatorKey.currentContext?.showNotifier(
text,
level: level,
actionState: actionState,
);
}
Future<void> openUrl(String url) async {
final res = await showMessage(
message: TextSpan(text: url),
title: currentAppLocalizations.externalLink,
confirmText: currentAppLocalizations.go,
);
if (res != true) {
return;
}
unawaited(launchUrl(Uri.parse(url)));
}
}
class _UpdatingMessageItem extends StatelessWidget {
final UpdatingMessage message;
const _UpdatingMessageItem({required this.message});
@override
Widget build(BuildContext context) {
return DecorationListItem(
minVerticalPadding: 12,
title: TooltipText(
text: Text(message.label, maxLines: 1, overflow: TextOverflow.ellipsis),
),
subtitle: TooltipText(
text: Text(
message.message,
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
),
);
}
}
final dialogs = Dialogs._();