From 9117663c48567aead70b1f765ff467521f0ade86 Mon Sep 17 00:00:00 2001 From: Henry-Hiles Date: Thu, 15 Oct 2026 20:06:37 -0400 Subject: [PATCH] mass migrate to adaptive sheets --- .../extensions/build_event_options.dart | 29 ++--- lib/helpers/extensions/show_about_dialog.dart | 121 +++++++++--------- lib/widgets/html/mention_chip.dart | 10 +- lib/widgets/invites_list.dart | 22 ++-- .../{join_dialog.dart => join_sheet.dart} | 45 ++++--- lib/widgets/room_appbar.dart | 8 +- lib/widgets/room_list.dart | 8 +- lib/widgets/room_menu_button.dart | 72 +++++------ ...ry_dialog.dart => room_summary_sheet.dart} | 112 ++++++++-------- lib/widgets/settings/dialog_list_tile.dart | 8 +- .../{radio_dialog.dart => radio_sheet.dart} | 43 ++++--- lib/widgets/sheet_layout.dart | 11 +- lib/widgets/user_bottom_sheet.dart | 92 +++++++------ 13 files changed, 292 insertions(+), 289 deletions(-) rename lib/widgets/{join_dialog.dart => join_sheet.dart} (55%) rename lib/widgets/{room_summary_dialog.dart => room_summary_sheet.dart} (61%) rename lib/widgets/settings/{radio_dialog.dart => radio_sheet.dart} (54%) diff --git a/lib/helpers/extensions/build_event_options.dart b/lib/helpers/extensions/build_event_options.dart index 5a84d57..7bf0d35 100644 --- a/lib/helpers/extensions/build_event_options.dart +++ b/lib/helpers/extensions/build_event_options.dart @@ -1,5 +1,6 @@ import "package:fast_immutable_collections/fast_immutable_collections.dart"; import "package:material_ui/material_ui.dart"; +import "package:nexus/widgets/sheet_layout.dart"; import "package:flutter/services.dart"; import "package:flutter_hooks/flutter_hooks.dart"; import "package:hooks_riverpod/hooks_riverpod.dart"; @@ -46,27 +47,13 @@ extension BuildEventOptions on Event { required String action, required Future Function(String reason) onConfirm, }) { - showDialog( - context: context, - builder: (context) => HookBuilder( + context.showAdaptiveSheet( + (context) => HookBuilder( builder: (context) { final reasonController = useTextEditingController(); - return AlertDialog( + return SheetLayout( title: Text(title), - content: Column( - mainAxisSize: .min, - crossAxisAlignment: .start, - children: [ - Text(description), - const SizedBox(height: 12), - TextField( - controller: reasonController, - textCapitalization: .sentences, - decoration: .new(labelText: "Reason (optional)"), - ), - ], - ), actions: [ TextButton( onPressed: Navigator.of(context).pop, @@ -82,6 +69,14 @@ extension BuildEventOptions on Event { child: Text(action), ), ], + children: [ + Text(description), + TextField( + controller: reasonController, + textCapitalization: .sentences, + decoration: .new(labelText: "Reason (optional)"), + ), + ], ); }, ), diff --git a/lib/helpers/extensions/show_about_dialog.dart b/lib/helpers/extensions/show_about_dialog.dart index e684dfd..689795a 100644 --- a/lib/helpers/extensions/show_about_dialog.dart +++ b/lib/helpers/extensions/show_about_dialog.dart @@ -1,4 +1,6 @@ import "package:material_ui/material_ui.dart"; +import "package:nexus/helpers/extensions/show_adaptive_sheet.dart"; +import "package:nexus/widgets/sheet_layout.dart"; import "package:flutter_svg/flutter_svg.dart"; import "package:hooks_riverpod/hooks_riverpod.dart"; import "package:m3e_card_list/m3e_card_list.dart"; @@ -10,64 +12,8 @@ extension ShowAboutDialog on BuildContext { final packageInfo = await PackageInfo.fromPlatform(); if (mounted) { - showDialog( - context: this, - builder: (context) => AlertDialog( - content: Column( - mainAxisSize: .min, - spacing: 16, - children: [ - Row( - spacing: 12, - children: [ - SvgPicture.asset("assets/bundled/icon.svg", width: 64), - Expanded( - child: Column( - crossAxisAlignment: .start, - children: [ - Wrap( - crossAxisAlignment: .center, - spacing: 4, - children: [ - Text( - "Nexus", - style: Theme.of(context).textTheme.headlineMedium, - ), - Text( - "(${packageInfo.version}+${const String.fromEnvironment("GIT_COMMIT", defaultValue: "dev")})", - ), - ], - ), - - Text( - "A simple and user-friendly Matrix client", - overflow: .ellipsis, - style: Theme.of(context).textTheme.titleMedium, - ), - ], - ), - ), - ], - ), - M3ECardColumn( - onTap: (index) => - ref.read(LaunchHelper.provider).launchUrl(switch (index) { - 0 => .https("git.federated.nexus", "nexus/nexus"), - _ => .https("liberapay.com", "QuadRadical"), - }), - children: [ - ListTile( - leading: Icon(Icons.commit), - title: Text("Source Code"), - ), - ListTile( - leading: Icon(Icons.favorite, color: Colors.pinkAccent), - title: Text("Donate"), - ), - ], - ), - ], - ), + showAdaptiveSheet( + (context) => SheetLayout( actions: [ TextButton( onPressed: () => showLicensePage(context: context), @@ -78,6 +24,65 @@ extension ShowAboutDialog on BuildContext { child: Text("Close"), ), ], + children: [ + Column( + mainAxisSize: .min, + spacing: 16, + children: [ + Row( + spacing: 12, + children: [ + SvgPicture.asset("assets/bundled/icon.svg", width: 64), + Expanded( + child: Column( + crossAxisAlignment: .start, + children: [ + Wrap( + crossAxisAlignment: .center, + spacing: 4, + children: [ + Text( + "Nexus", + style: Theme.of(context) + .textTheme + .headlineMedium, + ), + Text( + "(${packageInfo.version}+${const String.fromEnvironment("GIT_COMMIT", defaultValue: "dev")})", + ), + ], + ), + + Text( + "A simple and user-friendly Matrix client", + overflow: .ellipsis, + style: Theme.of(context).textTheme.titleMedium, + ), + ], + ), + ), + ], + ), + M3ECardColumn( + onTap: (index) => + ref.read(LaunchHelper.provider).launchUrl(switch (index) { + 0 => .https("git.federated.nexus", "nexus/nexus"), + _ => .https("liberapay.com", "QuadRadical"), + }), + children: [ + ListTile( + leading: Icon(Icons.commit), + title: Text("Source Code"), + ), + ListTile( + leading: Icon(Icons.favorite, color: Colors.pinkAccent), + title: Text("Donate"), + ), + ], + ), + ], + ), + ], ), ); } diff --git a/lib/widgets/html/mention_chip.dart b/lib/widgets/html/mention_chip.dart index 29e2310..c6a6e5b 100644 --- a/lib/widgets/html/mention_chip.dart +++ b/lib/widgets/html/mention_chip.dart @@ -1,4 +1,5 @@ import "package:material_ui/material_ui.dart"; +import "package:nexus/helpers/extensions/show_adaptive_sheet.dart"; import "package:flutter_riverpod/flutter_riverpod.dart"; import "package:nexus/controllers/event.dart"; import "package:nexus/controllers/room_summary.dart"; @@ -8,7 +9,7 @@ import "package:nexus/helpers/extensions/get_link_info.dart"; import "package:nexus/helpers/extensions/show_user_popover.dart"; import "package:nexus/models/content/membership.dart"; import "package:nexus/models/room_summary.dart"; -import "package:nexus/widgets/room_summary_dialog.dart"; +import "package:nexus/widgets/room_summary_sheet.dart"; class const MentionChip(final String content, final String? roomId, {super.key}) extends ConsumerWidget { @@ -58,10 +59,9 @@ class const MentionChip(final String content, final String? roomId, {super.key}) if (!await ref.focusRoom(summary.roomId, eventRowId) && context.mounted) { - showDialog( - context: context, - builder: (context) => Consumer( - builder: (context, ref, _) => RoomSummaryDialog( + context.showAdaptiveSheet( + (context) => Consumer( + builder: (context, ref, _) => RoomSummarySheet( ref .watch( RoomSummaryController.provider( diff --git a/lib/widgets/invites_list.dart b/lib/widgets/invites_list.dart index 6dbb0a8..f1be8f5 100644 --- a/lib/widgets/invites_list.dart +++ b/lib/widgets/invites_list.dart @@ -1,6 +1,8 @@ import "package:flutter_hooks/flutter_hooks.dart"; import "package:hooks_riverpod/hooks_riverpod.dart"; import "package:material_ui/material_ui.dart"; +import "package:nexus/helpers/extensions/show_adaptive_sheet.dart"; +import "package:nexus/widgets/sheet_layout.dart"; import "package:nexus/controllers/invites.dart"; import "package:nexus/helpers/extensions/get_localpart.dart"; import "package:nexus/main.dart"; @@ -8,7 +10,7 @@ import "package:nexus/models/content/create.dart"; import "package:nexus/models/nav_page.dart"; import "package:nexus/models/room_summary.dart"; import "package:nexus/widgets/avatar_or_hash.dart"; -import "package:nexus/widgets/room_summary_dialog.dart"; +import "package:nexus/widgets/room_summary_sheet.dart"; class const InvitesList({super.key}) extends HookConsumerWidget @@ -33,13 +35,9 @@ class const InvitesList({super.key}) final decliningAll = useState(false); Future declineAll() async { - final confirmed = await showDialog( - context: context, - builder: (context) => AlertDialog( + final confirmed = await context.showAdaptiveSheet( + (context) => SheetLayout( title: const Text("Decline All Invites"), - content: Text( - "Are you sure you want to decline all ${invites.length} invites?", - ), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(false), @@ -50,6 +48,11 @@ class const InvitesList({super.key}) child: const Text("Decline All"), ), ], + children: [ + Text( + "Are you sure you want to decline all ${invites.length} invites?", + ), + ], ), ); if (confirmed != true) return; @@ -103,10 +106,7 @@ class const InviteCard(final RoomSummary invite, {super.key}) return Card( clipBehavior: .antiAlias, child: ListTile( - onTap: () => showDialog( - context: context, - builder: (_) => RoomSummaryDialog(invite), - ), + onTap: () => context.showAdaptiveSheet((_) => RoomSummarySheet(invite)), leading: AvatarOrHash( invite.avatarUrl, name, diff --git a/lib/widgets/join_dialog.dart b/lib/widgets/join_sheet.dart similarity index 55% rename from lib/widgets/join_dialog.dart rename to lib/widgets/join_sheet.dart index b559023..b690e9c 100644 --- a/lib/widgets/join_dialog.dart +++ b/lib/widgets/join_sheet.dart @@ -1,11 +1,13 @@ import "package:material_ui/material_ui.dart"; +import "package:nexus/helpers/extensions/show_adaptive_sheet.dart"; +import "package:nexus/widgets/sheet_layout.dart"; import "package:flutter_hooks/flutter_hooks.dart"; import "package:hooks_riverpod/hooks_riverpod.dart"; import "package:nexus/controllers/room_summary.dart"; import "package:nexus/helpers/extensions/get_link_info.dart"; -import "package:nexus/widgets/room_summary_dialog.dart"; +import "package:nexus/widgets/room_summary_sheet.dart"; -class const JoinDialog(final WidgetRef ref, {super.key}) extends HookWidget { +class const JoinSheet(final WidgetRef ref, {super.key}) extends HookWidget { @override Widget build(BuildContext context) { final roomAlias = useTextEditingController(); @@ -17,16 +19,15 @@ class const JoinDialog(final WidgetRef ref, {super.key}) extends HookWidget { final via = linkInfo?.via ?? const .empty(); final roomIdOrAlias = linkInfo?.identifier ?? roomAlias.text; - showDialog( - context: context, - builder: (context) => Consumer( + context.showAdaptiveSheet( + (context) => Consumer( builder: (context, ref, _) => switch (ref.watch( RoomSummaryController.provider( .new(roomIdOrAlias: roomIdOrAlias, via: via), ), )) { - AsyncData(:final value) => RoomSummaryDialog(value, via: via), - AsyncError _ || AsyncLoading _ => RoomSummaryDialog( + AsyncData(:final value) => RoomSummarySheet(value, via: via), + AsyncError _ || AsyncLoading _ => RoomSummarySheet( .new(roomId: roomIdOrAlias), via: via, ), @@ -35,25 +36,27 @@ class const JoinDialog(final WidgetRef ref, {super.key}) extends HookWidget { ); } - return AlertDialog( + return SheetLayout( title: Text("Join a Room"), - content: Column( - mainAxisSize: .min, - crossAxisAlignment: .start, - children: [ - Text("Enter the room alias, Matrix URI, or Matrix.to link."), - SizedBox(height: 12), - TextField( - controller: roomAlias, - decoration: .new(hintText: "#room:server.name"), - onEditingComplete: onJoin, - ), - ], - ), actions: [ TextButton(onPressed: Navigator.of(context).pop, child: Text("Cancel")), TextButton(onPressed: onJoin, child: Text("Join")), ], + children: [ + Column( + mainAxisSize: .min, + crossAxisAlignment: .start, + children: [ + Text("Enter the room alias, Matrix URI, or Matrix.to link."), + SizedBox(height: 12), + TextField( + controller: roomAlias, + decoration: .new(hintText: "#room:server.name"), + onEditingComplete: onJoin, + ), + ], + ), + ], ); } } diff --git a/lib/widgets/room_appbar.dart b/lib/widgets/room_appbar.dart index 6bbe2a3..bcd4108 100644 --- a/lib/widgets/room_appbar.dart +++ b/lib/widgets/room_appbar.dart @@ -1,11 +1,12 @@ import "package:material_ui/material_ui.dart"; +import "package:nexus/helpers/extensions/show_adaptive_sheet.dart"; import "package:hooks_riverpod/hooks_riverpod.dart"; import "package:nexus/controllers/rooms.dart"; import "package:nexus/widgets/appbar.dart"; import "package:nexus/widgets/avatar_or_hash.dart"; import "package:nexus/widgets/count_badge.dart"; import "package:nexus/widgets/room_menu_button.dart"; -import "package:nexus/widgets/room_summary_dialog.dart"; +import "package:nexus/widgets/room_summary_sheet.dart"; import "package:nexus/widgets/sidebar.dart"; final class const RoomAppbar({ @@ -28,9 +29,8 @@ final class const RoomAppbar({ return Appbar( onTap: room?.metadata == null ? null - : () => showDialog( - context: context, - builder: (context) => RoomSummaryDialog( + : () => context.showAdaptiveSheet( + (context) => RoomSummarySheet( .new( roomId: room!.metadata!.id, joinedMembers: diff --git a/lib/widgets/room_list.dart b/lib/widgets/room_list.dart index 3d75339..9f00530 100644 --- a/lib/widgets/room_list.dart +++ b/lib/widgets/room_list.dart @@ -14,7 +14,7 @@ import "package:nexus/widgets/start_dm_sheet.dart"; import "package:nexus/widgets/divider_widget.dart"; import "package:nexus/widgets/error_dialog.dart"; import "package:nexus/widgets/pages/settings.dart"; -import "package:nexus/widgets/join_dialog.dart"; +import "package:nexus/widgets/join_sheet.dart"; import "package:nexus/widgets/room_menu_button.dart"; import "package:nexus/controllers/key.dart"; import "package:nexus/controllers/rooms.dart"; @@ -157,10 +157,8 @@ class const SpacesRail({super.key}) extends ConsumerWidget { tooltip: "Add a room", itemBuilder: (_) => [ PopupMenuItem( - onTap: () => showDialog( - context: context, - builder: (_) => JoinDialog(ref), - ), + onTap: () => + context.showAdaptiveSheet((_) => JoinSheet(ref)), child: const ListTile( title: Text("Join an existing room (or space)"), leading: Icon(Icons.numbers), diff --git a/lib/widgets/room_menu_button.dart b/lib/widgets/room_menu_button.dart index d6a61b8..a0647bf 100644 --- a/lib/widgets/room_menu_button.dart +++ b/lib/widgets/room_menu_button.dart @@ -1,5 +1,7 @@ import "package:fast_immutable_collections/fast_immutable_collections.dart"; import "package:material_ui/material_ui.dart"; +import "package:nexus/helpers/extensions/show_adaptive_sheet.dart"; +import "package:nexus/widgets/sheet_layout.dart"; import "package:flutter/services.dart"; import "package:flutter_riverpod/flutter_riverpod.dart"; import "package:nexus/controllers/client.dart"; @@ -50,9 +52,8 @@ final class const RoomMenuButton( ), ), PopupMenuItem( - onTap: () => showDialog( - context: context, - builder: (context) => HookBuilder( + onTap: () => context.showAdaptiveSheet( + (context) => HookBuilder( builder: (context) { final userIdController = useTextEditingController(); final reasonController = useTextEditingController(); @@ -75,35 +76,8 @@ final class const RoomMenuButton( .onError(showError); } - return AlertDialog( + return SheetLayout( title: Text("Invite User"), - content: Column( - mainAxisSize: .min, - spacing: 12, - children: [ - TextField( - controller: userIdController, - autofocus: true, - keyboardType: .emailAddress, - onSubmitted: (_) => submit(), - decoration: .new( - labelText: "User ID", - hintText: "@user:example.com", - errorText: text.isEmpty || valid - ? null - : "Enter a valid user ID", - ), - ), - TextField( - controller: reasonController, - textCapitalization: .sentences, - onSubmitted: (_) => submit(), - decoration: const .new( - labelText: "Invite reason (optional)", - ), - ), - ], - ), actions: [ TextButton( onPressed: Navigator.of(context).pop, @@ -114,6 +88,29 @@ final class const RoomMenuButton( child: Text("Invite"), ), ], + children: [ + TextField( + controller: userIdController, + autofocus: true, + keyboardType: .emailAddress, + onSubmitted: (_) => submit(), + decoration: .new( + labelText: "User ID", + hintText: "@user:example.com", + errorText: text.isEmpty || valid + ? null + : "Enter a valid user ID", + ), + ), + TextField( + controller: reasonController, + textCapitalization: .sentences, + onSubmitted: (_) => submit(), + decoration: const .new( + labelText: "Invite reason (optional)", + ), + ), + ], ); }, ), @@ -124,13 +121,9 @@ final class const RoomMenuButton( ), ), PopupMenuItem( - onTap: () => showDialog( - context: context, - builder: (context) => AlertDialog( + onTap: () => context.showAdaptiveSheet( + (context) => SheetLayout( title: Text("Leave Room"), - content: Text( - "Are you sure you want to leave \"${room!.metadata?.name ?? "Unnamed Room"}\"?", - ), actions: [ TextButton( onPressed: Navigator.of(context).pop, @@ -154,6 +147,11 @@ final class const RoomMenuButton( child: Text("Leave"), ), ], + children: [ + Text( + "Are you sure you want to leave \"${room!.metadata?.name ?? "Unnamed Room"}\"?", + ), + ], ), ), child: ListTile( diff --git a/lib/widgets/room_summary_dialog.dart b/lib/widgets/room_summary_sheet.dart similarity index 61% rename from lib/widgets/room_summary_dialog.dart rename to lib/widgets/room_summary_sheet.dart index b892f5d..bfc14c7 100644 --- a/lib/widgets/room_summary_dialog.dart +++ b/lib/widgets/room_summary_sheet.dart @@ -1,6 +1,7 @@ import "package:fast_immutable_collections/fast_immutable_collections.dart"; import "package:flutter_riverpod/flutter_riverpod.dart"; import "package:material_ui/material_ui.dart"; +import "package:nexus/widgets/sheet_layout.dart"; import "package:nexus/controllers/invites.dart"; import "package:nexus/helpers/extensions/get_localpart.dart"; import "package:nexus/helpers/extensions/join_room.dart"; @@ -10,7 +11,7 @@ import "package:nexus/widgets/expandable_image.dart"; import "package:nexus/widgets/linkified_text.dart"; import "package:nexus/models/room_summary.dart"; -class const RoomSummaryDialog( +class const RoomSummarySheet( final RoomSummary summary, { final IList? via, super.key, @@ -21,10 +22,7 @@ class const RoomSummaryDialog( final isInvite = summary.membership == .invite; final colorScheme = Theme.of(context).colorScheme; - return AlertDialog( - constraints: .loose(.fromWidth(400)), - scrollable: true, - contentPadding: EdgeInsets.all(24).copyWith(top: 8), + return SheetLayout( title: Row( spacing: 12, mainAxisSize: .min, @@ -48,56 +46,6 @@ class const RoomSummaryDialog( ), ], ), - content: Column( - children: [ - if (summary.invitedBy case final invitedBy?) - ListTile( - leading: AvatarOrHash( - summary.inviterProfile?.avatarUrl, - summary.inviterProfile?.displayName ?? invitedBy.localpart, - ), - title: Text( - "${summary.isDirect ? "Direct message from" : "Invited by"} ${summary.inviterProfile?.displayName ?? invitedBy.localpart}", - ), - subtitle: SelectableText(invitedBy), - ), - if (summary.inviteReason case final reason?) - ListTile( - titleAlignment: ListTileTitleAlignment.titleHeight, - leading: const Icon(Icons.format_quote), - title: const Text("Invite reason"), - subtitle: SelectableText(reason), - ), - // Invite state never includes the member count - if (summary.joinedMembers != null || !isInvite) - ListTile( - title: Text( - "${summary.joinedMembers ?? "Unknown number of"} members", - ), - leading: Icon(Icons.people), - ), - ListTile( - title: Text( - summary.canonicalAlias == null ? "Room ID" : "Canonical Alias", - ), - subtitle: SelectableText(summary.canonicalAlias ?? summary.roomId), - leading: Icon(Icons.numbers), - ), - if (summary.topic != null) - ListTile( - title: Text("Topic"), - subtitle: LinkifiedText(summary.topic!), - leading: Icon(Icons.info), - ), - if (summary.encryption != null) - ListTile(title: Text("Encrypted"), leading: Icon(Icons.lock)), - if (summary.worldReadable == true) - ListTile( - title: Text("Anyone can read room history"), - leading: Icon(Icons.public), - ), - ], - ), actions: [ TextButton(onPressed: Navigator.of(context).pop, child: Text("Cancel")), if (isInvite) ...[ @@ -146,6 +94,60 @@ class const RoomSummaryDialog( child: Text("Join"), ), ], + children: [ + Column( + children: [ + if (summary.invitedBy case final invitedBy?) + ListTile( + leading: AvatarOrHash( + summary.inviterProfile?.avatarUrl, + summary.inviterProfile?.displayName ?? invitedBy.localpart, + ), + title: Text( + "${summary.isDirect ? "Direct message from" : "Invited by"} ${summary.inviterProfile?.displayName ?? invitedBy.localpart}", + ), + subtitle: SelectableText(invitedBy), + ), + if (summary.inviteReason case final reason?) + ListTile( + titleAlignment: ListTileTitleAlignment.titleHeight, + leading: const Icon(Icons.format_quote), + title: const Text("Invite reason"), + subtitle: SelectableText(reason), + ), + // Invite state never includes the member count + if (summary.joinedMembers != null || !isInvite) + ListTile( + title: Text( + "${summary.joinedMembers ?? "Unknown number of"} members", + ), + leading: Icon(Icons.people), + ), + ListTile( + title: Text( + summary.canonicalAlias == null ? "Room ID" : "Canonical Alias", + ), + subtitle: SelectableText( + summary.canonicalAlias ?? summary.roomId, + ), + leading: Icon(Icons.numbers), + ), + if (summary.topic != null) + ListTile( + title: Text("Topic"), + subtitle: LinkifiedText(summary.topic!), + leading: Icon(Icons.info), + ), + if (summary.encryption != null) + ListTile(title: Text("Encrypted"), leading: Icon(Icons.lock)), + if (summary.worldReadable == true) + ListTile( + title: Text("Anyone can read room history"), + leading: Icon(Icons.public), + ), + ], + ), + ], ); } } diff --git a/lib/widgets/settings/dialog_list_tile.dart b/lib/widgets/settings/dialog_list_tile.dart index e66e335..afa79f3 100644 --- a/lib/widgets/settings/dialog_list_tile.dart +++ b/lib/widgets/settings/dialog_list_tile.dart @@ -1,6 +1,7 @@ import "package:material_ui/material_ui.dart"; +import "package:nexus/helpers/extensions/show_adaptive_sheet.dart"; import "package:flutter_riverpod/flutter_riverpod.dart"; -import "package:nexus/widgets/settings/radio_dialog.dart"; +import "package:nexus/widgets/settings/radio_sheet.dart"; final class const DialogListTile({ required final T? initialValue, @@ -26,9 +27,8 @@ final class const DialogListTile({ ), child: ListTile( enabled: onChanged != null, - onTap: () => showDialog( - context: context, - builder: (context) => RadioDialog( + onTap: () => context.showAdaptiveSheet( + (context) => RadioSheet( title: title, getName: getName, onChanged: onChanged == null diff --git a/lib/widgets/settings/radio_dialog.dart b/lib/widgets/settings/radio_sheet.dart similarity index 54% rename from lib/widgets/settings/radio_dialog.dart rename to lib/widgets/settings/radio_sheet.dart index 6696139..051b67a 100644 --- a/lib/widgets/settings/radio_dialog.dart +++ b/lib/widgets/settings/radio_sheet.dart @@ -1,7 +1,8 @@ import "package:material_ui/material_ui.dart"; +import "package:nexus/widgets/sheet_layout.dart"; import "package:flutter_hooks/flutter_hooks.dart"; -final class const RadioDialog({ +final class const RadioSheet({ required final T? value, required final String title, required final List options, @@ -12,26 +13,8 @@ final class const RadioDialog({ @override Widget build(BuildContext context) { final mutValue = useState(null); - return AlertDialog( + return SheetLayout( title: Text(title), - content: RadioGroup( - groupValue: mutValue.value ?? value, - onChanged: (value) => mutValue.value = value ?? mutValue.value, - - child: Column( - mainAxisSize: MainAxisSize.min, - children: options - .map( - (option) => RadioListTile( - enabled: onChanged != null, - value: option, - title: Text(getName(option)), - dense: true, - ), - ) - .toList(), - ), - ), actions: [ TextButton(onPressed: Navigator.of(context).pop, child: Text("Cancel")), if (onChanged != null) @@ -43,6 +26,26 @@ final class const RadioDialog({ child: Text("OK"), ), ], + children: [ + RadioGroup( + groupValue: mutValue.value ?? value, + onChanged: (value) => mutValue.value = value ?? mutValue.value, + + child: Column( + mainAxisSize: MainAxisSize.min, + children: options + .map( + (option) => RadioListTile( + enabled: onChanged != null, + value: option, + title: Text(getName(option)), + dense: true, + ), + ) + .toList(), + ), + ), + ], ); } } diff --git a/lib/widgets/sheet_layout.dart b/lib/widgets/sheet_layout.dart index 40f9e15..0ce98d1 100644 --- a/lib/widgets/sheet_layout.dart +++ b/lib/widgets/sheet_layout.dart @@ -1,7 +1,7 @@ import "package:material_ui/material_ui.dart"; class const SheetLayout({ - required final Widget title, + final Widget? title, required final List children, required final List actions, super.key, @@ -16,10 +16,11 @@ class const SheetLayout({ crossAxisAlignment: .stretch, spacing: 12, children: [ - DefaultTextStyle( - style: Theme.of(context).textTheme.headlineSmall!, - child: title, - ), + if (title case final title?) + DefaultTextStyle( + style: Theme.of(context).textTheme.headlineSmall!, + child: title, + ), ...children, Row(mainAxisAlignment: .end, spacing: 8, children: actions), ], diff --git a/lib/widgets/user_bottom_sheet.dart b/lib/widgets/user_bottom_sheet.dart index 4e654a3..ad2c511 100644 --- a/lib/widgets/user_bottom_sheet.dart +++ b/lib/widgets/user_bottom_sheet.dart @@ -1,5 +1,6 @@ import "package:collection/collection.dart"; import "package:material_ui/material_ui.dart"; +import "package:nexus/widgets/sheet_layout.dart"; import "package:flutter_hooks/flutter_hooks.dart"; import "package:flutter_riverpod/flutter_riverpod.dart"; import "package:intl/intl.dart"; @@ -35,54 +36,51 @@ final class const UserBottomSheet( ); final client = ref.read(ClientController.provider.notifier); - void showMembershipDialog(MembershipAction action) => showDialog( - context: context, - builder: (context) => HookBuilder( - builder: (context) { - final actionReasonController = useTextEditingController(); - return AlertDialog( - title: Text("${toBeginningOfSentenceCase(action.name)} $userId"), - content: Column( - mainAxisSize: .min, - crossAxisAlignment: .start, - children: [ - Text("Are you sure you want to ${action.name} $userId?"), - SizedBox(height: 12), - TextField( - textCapitalization: .sentences, - controller: actionReasonController, - decoration: .new( - labelText: "Reason for ${action.name} (optional)", - ), + void showMembershipDialog(MembershipAction action) => + context.showAdaptiveSheet( + (context) => HookBuilder( + builder: (context) { + final actionReasonController = useTextEditingController(); + return SheetLayout( + title: Text( + "${toBeginningOfSentenceCase(action.name)} $userId", ), - ], - ), - actions: [ - TextButton( - onPressed: Navigator.of(context).pop, - child: Text("Cancel"), - ), - TextButton( - onPressed: () { - Navigator.of(context).pop(); - client - .setMembership( - .new( - userId: userId, - roomId: roomId!, - action: action, - reason: actionReasonController.text, - ), - ) - .onError(showError); - }, - child: Text(toBeginningOfSentenceCase(action.name)), - ), - ], - ); - }, - ), - ); + actions: [ + TextButton( + onPressed: Navigator.of(context).pop, + child: Text("Cancel"), + ), + TextButton( + onPressed: () { + Navigator.of(context).pop(); + client + .setMembership( + .new( + userId: userId, + roomId: roomId!, + action: action, + reason: actionReasonController.text, + ), + ) + .onError(showError); + }, + child: Text(toBeginningOfSentenceCase(action.name)), + ), + ], + children: [ + Text("Are you sure you want to ${action.name} $userId?"), + TextField( + textCapitalization: .sentences, + controller: actionReasonController, + decoration: .new( + labelText: "Reason for ${action.name} (optional)", + ), + ), + ], + ); + }, + ), + ); return SingleChildScrollView( child: Padding(