♻️ Dangerous confirm dialog variant

This commit is contained in:
2025-11-24 23:13:35 +08:00
parent b20d8350a8
commit 88c8227c66
24 changed files with 144 additions and 61 deletions

View File

@@ -62,6 +62,7 @@ class AccountSettingsScreen extends HookConsumerWidget {
final confirm = await showConfirmAlert( final confirm = await showConfirmAlert(
'accountDeletionHint'.tr(), 'accountDeletionHint'.tr(),
'accountDeletion'.tr(), 'accountDeletion'.tr(),
isDanger: true,
); );
if (!confirm || !context.mounted) return; if (!confirm || !context.mounted) return;
try { try {

View File

@@ -26,6 +26,7 @@ class AuthFactorSheet extends HookConsumerWidget {
final confirm = await showConfirmAlert( final confirm = await showConfirmAlert(
'authFactorDeleteHint'.tr(), 'authFactorDeleteHint'.tr(),
'authFactorDelete'.tr(), 'authFactorDelete'.tr(),
isDanger: true,
); );
if (!confirm || !context.mounted) return; if (!confirm || !context.mounted) return;
try { try {

View File

@@ -82,6 +82,7 @@ class AccountConnectionSheet extends HookConsumerWidget {
final confirm = await showConfirmAlert( final confirm = await showConfirmAlert(
'accountConnectionDeleteHint'.tr(), 'accountConnectionDeleteHint'.tr(),
'accountConnectionDelete'.tr(), 'accountConnectionDelete'.tr(),
isDanger: true,
); );
if (!confirm || !context.mounted) return; if (!confirm || !context.mounted) return;
try { try {
@@ -332,6 +333,7 @@ class AccountConnectionsSheet extends HookConsumerWidget {
final confirm = await showConfirmAlert( final confirm = await showConfirmAlert(
'accountConnectionDeleteHint'.tr(), 'accountConnectionDeleteHint'.tr(),
'accountConnectionDelete'.tr(), 'accountConnectionDelete'.tr(),
isDanger: true,
); );
if (confirm && context.mounted) { if (confirm && context.mounted) {
try { try {

View File

@@ -20,6 +20,7 @@ class ContactMethodSheet extends HookConsumerWidget {
final confirm = await showConfirmAlert( final confirm = await showConfirmAlert(
'contactMethodDeleteHint'.tr(), 'contactMethodDeleteHint'.tr(),
'contactMethodDelete'.tr(), 'contactMethodDelete'.tr(),
isDanger: true,
); );
if (!confirm || !context.mounted) return; if (!confirm || !context.mounted) return;
try { try {

View File

@@ -487,6 +487,7 @@ class _ChatRoomActionMenu extends HookConsumerWidget {
showConfirmAlert( showConfirmAlert(
'deleteChatRoomHint'.tr(), 'deleteChatRoomHint'.tr(),
'deleteChatRoom'.tr(), 'deleteChatRoom'.tr(),
isDanger: true,
).then((confirm) async { ).then((confirm) async {
if (confirm) { if (confirm) {
final client = ref.watch(apiClientProvider); final client = ref.watch(apiClientProvider);

View File

@@ -304,16 +304,18 @@ class CreatorHubScreen extends HookConsumerWidget {
} }
void deletePublisher() { void deletePublisher() {
showConfirmAlert('deletePublisherHint'.tr(), 'deletePublisher'.tr()).then( showConfirmAlert(
(confirm) { 'deletePublisherHint'.tr(),
if (confirm) { 'deletePublisher'.tr(),
final client = ref.watch(apiClientProvider); isDanger: true,
client.delete('/sphere/publishers/${currentPublisher.value!.name}'); ).then((confirm) {
ref.invalidate(publishersManagedProvider); if (confirm) {
currentPublisher.value = null; final client = ref.watch(apiClientProvider);
} client.delete('/sphere/publishers/${currentPublisher.value!.name}');
}, ref.invalidate(publishersManagedProvider);
); currentPublisher.value = null;
}
});
} }
final List<DropdownMenuItem<SnPublisher>> publishersMenu = publishers.when( final List<DropdownMenuItem<SnPublisher>> publishersMenu = publishers.when(

View File

@@ -190,6 +190,7 @@ class SiteForm extends HookConsumerWidget {
final confirmed = await showConfirmAlert( final confirmed = await showConfirmAlert(
'publicationSiteDeleteConfirm'.tr(), 'publicationSiteDeleteConfirm'.tr(),
'deletePublicationSite'.tr(), 'deletePublicationSite'.tr(),
isDanger: true,
); );
if (confirmed != true) return; if (confirmed != true) return;

View File

@@ -288,6 +288,7 @@ class StickerPackActionMenu extends HookConsumerWidget {
showConfirmAlert( showConfirmAlert(
'deleteStickerPackHint'.tr(), 'deleteStickerPackHint'.tr(),
'deleteStickerPack'.tr(), 'deleteStickerPack'.tr(),
isDanger: true,
).then((confirm) { ).then((confirm) {
if (confirm) { if (confirm) {
final client = ref.watch(apiClientProvider); final client = ref.watch(apiClientProvider);

View File

@@ -70,6 +70,7 @@ class WebfeedForm extends HookConsumerWidget {
final confirmed = await showConfirmAlert( final confirmed = await showConfirmAlert(
'Are you sure you want to delete this web feed? This action cannot be undone.', 'Are you sure you want to delete this web feed? This action cannot be undone.',
'Delete Web Feed', 'Delete Web Feed',
isDanger: true,
); );
if (confirmed != true) return; if (confirmed != true) return;

View File

@@ -211,6 +211,7 @@ class AppSecretsScreen extends HookConsumerWidget {
showConfirmAlert( showConfirmAlert(
'deleteSecretHint'.tr(), 'deleteSecretHint'.tr(),
'deleteSecret'.tr(), 'deleteSecret'.tr(),
isDanger: true,
).then((confirm) { ).then((confirm) {
if (confirm) { if (confirm) {
final client = ref.read(apiClientProvider); final client = ref.read(apiClientProvider);

View File

@@ -231,6 +231,7 @@ class CustomAppsScreen extends HookConsumerWidget {
showConfirmAlert( showConfirmAlert(
'deleteCustomAppHint'.tr(), 'deleteCustomAppHint'.tr(),
'deleteCustomApp'.tr(), 'deleteCustomApp'.tr(),
isDanger: true,
).then((confirm) { ).then((confirm) {
if (confirm) { if (confirm) {
final client = ref.read( final client = ref.read(

View File

@@ -159,9 +159,11 @@ class BotKeysScreen extends HookConsumerWidget {
} }
void revokeKey(String keyId) { void revokeKey(String keyId) {
showConfirmAlert('revokeBotKeyHint'.tr(), 'revokeBotKey'.tr()).then(( showConfirmAlert(
confirm, 'revokeBotKeyHint'.tr(),
) { 'revokeBotKey'.tr(),
isDanger: true,
).then((confirm) {
if (confirm) { if (confirm) {
final client = ref.read(apiClientProvider); final client = ref.read(apiClientProvider);
client client

View File

@@ -172,6 +172,7 @@ class BotsScreen extends HookConsumerWidget {
showConfirmAlert( showConfirmAlert(
'deleteBotHint'.tr(), 'deleteBotHint'.tr(),
'deleteBot'.tr(), 'deleteBot'.tr(),
isDanger: true,
).then((confirm) { ).then((confirm) {
if (confirm) { if (confirm) {
final client = ref.read(apiClientProvider); final client = ref.read(apiClientProvider);

View File

@@ -631,6 +631,7 @@ class _ProjectListTile extends HookConsumerWidget {
showConfirmAlert( showConfirmAlert(
'deleteProjectHint'.tr(), 'deleteProjectHint'.tr(),
'deleteProject'.tr(), 'deleteProject'.tr(),
isDanger: true,
).then((confirm) { ).then((confirm) {
if (confirm) { if (confirm) {
final client = ref.read(apiClientProvider); final client = ref.read(apiClientProvider);

View File

@@ -145,9 +145,11 @@ class PostActionButtons extends HookConsumerWidget {
message: 'delete'.tr(), message: 'delete'.tr(),
child: FilledButton.tonal( child: FilledButton.tonal(
onPressed: () { onPressed: () {
showConfirmAlert('deletePostHint'.tr(), 'deletePost'.tr()).then(( showConfirmAlert(
confirm, 'deletePostHint'.tr(),
) { 'deletePost'.tr(),
isDanger: true,
).then((confirm) {
if (confirm) { if (confirm) {
final client = ref.watch(apiClientProvider); final client = ref.watch(apiClientProvider);
client client

View File

@@ -427,6 +427,7 @@ class _RealmActionMenu extends HookConsumerWidget {
showConfirmAlert( showConfirmAlert(
'deleteRealmHint'.tr(), 'deleteRealmHint'.tr(),
'deleteRealm'.tr(), 'deleteRealm'.tr(),
isDanger: true,
).then((confirm) { ).then((confirm) {
if (confirm) { if (confirm) {
final client = ref.watch(apiClientProvider); final client = ref.watch(apiClientProvider);

View File

@@ -150,6 +150,7 @@ class AccountSessionSheet extends HookConsumerWidget {
final confirm = await showConfirmAlert( final confirm = await showConfirmAlert(
'authDeviceLogoutHint'.tr(), 'authDeviceLogoutHint'.tr(),
'authDeviceLogout'.tr(), 'authDeviceLogout'.tr(),
isDanger: true,
); );
if (!confirm || !context.mounted) return; if (!confirm || !context.mounted) return;
try { try {
@@ -276,6 +277,7 @@ class AccountSessionSheet extends HookConsumerWidget {
final confirm = await showConfirmAlert( final confirm = await showConfirmAlert(
'authDeviceLogoutHint'.tr(), 'authDeviceLogoutHint'.tr(),
'authDeviceLogout'.tr(), 'authDeviceLogout'.tr(),
isDanger: true,
); );
if (confirm && context.mounted) { if (confirm && context.mounted) {
try { try {

View File

@@ -6,6 +6,7 @@ import 'package:flutter/material.dart';
import 'package:gap/gap.dart'; import 'package:gap/gap.dart';
import 'package:island/main.dart'; import 'package:island/main.dart';
import 'package:island/talker.dart'; import 'package:island/talker.dart';
import 'package:material_symbols_icons/symbols.dart';
import 'package:styled_widget/styled_widget.dart'; import 'package:styled_widget/styled_widget.dart';
import 'package:top_snackbar_flutter/top_snack_bar.dart'; import 'package:top_snackbar_flutter/top_snack_bar.dart';
@@ -220,7 +221,7 @@ Future<T?> showOverlayDialog<T>({
const kDialogMaxWidth = 480.0; const kDialogMaxWidth = 480.0;
void showErrorAlert(dynamic err) { void showErrorAlert(dynamic err, {IconData? icon}) {
if (err is Error) { if (err is Error) {
talker.error('Something went wrong...', err, err.stackTrace); talker.error('Something went wrong...', err, err.stackTrace);
} }
@@ -236,8 +237,27 @@ void showErrorAlert(dynamic err) {
(context, close) => ConstrainedBox( (context, close) => ConstrainedBox(
constraints: const BoxConstraints(maxWidth: kDialogMaxWidth), constraints: const BoxConstraints(maxWidth: kDialogMaxWidth),
child: AlertDialog( child: AlertDialog(
title: Text('somethingWentWrong'.tr()), title: null,
content: Text(text), 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: [ actions: [
TextButton( TextButton(
onPressed: () => close(null), 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<void>( showOverlayDialog<void>(
builder: builder:
(context, close) => ConstrainedBox( (context, close) => ConstrainedBox(
constraints: const BoxConstraints(maxWidth: kDialogMaxWidth), constraints: const BoxConstraints(maxWidth: kDialogMaxWidth),
child: AlertDialog( child: AlertDialog(
title: Text(title), title: null,
content: Text(message), 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: [ actions: [
TextButton( TextButton(
onPressed: () => close(null), onPressed: () => close(null),
@@ -268,14 +306,37 @@ void showInfoAlert(String message, String title) {
); );
} }
Future<bool> showConfirmAlert(String message, String title) async { Future<bool> showConfirmAlert(
String message,
String title, {
IconData? icon,
bool isDanger = false,
}) async {
final result = await showOverlayDialog<bool>( final result = await showOverlayDialog<bool>(
builder: builder:
(context, close) => ConstrainedBox( (context, close) => ConstrainedBox(
constraints: const BoxConstraints(maxWidth: kDialogMaxWidth), constraints: const BoxConstraints(maxWidth: kDialogMaxWidth),
child: AlertDialog( child: AlertDialog(
title: Text(title), title: null,
content: Text(message), 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: [ actions: [
TextButton( TextButton(
onPressed: () => close(false), onPressed: () => close(false),
@@ -285,6 +346,12 @@ Future<bool> showConfirmAlert(String message, String title) async {
), ),
TextButton( TextButton(
onPressed: () => close(true), onPressed: () => close(true),
style:
isDanger
? TextButton.styleFrom(
foregroundColor: Theme.of(context).colorScheme.error,
)
: null,
child: Text(MaterialLocalizations.of(context).okButtonLabel), child: Text(MaterialLocalizations.of(context).okButtonLabel),
), ),
], ],

View File

@@ -596,6 +596,7 @@ class MessageHoverActionMenu extends StatelessWidget {
final confirmed = await showConfirmAlert( final confirmed = await showConfirmAlert(
'deleteMessageConfirmation'.tr(), 'deleteMessageConfirmation'.tr(),
'deleteMessage'.tr(), 'deleteMessage'.tr(),
isDanger: true,
); );
if (confirmed) { if (confirmed) {

View File

@@ -512,6 +512,7 @@ class FileListView extends HookConsumerWidget {
final confirmed = await showConfirmAlert( final confirmed = await showConfirmAlert(
'Are you sure you want to delete the selected files?', 'Are you sure you want to delete the selected files?',
'Delete Selected Files', 'Delete Selected Files',
isDanger: true,
); );
if (!confirmed) return; if (!confirmed) return;
if (context.mounted) { if (context.mounted) {
@@ -788,6 +789,7 @@ class FileListView extends HookConsumerWidget {
final confirmed = await showConfirmAlert( final confirmed = await showConfirmAlert(
'confirmDeleteFile'.tr(), 'confirmDeleteFile'.tr(),
'deleteFile'.tr(), 'deleteFile'.tr(),
isDanger: true,
); );
if (!confirmed) return; if (!confirmed) return;
@@ -1156,6 +1158,7 @@ class FileListView extends HookConsumerWidget {
final confirmed = await showConfirmAlert( final confirmed = await showConfirmAlert(
'confirmDeleteFile'.tr(), 'confirmDeleteFile'.tr(),
'deleteFile'.tr(), 'deleteFile'.tr(),
isDanger: true,
); );
if (!confirmed) return; if (!confirmed) return;
@@ -1224,6 +1227,7 @@ class FileListView extends HookConsumerWidget {
final confirmed = await showConfirmAlert( final confirmed = await showConfirmAlert(
'confirmDeleteFile'.tr(), 'confirmDeleteFile'.tr(),
'deleteFile'.tr(), 'deleteFile'.tr(),
isDanger: true,
); );
if (!confirmed) return; if (!confirmed) return;
@@ -1266,6 +1270,7 @@ class FileListView extends HookConsumerWidget {
final confirmed = await showConfirmAlert( final confirmed = await showConfirmAlert(
'confirmDeleteFile'.tr(), 'confirmDeleteFile'.tr(),
'deleteFile'.tr(), 'deleteFile'.tr(),
isDanger: true,
); );
if (!confirmed) return; if (!confirmed) return;

View File

@@ -122,6 +122,7 @@ class DraftManagerSheet extends HookConsumerWidget {
final confirmed = await showConfirmAlert( final confirmed = await showConfirmAlert(
'clearAllDraftsConfirm'.tr(), 'clearAllDraftsConfirm'.tr(),
'clearAllDrafts'.tr(), 'clearAllDrafts'.tr(),
isDanger: true,
); );
if (confirmed == true) { if (confirmed == true) {

View File

@@ -197,6 +197,7 @@ class PostActionableItem extends HookConsumerWidget {
showConfirmAlert( showConfirmAlert(
'deletePostHint'.tr(), 'deletePostHint'.tr(),
'deletePost'.tr(), 'deletePost'.tr(),
isDanger: true,
).then((confirm) { ).then((confirm) {
if (confirm) { if (confirm) {
final client = ref.watch(apiClientProvider); final client = ref.watch(apiClientProvider);

View File

@@ -69,22 +69,24 @@ class PostItemCreator extends HookConsumerWidget {
title: 'delete'.tr(), title: 'delete'.tr(),
image: MenuImage.icon(Symbols.delete), image: MenuImage.icon(Symbols.delete),
callback: () { callback: () {
showConfirmAlert('deletePostHint'.tr(), 'deletePost'.tr()).then( showConfirmAlert(
(confirm) { 'deletePostHint'.tr(),
if (confirm) { 'deletePost'.tr(),
final client = ref.watch(apiClientProvider); isDanger: true,
client ).then((confirm) {
.delete('/sphere/posts/${item.id}') if (confirm) {
.catchError((err) { final client = ref.watch(apiClientProvider);
showErrorAlert(err); client
return err; .delete('/sphere/posts/${item.id}')
}) .catchError((err) {
.then((_) { showErrorAlert(err);
onRefresh?.call(); return err;
}); })
} .then((_) {
}, onRefresh?.call();
); });
}
});
}, },
), ),
MenuSeparator(), MenuSeparator(),

View File

@@ -72,26 +72,10 @@ class FileManagementActionSection extends HookConsumerWidget {
} }
Future<void> _purgeFiles(BuildContext context, WidgetRef ref) async { Future<void> _purgeFiles(BuildContext context, WidgetRef ref) async {
final confirmed = await showDialog<bool>( final confirmed = await showConfirmAlert(
context: context, 'purgeFilesConfirm'.tr(),
builder: 'confirmPurge'.tr(),
(context) => AlertDialog( isDanger: true,
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()),
),
],
),
); );
if (confirmed != true) return; if (confirmed != true) return;