From 88c8227c66f3375d897cb017c258eebfd66b1365 Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Mon, 24 Nov 2025 23:13:35 +0800 Subject: [PATCH] :recycle: Dangerous confirm dialog variant --- lib/screens/account/me/account_settings.dart | 1 + .../account/me/settings_auth_factors.dart | 1 + .../account/me/settings_connections.dart | 2 + lib/screens/account/me/settings_contacts.dart | 1 + lib/screens/chat/room_detail.dart | 1 + lib/screens/creators/hub.dart | 22 ++--- lib/screens/creators/sites/site_edit.dart | 1 + .../creators/stickers/pack_detail.dart | 1 + .../creators/webfeed/webfeed_edit.dart | 1 + lib/screens/developers/app_secrets.dart | 1 + lib/screens/developers/apps.dart | 1 + lib/screens/developers/bot_keys.dart | 8 +- lib/screens/developers/bots.dart | 1 + lib/screens/developers/hub.dart | 1 + lib/screens/posts/post_detail.dart | 8 +- lib/screens/realm/realm_detail.dart | 1 + lib/widgets/account/account_devices.dart | 2 + lib/widgets/alert.dart | 85 +++++++++++++++++-- lib/widgets/chat/message_item.dart | 1 + lib/widgets/file_list_view.dart | 5 ++ lib/widgets/post/draft_manager.dart | 1 + lib/widgets/post/post_item.dart | 1 + lib/widgets/post/post_item_creator.dart | 34 ++++---- .../sites/file_management_action_section.dart | 24 +----- 24 files changed, 144 insertions(+), 61 deletions(-) diff --git a/lib/screens/account/me/account_settings.dart b/lib/screens/account/me/account_settings.dart index 81bb383b..810e5035 100644 --- a/lib/screens/account/me/account_settings.dart +++ b/lib/screens/account/me/account_settings.dart @@ -62,6 +62,7 @@ class AccountSettingsScreen extends HookConsumerWidget { final confirm = await showConfirmAlert( 'accountDeletionHint'.tr(), 'accountDeletion'.tr(), + isDanger: true, ); if (!confirm || !context.mounted) return; try { diff --git a/lib/screens/account/me/settings_auth_factors.dart b/lib/screens/account/me/settings_auth_factors.dart index 4a33a07c..86ea4c7a 100644 --- a/lib/screens/account/me/settings_auth_factors.dart +++ b/lib/screens/account/me/settings_auth_factors.dart @@ -26,6 +26,7 @@ class AuthFactorSheet extends HookConsumerWidget { final confirm = await showConfirmAlert( 'authFactorDeleteHint'.tr(), 'authFactorDelete'.tr(), + isDanger: true, ); if (!confirm || !context.mounted) return; try { diff --git a/lib/screens/account/me/settings_connections.dart b/lib/screens/account/me/settings_connections.dart index 7369474c..e3640fc6 100644 --- a/lib/screens/account/me/settings_connections.dart +++ b/lib/screens/account/me/settings_connections.dart @@ -82,6 +82,7 @@ class AccountConnectionSheet extends HookConsumerWidget { final confirm = await showConfirmAlert( 'accountConnectionDeleteHint'.tr(), 'accountConnectionDelete'.tr(), + isDanger: true, ); if (!confirm || !context.mounted) return; try { @@ -332,6 +333,7 @@ class AccountConnectionsSheet extends HookConsumerWidget { final confirm = await showConfirmAlert( 'accountConnectionDeleteHint'.tr(), 'accountConnectionDelete'.tr(), + isDanger: true, ); if (confirm && context.mounted) { try { diff --git a/lib/screens/account/me/settings_contacts.dart b/lib/screens/account/me/settings_contacts.dart index c8923854..f24b02ca 100644 --- a/lib/screens/account/me/settings_contacts.dart +++ b/lib/screens/account/me/settings_contacts.dart @@ -20,6 +20,7 @@ class ContactMethodSheet extends HookConsumerWidget { final confirm = await showConfirmAlert( 'contactMethodDeleteHint'.tr(), 'contactMethodDelete'.tr(), + isDanger: true, ); if (!confirm || !context.mounted) return; try { diff --git a/lib/screens/chat/room_detail.dart b/lib/screens/chat/room_detail.dart index 325a766e..765060e2 100644 --- a/lib/screens/chat/room_detail.dart +++ b/lib/screens/chat/room_detail.dart @@ -487,6 +487,7 @@ class _ChatRoomActionMenu extends HookConsumerWidget { showConfirmAlert( 'deleteChatRoomHint'.tr(), 'deleteChatRoom'.tr(), + isDanger: true, ).then((confirm) async { if (confirm) { final client = ref.watch(apiClientProvider); diff --git a/lib/screens/creators/hub.dart b/lib/screens/creators/hub.dart index 79af2e49..50c4f9ac 100644 --- a/lib/screens/creators/hub.dart +++ b/lib/screens/creators/hub.dart @@ -304,16 +304,18 @@ class CreatorHubScreen extends HookConsumerWidget { } void deletePublisher() { - showConfirmAlert('deletePublisherHint'.tr(), 'deletePublisher'.tr()).then( - (confirm) { - if (confirm) { - final client = ref.watch(apiClientProvider); - client.delete('/sphere/publishers/${currentPublisher.value!.name}'); - ref.invalidate(publishersManagedProvider); - currentPublisher.value = null; - } - }, - ); + showConfirmAlert( + 'deletePublisherHint'.tr(), + 'deletePublisher'.tr(), + isDanger: true, + ).then((confirm) { + if (confirm) { + final client = ref.watch(apiClientProvider); + client.delete('/sphere/publishers/${currentPublisher.value!.name}'); + ref.invalidate(publishersManagedProvider); + currentPublisher.value = null; + } + }); } final List> publishersMenu = publishers.when( diff --git a/lib/screens/creators/sites/site_edit.dart b/lib/screens/creators/sites/site_edit.dart index 574e6b00..b9694664 100644 --- a/lib/screens/creators/sites/site_edit.dart +++ b/lib/screens/creators/sites/site_edit.dart @@ -190,6 +190,7 @@ class SiteForm extends HookConsumerWidget { final confirmed = await showConfirmAlert( 'publicationSiteDeleteConfirm'.tr(), 'deletePublicationSite'.tr(), + isDanger: true, ); if (confirmed != true) return; diff --git a/lib/screens/creators/stickers/pack_detail.dart b/lib/screens/creators/stickers/pack_detail.dart index cc2a8aff..85e915e2 100644 --- a/lib/screens/creators/stickers/pack_detail.dart +++ b/lib/screens/creators/stickers/pack_detail.dart @@ -288,6 +288,7 @@ class StickerPackActionMenu extends HookConsumerWidget { showConfirmAlert( 'deleteStickerPackHint'.tr(), 'deleteStickerPack'.tr(), + isDanger: true, ).then((confirm) { if (confirm) { final client = ref.watch(apiClientProvider); diff --git a/lib/screens/creators/webfeed/webfeed_edit.dart b/lib/screens/creators/webfeed/webfeed_edit.dart index 4f38de5e..1b03886b 100644 --- a/lib/screens/creators/webfeed/webfeed_edit.dart +++ b/lib/screens/creators/webfeed/webfeed_edit.dart @@ -70,6 +70,7 @@ class WebfeedForm extends HookConsumerWidget { final confirmed = await showConfirmAlert( 'Are you sure you want to delete this web feed? This action cannot be undone.', 'Delete Web Feed', + isDanger: true, ); if (confirmed != true) return; diff --git a/lib/screens/developers/app_secrets.dart b/lib/screens/developers/app_secrets.dart index fbf5c3ad..9254955a 100644 --- a/lib/screens/developers/app_secrets.dart +++ b/lib/screens/developers/app_secrets.dart @@ -211,6 +211,7 @@ class AppSecretsScreen extends HookConsumerWidget { showConfirmAlert( 'deleteSecretHint'.tr(), 'deleteSecret'.tr(), + isDanger: true, ).then((confirm) { if (confirm) { final client = ref.read(apiClientProvider); diff --git a/lib/screens/developers/apps.dart b/lib/screens/developers/apps.dart index 66f27b2d..86a0d547 100644 --- a/lib/screens/developers/apps.dart +++ b/lib/screens/developers/apps.dart @@ -231,6 +231,7 @@ class CustomAppsScreen extends HookConsumerWidget { showConfirmAlert( 'deleteCustomAppHint'.tr(), 'deleteCustomApp'.tr(), + isDanger: true, ).then((confirm) { if (confirm) { final client = ref.read( diff --git a/lib/screens/developers/bot_keys.dart b/lib/screens/developers/bot_keys.dart index 3f19084a..52ad32d7 100644 --- a/lib/screens/developers/bot_keys.dart +++ b/lib/screens/developers/bot_keys.dart @@ -159,9 +159,11 @@ class BotKeysScreen extends HookConsumerWidget { } void revokeKey(String keyId) { - showConfirmAlert('revokeBotKeyHint'.tr(), 'revokeBotKey'.tr()).then(( - confirm, - ) { + showConfirmAlert( + 'revokeBotKeyHint'.tr(), + 'revokeBotKey'.tr(), + isDanger: true, + ).then((confirm) { if (confirm) { final client = ref.read(apiClientProvider); client diff --git a/lib/screens/developers/bots.dart b/lib/screens/developers/bots.dart index ff49e4f8..a5f97dbb 100644 --- a/lib/screens/developers/bots.dart +++ b/lib/screens/developers/bots.dart @@ -172,6 +172,7 @@ class BotsScreen extends HookConsumerWidget { showConfirmAlert( 'deleteBotHint'.tr(), 'deleteBot'.tr(), + isDanger: true, ).then((confirm) { if (confirm) { final client = ref.read(apiClientProvider); diff --git a/lib/screens/developers/hub.dart b/lib/screens/developers/hub.dart index 70d860ea..a4418d96 100644 --- a/lib/screens/developers/hub.dart +++ b/lib/screens/developers/hub.dart @@ -631,6 +631,7 @@ class _ProjectListTile extends HookConsumerWidget { showConfirmAlert( 'deleteProjectHint'.tr(), 'deleteProject'.tr(), + isDanger: true, ).then((confirm) { if (confirm) { final client = ref.read(apiClientProvider); diff --git a/lib/screens/posts/post_detail.dart b/lib/screens/posts/post_detail.dart index 928c46f6..7f42f3a0 100644 --- a/lib/screens/posts/post_detail.dart +++ b/lib/screens/posts/post_detail.dart @@ -145,9 +145,11 @@ class PostActionButtons extends HookConsumerWidget { message: 'delete'.tr(), child: FilledButton.tonal( onPressed: () { - showConfirmAlert('deletePostHint'.tr(), 'deletePost'.tr()).then(( - confirm, - ) { + showConfirmAlert( + 'deletePostHint'.tr(), + 'deletePost'.tr(), + isDanger: true, + ).then((confirm) { if (confirm) { final client = ref.watch(apiClientProvider); client diff --git a/lib/screens/realm/realm_detail.dart b/lib/screens/realm/realm_detail.dart index 672b7e93..e3edb2f1 100644 --- a/lib/screens/realm/realm_detail.dart +++ b/lib/screens/realm/realm_detail.dart @@ -427,6 +427,7 @@ class _RealmActionMenu extends HookConsumerWidget { showConfirmAlert( 'deleteRealmHint'.tr(), 'deleteRealm'.tr(), + isDanger: true, ).then((confirm) { if (confirm) { final client = ref.watch(apiClientProvider); diff --git a/lib/widgets/account/account_devices.dart b/lib/widgets/account/account_devices.dart index cb14826e..4ed7289a 100644 --- a/lib/widgets/account/account_devices.dart +++ b/lib/widgets/account/account_devices.dart @@ -150,6 +150,7 @@ class AccountSessionSheet extends HookConsumerWidget { final confirm = await showConfirmAlert( 'authDeviceLogoutHint'.tr(), 'authDeviceLogout'.tr(), + isDanger: true, ); if (!confirm || !context.mounted) return; try { @@ -276,6 +277,7 @@ class AccountSessionSheet extends HookConsumerWidget { final confirm = await showConfirmAlert( 'authDeviceLogoutHint'.tr(), 'authDeviceLogout'.tr(), + isDanger: true, ); if (confirm && context.mounted) { try { diff --git a/lib/widgets/alert.dart b/lib/widgets/alert.dart index bba89abd..4249ebde 100644 --- a/lib/widgets/alert.dart +++ b/lib/widgets/alert.dart @@ -6,6 +6,7 @@ import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; import 'package:island/main.dart'; import 'package:island/talker.dart'; +import 'package:material_symbols_icons/symbols.dart'; import 'package:styled_widget/styled_widget.dart'; import 'package:top_snackbar_flutter/top_snack_bar.dart'; @@ -220,7 +221,7 @@ Future showOverlayDialog({ const kDialogMaxWidth = 480.0; -void showErrorAlert(dynamic err) { +void showErrorAlert(dynamic err, {IconData? icon}) { if (err is Error) { talker.error('Something went wrong...', err, err.stackTrace); } @@ -236,8 +237,27 @@ void showErrorAlert(dynamic err) { (context, close) => ConstrainedBox( constraints: const BoxConstraints(maxWidth: kDialogMaxWidth), child: AlertDialog( - title: Text('somethingWentWrong'.tr()), - content: Text(text), + title: null, + titlePadding: EdgeInsets.zero, + contentPadding: const EdgeInsets.fromLTRB(24, 24, 24, 0), + content: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Icon( + icon ?? Icons.error_outline_rounded, + size: 48, + color: Theme.of(context).colorScheme.error, + ), + const Gap(16), + Text( + 'somethingWentWrong'.tr(), + style: Theme.of(context).textTheme.titleLarge, + ), + const Gap(8), + Text(text), + ], + ), actions: [ TextButton( onPressed: () => close(null), @@ -249,14 +269,32 @@ void showErrorAlert(dynamic err) { ); } -void showInfoAlert(String message, String title) { +void showInfoAlert(String message, String title, {IconData? icon}) { showOverlayDialog( builder: (context, close) => ConstrainedBox( constraints: const BoxConstraints(maxWidth: kDialogMaxWidth), child: AlertDialog( - title: Text(title), - content: Text(message), + title: null, + titlePadding: EdgeInsets.zero, + contentPadding: const EdgeInsets.fromLTRB(24, 24, 24, 0), + content: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Icon( + icon ?? Symbols.info_rounded, + fill: 1, + size: 48, + color: Theme.of(context).colorScheme.primary, + ), + const Gap(16), + Text(title, style: Theme.of(context).textTheme.titleLarge), + const Gap(8), + Text(message), + const Gap(8), + ], + ), actions: [ TextButton( onPressed: () => close(null), @@ -268,14 +306,37 @@ void showInfoAlert(String message, String title) { ); } -Future showConfirmAlert(String message, String title) async { +Future showConfirmAlert( + String message, + String title, { + IconData? icon, + bool isDanger = false, +}) async { final result = await showOverlayDialog( builder: (context, close) => ConstrainedBox( constraints: const BoxConstraints(maxWidth: kDialogMaxWidth), child: AlertDialog( - title: Text(title), - content: Text(message), + title: null, + titlePadding: EdgeInsets.zero, + contentPadding: const EdgeInsets.fromLTRB(24, 24, 24, 0), + content: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Icon( + icon ?? Symbols.help_rounded, + size: 48, + fill: 1, + color: Theme.of(context).colorScheme.primary, + ), + const Gap(16), + Text(title, style: Theme.of(context).textTheme.titleLarge), + const Gap(8), + Text(message), + const Gap(8), + ], + ), actions: [ TextButton( onPressed: () => close(false), @@ -285,6 +346,12 @@ Future showConfirmAlert(String message, String title) async { ), TextButton( onPressed: () => close(true), + style: + isDanger + ? TextButton.styleFrom( + foregroundColor: Theme.of(context).colorScheme.error, + ) + : null, child: Text(MaterialLocalizations.of(context).okButtonLabel), ), ], diff --git a/lib/widgets/chat/message_item.dart b/lib/widgets/chat/message_item.dart index 47556300..2915c70e 100644 --- a/lib/widgets/chat/message_item.dart +++ b/lib/widgets/chat/message_item.dart @@ -596,6 +596,7 @@ class MessageHoverActionMenu extends StatelessWidget { final confirmed = await showConfirmAlert( 'deleteMessageConfirmation'.tr(), 'deleteMessage'.tr(), + isDanger: true, ); if (confirmed) { diff --git a/lib/widgets/file_list_view.dart b/lib/widgets/file_list_view.dart index 70fe99f8..4afd816a 100644 --- a/lib/widgets/file_list_view.dart +++ b/lib/widgets/file_list_view.dart @@ -512,6 +512,7 @@ class FileListView extends HookConsumerWidget { final confirmed = await showConfirmAlert( 'Are you sure you want to delete the selected files?', 'Delete Selected Files', + isDanger: true, ); if (!confirmed) return; if (context.mounted) { @@ -788,6 +789,7 @@ class FileListView extends HookConsumerWidget { final confirmed = await showConfirmAlert( 'confirmDeleteFile'.tr(), 'deleteFile'.tr(), + isDanger: true, ); if (!confirmed) return; @@ -1156,6 +1158,7 @@ class FileListView extends HookConsumerWidget { final confirmed = await showConfirmAlert( 'confirmDeleteFile'.tr(), 'deleteFile'.tr(), + isDanger: true, ); if (!confirmed) return; @@ -1224,6 +1227,7 @@ class FileListView extends HookConsumerWidget { final confirmed = await showConfirmAlert( 'confirmDeleteFile'.tr(), 'deleteFile'.tr(), + isDanger: true, ); if (!confirmed) return; @@ -1266,6 +1270,7 @@ class FileListView extends HookConsumerWidget { final confirmed = await showConfirmAlert( 'confirmDeleteFile'.tr(), 'deleteFile'.tr(), + isDanger: true, ); if (!confirmed) return; diff --git a/lib/widgets/post/draft_manager.dart b/lib/widgets/post/draft_manager.dart index 2dcc3047..f17b3f50 100644 --- a/lib/widgets/post/draft_manager.dart +++ b/lib/widgets/post/draft_manager.dart @@ -122,6 +122,7 @@ class DraftManagerSheet extends HookConsumerWidget { final confirmed = await showConfirmAlert( 'clearAllDraftsConfirm'.tr(), 'clearAllDrafts'.tr(), + isDanger: true, ); if (confirmed == true) { diff --git a/lib/widgets/post/post_item.dart b/lib/widgets/post/post_item.dart index e646ce64..cf9a1a58 100644 --- a/lib/widgets/post/post_item.dart +++ b/lib/widgets/post/post_item.dart @@ -197,6 +197,7 @@ class PostActionableItem extends HookConsumerWidget { showConfirmAlert( 'deletePostHint'.tr(), 'deletePost'.tr(), + isDanger: true, ).then((confirm) { if (confirm) { final client = ref.watch(apiClientProvider); diff --git a/lib/widgets/post/post_item_creator.dart b/lib/widgets/post/post_item_creator.dart index 4ddb991c..e7056ac0 100644 --- a/lib/widgets/post/post_item_creator.dart +++ b/lib/widgets/post/post_item_creator.dart @@ -69,22 +69,24 @@ class PostItemCreator extends HookConsumerWidget { title: 'delete'.tr(), image: MenuImage.icon(Symbols.delete), callback: () { - showConfirmAlert('deletePostHint'.tr(), 'deletePost'.tr()).then( - (confirm) { - if (confirm) { - final client = ref.watch(apiClientProvider); - client - .delete('/sphere/posts/${item.id}') - .catchError((err) { - showErrorAlert(err); - return err; - }) - .then((_) { - onRefresh?.call(); - }); - } - }, - ); + showConfirmAlert( + 'deletePostHint'.tr(), + 'deletePost'.tr(), + isDanger: true, + ).then((confirm) { + if (confirm) { + final client = ref.watch(apiClientProvider); + client + .delete('/sphere/posts/${item.id}') + .catchError((err) { + showErrorAlert(err); + return err; + }) + .then((_) { + onRefresh?.call(); + }); + } + }); }, ), MenuSeparator(), diff --git a/lib/widgets/sites/file_management_action_section.dart b/lib/widgets/sites/file_management_action_section.dart index f6481d57..161bb0f2 100644 --- a/lib/widgets/sites/file_management_action_section.dart +++ b/lib/widgets/sites/file_management_action_section.dart @@ -72,26 +72,10 @@ class FileManagementActionSection extends HookConsumerWidget { } Future _purgeFiles(BuildContext context, WidgetRef ref) async { - final confirmed = await showDialog( - context: context, - builder: - (context) => AlertDialog( - title: Text('confirmPurge'.tr()), - content: Text('purgeFilesConfirm'.tr()), - actions: [ - TextButton( - onPressed: () => Navigator.pop(context, false), - child: Text('cancel'.tr()), - ), - FilledButton( - onPressed: () => Navigator.pop(context, true), - style: FilledButton.styleFrom( - backgroundColor: Theme.of(context).colorScheme.error, - ), - child: Text('purgeAllFiles'.tr()), - ), - ], - ), + final confirmed = await showConfirmAlert( + 'purgeFilesConfirm'.tr(), + 'confirmPurge'.tr(), + isDanger: true, ); if (confirmed != true) return;