mass migrate to adaptive sheets

This commit is contained in:
Henry Hiles 2026-10-15 20:06:37 -04:00
commit 9117663c48
Signed by: Henry-Hiles
SSH key fingerprint: SHA256:VKQUdS31Q90KvX7EkKMHMBpUspcmItAh86a+v7PGiIs
13 changed files with 292 additions and 289 deletions

View file

@ -1,5 +1,6 @@
import "package:fast_immutable_collections/fast_immutable_collections.dart"; import "package:fast_immutable_collections/fast_immutable_collections.dart";
import "package:material_ui/material_ui.dart"; import "package:material_ui/material_ui.dart";
import "package:nexus/widgets/sheet_layout.dart";
import "package:flutter/services.dart"; import "package:flutter/services.dart";
import "package:flutter_hooks/flutter_hooks.dart"; import "package:flutter_hooks/flutter_hooks.dart";
import "package:hooks_riverpod/hooks_riverpod.dart"; import "package:hooks_riverpod/hooks_riverpod.dart";
@ -46,27 +47,13 @@ extension BuildEventOptions on Event {
required String action, required String action,
required Future<void> Function(String reason) onConfirm, required Future<void> Function(String reason) onConfirm,
}) { }) {
showDialog( context.showAdaptiveSheet(
context: context, (context) => HookBuilder(
builder: (context) => HookBuilder(
builder: (context) { builder: (context) {
final reasonController = useTextEditingController(); final reasonController = useTextEditingController();
return AlertDialog( return SheetLayout(
title: Text(title), 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: [ actions: [
TextButton( TextButton(
onPressed: Navigator.of(context).pop, onPressed: Navigator.of(context).pop,
@ -82,6 +69,14 @@ extension BuildEventOptions on Event {
child: Text(action), child: Text(action),
), ),
], ],
children: [
Text(description),
TextField(
controller: reasonController,
textCapitalization: .sentences,
decoration: .new(labelText: "Reason (optional)"),
),
],
); );
}, },
), ),

View file

@ -1,4 +1,6 @@
import "package:material_ui/material_ui.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_svg/flutter_svg.dart"; import "package:flutter_svg/flutter_svg.dart";
import "package:hooks_riverpod/hooks_riverpod.dart"; import "package:hooks_riverpod/hooks_riverpod.dart";
import "package:m3e_card_list/m3e_card_list.dart"; import "package:m3e_card_list/m3e_card_list.dart";
@ -10,10 +12,20 @@ extension ShowAboutDialog on BuildContext {
final packageInfo = await PackageInfo.fromPlatform(); final packageInfo = await PackageInfo.fromPlatform();
if (mounted) { if (mounted) {
showDialog( showAdaptiveSheet(
context: this, (context) => SheetLayout(
builder: (context) => AlertDialog( actions: [
content: Column( TextButton(
onPressed: () => showLicensePage(context: context),
child: Text("View licenses"),
),
TextButton(
onPressed: Navigator.of(context).pop,
child: Text("Close"),
),
],
children: [
Column(
mainAxisSize: .min, mainAxisSize: .min,
spacing: 16, spacing: 16,
children: [ children: [
@ -31,7 +43,9 @@ extension ShowAboutDialog on BuildContext {
children: [ children: [
Text( Text(
"Nexus", "Nexus",
style: Theme.of(context).textTheme.headlineMedium, style: Theme.of(context)
.textTheme
.headlineMedium,
), ),
Text( Text(
"(${packageInfo.version}+${const String.fromEnvironment("GIT_COMMIT", defaultValue: "dev")})", "(${packageInfo.version}+${const String.fromEnvironment("GIT_COMMIT", defaultValue: "dev")})",
@ -68,15 +82,6 @@ extension ShowAboutDialog on BuildContext {
), ),
], ],
), ),
actions: [
TextButton(
onPressed: () => showLicensePage(context: context),
child: Text("View licenses"),
),
TextButton(
onPressed: Navigator.of(context).pop,
child: Text("Close"),
),
], ],
), ),
); );

View file

@ -1,4 +1,5 @@
import "package:material_ui/material_ui.dart"; 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:flutter_riverpod/flutter_riverpod.dart";
import "package:nexus/controllers/event.dart"; import "package:nexus/controllers/event.dart";
import "package:nexus/controllers/room_summary.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/helpers/extensions/show_user_popover.dart";
import "package:nexus/models/content/membership.dart"; import "package:nexus/models/content/membership.dart";
import "package:nexus/models/room_summary.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}) class const MentionChip(final String content, final String? roomId, {super.key})
extends ConsumerWidget { extends ConsumerWidget {
@ -58,10 +59,9 @@ class const MentionChip(final String content, final String? roomId, {super.key})
if (!await ref.focusRoom(summary.roomId, eventRowId) && if (!await ref.focusRoom(summary.roomId, eventRowId) &&
context.mounted) { context.mounted) {
showDialog( context.showAdaptiveSheet(
context: context, (context) => Consumer(
builder: (context) => Consumer( builder: (context, ref, _) => RoomSummarySheet(
builder: (context, ref, _) => RoomSummaryDialog(
ref ref
.watch( .watch(
RoomSummaryController.provider( RoomSummaryController.provider(

View file

@ -1,6 +1,8 @@
import "package:flutter_hooks/flutter_hooks.dart"; import "package:flutter_hooks/flutter_hooks.dart";
import "package:hooks_riverpod/hooks_riverpod.dart"; import "package:hooks_riverpod/hooks_riverpod.dart";
import "package:material_ui/material_ui.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/controllers/invites.dart";
import "package:nexus/helpers/extensions/get_localpart.dart"; import "package:nexus/helpers/extensions/get_localpart.dart";
import "package:nexus/main.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/nav_page.dart";
import "package:nexus/models/room_summary.dart"; import "package:nexus/models/room_summary.dart";
import "package:nexus/widgets/avatar_or_hash.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}) class const InvitesList({super.key})
extends HookConsumerWidget extends HookConsumerWidget
@ -33,13 +35,9 @@ class const InvitesList({super.key})
final decliningAll = useState(false); final decliningAll = useState(false);
Future<void> declineAll() async { Future<void> declineAll() async {
final confirmed = await showDialog<bool>( final confirmed = await context.showAdaptiveSheet<bool>(
context: context, (context) => SheetLayout(
builder: (context) => AlertDialog(
title: const Text("Decline All Invites"), title: const Text("Decline All Invites"),
content: Text(
"Are you sure you want to decline all ${invites.length} invites?",
),
actions: [ actions: [
TextButton( TextButton(
onPressed: () => Navigator.of(context).pop(false), onPressed: () => Navigator.of(context).pop(false),
@ -50,6 +48,11 @@ class const InvitesList({super.key})
child: const Text("Decline All"), child: const Text("Decline All"),
), ),
], ],
children: [
Text(
"Are you sure you want to decline all ${invites.length} invites?",
),
],
), ),
); );
if (confirmed != true) return; if (confirmed != true) return;
@ -103,10 +106,7 @@ class const InviteCard(final RoomSummary invite, {super.key})
return Card( return Card(
clipBehavior: .antiAlias, clipBehavior: .antiAlias,
child: ListTile( child: ListTile(
onTap: () => showDialog( onTap: () => context.showAdaptiveSheet((_) => RoomSummarySheet(invite)),
context: context,
builder: (_) => RoomSummaryDialog(invite),
),
leading: AvatarOrHash( leading: AvatarOrHash(
invite.avatarUrl, invite.avatarUrl,
name, name,

View file

@ -1,11 +1,13 @@
import "package:material_ui/material_ui.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_hooks/flutter_hooks.dart"; import "package:flutter_hooks/flutter_hooks.dart";
import "package:hooks_riverpod/hooks_riverpod.dart"; import "package:hooks_riverpod/hooks_riverpod.dart";
import "package:nexus/controllers/room_summary.dart"; import "package:nexus/controllers/room_summary.dart";
import "package:nexus/helpers/extensions/get_link_info.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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final roomAlias = useTextEditingController(); final roomAlias = useTextEditingController();
@ -17,16 +19,15 @@ class const JoinDialog(final WidgetRef ref, {super.key}) extends HookWidget {
final via = linkInfo?.via ?? const .empty(); final via = linkInfo?.via ?? const .empty();
final roomIdOrAlias = linkInfo?.identifier ?? roomAlias.text; final roomIdOrAlias = linkInfo?.identifier ?? roomAlias.text;
showDialog( context.showAdaptiveSheet(
context: context, (context) => Consumer(
builder: (context) => Consumer(
builder: (context, ref, _) => switch (ref.watch( builder: (context, ref, _) => switch (ref.watch(
RoomSummaryController.provider( RoomSummaryController.provider(
.new(roomIdOrAlias: roomIdOrAlias, via: via), .new(roomIdOrAlias: roomIdOrAlias, via: via),
), ),
)) { )) {
AsyncData(:final value) => RoomSummaryDialog(value, via: via), AsyncData(:final value) => RoomSummarySheet(value, via: via),
AsyncError _ || AsyncLoading _ => RoomSummaryDialog( AsyncError _ || AsyncLoading _ => RoomSummarySheet(
.new(roomId: roomIdOrAlias), .new(roomId: roomIdOrAlias),
via: via, via: via,
), ),
@ -35,9 +36,14 @@ class const JoinDialog(final WidgetRef ref, {super.key}) extends HookWidget {
); );
} }
return AlertDialog( return SheetLayout(
title: Text("Join a Room"), title: Text("Join a Room"),
content: Column( actions: [
TextButton(onPressed: Navigator.of(context).pop, child: Text("Cancel")),
TextButton(onPressed: onJoin, child: Text("Join")),
],
children: [
Column(
mainAxisSize: .min, mainAxisSize: .min,
crossAxisAlignment: .start, crossAxisAlignment: .start,
children: [ children: [
@ -50,9 +56,6 @@ class const JoinDialog(final WidgetRef ref, {super.key}) extends HookWidget {
), ),
], ],
), ),
actions: [
TextButton(onPressed: Navigator.of(context).pop, child: Text("Cancel")),
TextButton(onPressed: onJoin, child: Text("Join")),
], ],
); );
} }

View file

@ -1,11 +1,12 @@
import "package:material_ui/material_ui.dart"; 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:hooks_riverpod/hooks_riverpod.dart";
import "package:nexus/controllers/rooms.dart"; import "package:nexus/controllers/rooms.dart";
import "package:nexus/widgets/appbar.dart"; import "package:nexus/widgets/appbar.dart";
import "package:nexus/widgets/avatar_or_hash.dart"; import "package:nexus/widgets/avatar_or_hash.dart";
import "package:nexus/widgets/count_badge.dart"; import "package:nexus/widgets/count_badge.dart";
import "package:nexus/widgets/room_menu_button.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"; import "package:nexus/widgets/sidebar.dart";
final class const RoomAppbar({ final class const RoomAppbar({
@ -28,9 +29,8 @@ final class const RoomAppbar({
return Appbar( return Appbar(
onTap: room?.metadata == null onTap: room?.metadata == null
? null ? null
: () => showDialog( : () => context.showAdaptiveSheet(
context: context, (context) => RoomSummarySheet(
builder: (context) => RoomSummaryDialog(
.new( .new(
roomId: room!.metadata!.id, roomId: room!.metadata!.id,
joinedMembers: joinedMembers:

View file

@ -14,7 +14,7 @@ import "package:nexus/widgets/start_dm_sheet.dart";
import "package:nexus/widgets/divider_widget.dart"; import "package:nexus/widgets/divider_widget.dart";
import "package:nexus/widgets/error_dialog.dart"; import "package:nexus/widgets/error_dialog.dart";
import "package:nexus/widgets/pages/settings.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/widgets/room_menu_button.dart";
import "package:nexus/controllers/key.dart"; import "package:nexus/controllers/key.dart";
import "package:nexus/controllers/rooms.dart"; import "package:nexus/controllers/rooms.dart";
@ -157,10 +157,8 @@ class const SpacesRail({super.key}) extends ConsumerWidget {
tooltip: "Add a room", tooltip: "Add a room",
itemBuilder: (_) => [ itemBuilder: (_) => [
PopupMenuItem( PopupMenuItem(
onTap: () => showDialog( onTap: () =>
context: context, context.showAdaptiveSheet((_) => JoinSheet(ref)),
builder: (_) => JoinDialog(ref),
),
child: const ListTile( child: const ListTile(
title: Text("Join an existing room (or space)"), title: Text("Join an existing room (or space)"),
leading: Icon(Icons.numbers), leading: Icon(Icons.numbers),

View file

@ -1,5 +1,7 @@
import "package:fast_immutable_collections/fast_immutable_collections.dart"; import "package:fast_immutable_collections/fast_immutable_collections.dart";
import "package:material_ui/material_ui.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/services.dart";
import "package:flutter_riverpod/flutter_riverpod.dart"; import "package:flutter_riverpod/flutter_riverpod.dart";
import "package:nexus/controllers/client.dart"; import "package:nexus/controllers/client.dart";
@ -50,9 +52,8 @@ final class const RoomMenuButton(
), ),
), ),
PopupMenuItem( PopupMenuItem(
onTap: () => showDialog( onTap: () => context.showAdaptiveSheet(
context: context, (context) => HookBuilder(
builder: (context) => HookBuilder(
builder: (context) { builder: (context) {
final userIdController = useTextEditingController(); final userIdController = useTextEditingController();
final reasonController = useTextEditingController(); final reasonController = useTextEditingController();
@ -75,11 +76,18 @@ final class const RoomMenuButton(
.onError(showError); .onError(showError);
} }
return AlertDialog( return SheetLayout(
title: Text("Invite User"), title: Text("Invite User"),
content: Column( actions: [
mainAxisSize: .min, TextButton(
spacing: 12, onPressed: Navigator.of(context).pop,
child: Text("Cancel"),
),
TextButton(
onPressed: valid ? submit : null,
child: Text("Invite"),
),
],
children: [ children: [
TextField( TextField(
controller: userIdController, controller: userIdController,
@ -103,17 +111,6 @@ final class const RoomMenuButton(
), ),
), ),
], ],
),
actions: [
TextButton(
onPressed: Navigator.of(context).pop,
child: Text("Cancel"),
),
TextButton(
onPressed: valid ? submit : null,
child: Text("Invite"),
),
],
); );
}, },
), ),
@ -124,13 +121,9 @@ final class const RoomMenuButton(
), ),
), ),
PopupMenuItem( PopupMenuItem(
onTap: () => showDialog( onTap: () => context.showAdaptiveSheet(
context: context, (context) => SheetLayout(
builder: (context) => AlertDialog(
title: Text("Leave Room"), title: Text("Leave Room"),
content: Text(
"Are you sure you want to leave \"${room!.metadata?.name ?? "Unnamed Room"}\"?",
),
actions: [ actions: [
TextButton( TextButton(
onPressed: Navigator.of(context).pop, onPressed: Navigator.of(context).pop,
@ -154,6 +147,11 @@ final class const RoomMenuButton(
child: Text("Leave"), child: Text("Leave"),
), ),
], ],
children: [
Text(
"Are you sure you want to leave \"${room!.metadata?.name ?? "Unnamed Room"}\"?",
),
],
), ),
), ),
child: ListTile( child: ListTile(

View file

@ -1,6 +1,7 @@
import "package:fast_immutable_collections/fast_immutable_collections.dart"; import "package:fast_immutable_collections/fast_immutable_collections.dart";
import "package:flutter_riverpod/flutter_riverpod.dart"; import "package:flutter_riverpod/flutter_riverpod.dart";
import "package:material_ui/material_ui.dart"; import "package:material_ui/material_ui.dart";
import "package:nexus/widgets/sheet_layout.dart";
import "package:nexus/controllers/invites.dart"; import "package:nexus/controllers/invites.dart";
import "package:nexus/helpers/extensions/get_localpart.dart"; import "package:nexus/helpers/extensions/get_localpart.dart";
import "package:nexus/helpers/extensions/join_room.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/widgets/linkified_text.dart";
import "package:nexus/models/room_summary.dart"; import "package:nexus/models/room_summary.dart";
class const RoomSummaryDialog( class const RoomSummarySheet(
final RoomSummary summary, { final RoomSummary summary, {
final IList<String>? via, final IList<String>? via,
super.key, super.key,
@ -21,10 +22,7 @@ class const RoomSummaryDialog(
final isInvite = summary.membership == .invite; final isInvite = summary.membership == .invite;
final colorScheme = Theme.of(context).colorScheme; final colorScheme = Theme.of(context).colorScheme;
return AlertDialog( return SheetLayout(
constraints: .loose(.fromWidth(400)),
scrollable: true,
contentPadding: EdgeInsets.all(24).copyWith(top: 8),
title: Row( title: Row(
spacing: 12, spacing: 12,
mainAxisSize: .min, 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: [ actions: [
TextButton(onPressed: Navigator.of(context).pop, child: Text("Cancel")), TextButton(onPressed: Navigator.of(context).pop, child: Text("Cancel")),
if (isInvite) ...[ if (isInvite) ...[
@ -146,6 +94,60 @@ class const RoomSummaryDialog(
child: Text("Join"), 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),
),
],
),
],
); );
} }
} }

View file

@ -1,6 +1,7 @@
import "package:material_ui/material_ui.dart"; 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: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<T>({ final class const DialogListTile<T>({
required final T? initialValue, required final T? initialValue,
@ -26,9 +27,8 @@ final class const DialogListTile<T>({
), ),
child: ListTile( child: ListTile(
enabled: onChanged != null, enabled: onChanged != null,
onTap: () => showDialog( onTap: () => context.showAdaptiveSheet(
context: context, (context) => RadioSheet<T>(
builder: (context) => RadioDialog<T>(
title: title, title: title,
getName: getName, getName: getName,
onChanged: onChanged == null onChanged: onChanged == null

View file

@ -1,7 +1,8 @@
import "package:material_ui/material_ui.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_hooks/flutter_hooks.dart";
final class const RadioDialog<T>({ final class const RadioSheet<T>({
required final T? value, required final T? value,
required final String title, required final String title,
required final List<T> options, required final List<T> options,
@ -12,9 +13,21 @@ final class const RadioDialog<T>({
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final mutValue = useState<T?>(null); final mutValue = useState<T?>(null);
return AlertDialog( return SheetLayout(
title: Text(title), title: Text(title),
content: RadioGroup<T>( actions: [
TextButton(onPressed: Navigator.of(context).pop, child: Text("Cancel")),
if (onChanged != null)
TextButton(
onPressed: () {
if (mutValue.value != null) onChanged!(mutValue.value as T);
Navigator.of(context).pop();
},
child: Text("OK"),
),
],
children: [
RadioGroup<T>(
groupValue: mutValue.value ?? value, groupValue: mutValue.value ?? value,
onChanged: (value) => mutValue.value = value ?? mutValue.value, onChanged: (value) => mutValue.value = value ?? mutValue.value,
@ -32,16 +45,6 @@ final class const RadioDialog<T>({
.toList(), .toList(),
), ),
), ),
actions: [
TextButton(onPressed: Navigator.of(context).pop, child: Text("Cancel")),
if (onChanged != null)
TextButton(
onPressed: () {
if (mutValue.value != null) onChanged!(mutValue.value as T);
Navigator.of(context).pop();
},
child: Text("OK"),
),
], ],
); );
} }

View file

@ -1,7 +1,7 @@
import "package:material_ui/material_ui.dart"; import "package:material_ui/material_ui.dart";
class const SheetLayout({ class const SheetLayout({
required final Widget title, final Widget? title,
required final List<Widget> children, required final List<Widget> children,
required final List<Widget> actions, required final List<Widget> actions,
super.key, super.key,
@ -16,6 +16,7 @@ class const SheetLayout({
crossAxisAlignment: .stretch, crossAxisAlignment: .stretch,
spacing: 12, spacing: 12,
children: [ children: [
if (title case final title?)
DefaultTextStyle( DefaultTextStyle(
style: Theme.of(context).textTheme.headlineSmall!, style: Theme.of(context).textTheme.headlineSmall!,
child: title, child: title,

View file

@ -1,5 +1,6 @@
import "package:collection/collection.dart"; import "package:collection/collection.dart";
import "package:material_ui/material_ui.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_hooks/flutter_hooks.dart";
import "package:flutter_riverpod/flutter_riverpod.dart"; import "package:flutter_riverpod/flutter_riverpod.dart";
import "package:intl/intl.dart"; import "package:intl/intl.dart";
@ -35,27 +36,14 @@ final class const UserBottomSheet(
); );
final client = ref.read(ClientController.provider.notifier); final client = ref.read(ClientController.provider.notifier);
void showMembershipDialog(MembershipAction action) => showDialog( void showMembershipDialog(MembershipAction action) =>
context: context, context.showAdaptiveSheet(
builder: (context) => HookBuilder( (context) => HookBuilder(
builder: (context) { builder: (context) {
final actionReasonController = useTextEditingController(); final actionReasonController = useTextEditingController();
return AlertDialog( return SheetLayout(
title: Text("${toBeginningOfSentenceCase(action.name)} $userId"), title: Text(
content: Column( "${toBeginningOfSentenceCase(action.name)} $userId",
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)",
),
),
],
), ),
actions: [ actions: [
TextButton( TextButton(
@ -79,6 +67,16 @@ final class const UserBottomSheet(
child: Text(toBeginningOfSentenceCase(action.name)), 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)",
),
),
],
); );
}, },
), ),