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: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<void> 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)"),
),
],
);
},
),

View file

@ -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,10 +12,20 @@ extension ShowAboutDialog on BuildContext {
final packageInfo = await PackageInfo.fromPlatform();
if (mounted) {
showDialog(
context: this,
builder: (context) => AlertDialog(
content: Column(
showAdaptiveSheet(
(context) => SheetLayout(
actions: [
TextButton(
onPressed: () => showLicensePage(context: context),
child: Text("View licenses"),
),
TextButton(
onPressed: Navigator.of(context).pop,
child: Text("Close"),
),
],
children: [
Column(
mainAxisSize: .min,
spacing: 16,
children: [
@ -31,7 +43,9 @@ extension ShowAboutDialog on BuildContext {
children: [
Text(
"Nexus",
style: Theme.of(context).textTheme.headlineMedium,
style: Theme.of(context)
.textTheme
.headlineMedium,
),
Text(
"(${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: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(

View file

@ -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<void> declineAll() async {
final confirmed = await showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
final confirmed = await context.showAdaptiveSheet<bool>(
(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,

View file

@ -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,9 +36,14 @@ class const JoinDialog(final WidgetRef ref, {super.key}) extends HookWidget {
);
}
return AlertDialog(
return SheetLayout(
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,
crossAxisAlignment: .start,
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: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:

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/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),

View file

@ -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,11 +76,18 @@ final class const RoomMenuButton(
.onError(showError);
}
return AlertDialog(
return SheetLayout(
title: Text("Invite User"),
content: Column(
mainAxisSize: .min,
spacing: 12,
actions: [
TextButton(
onPressed: Navigator.of(context).pop,
child: Text("Cancel"),
),
TextButton(
onPressed: valid ? submit : null,
child: Text("Invite"),
),
],
children: [
TextField(
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(
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(

View file

@ -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<String>? 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),
),
],
),
],
);
}
}

View file

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

View file

@ -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<T>({
final class const RadioSheet<T>({
required final T? value,
required final String title,
required final List<T> options,
@ -12,9 +13,21 @@ final class const RadioDialog<T>({
@override
Widget build(BuildContext context) {
final mutValue = useState<T?>(null);
return AlertDialog(
return SheetLayout(
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,
onChanged: (value) => mutValue.value = value ?? mutValue.value,
@ -32,16 +45,6 @@ final class const RadioDialog<T>({
.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";
class const SheetLayout({
required final Widget title,
final Widget? title,
required final List<Widget> children,
required final List<Widget> actions,
super.key,
@ -16,6 +16,7 @@ class const SheetLayout({
crossAxisAlignment: .stretch,
spacing: 12,
children: [
if (title case final title?)
DefaultTextStyle(
style: Theme.of(context).textTheme.headlineSmall!,
child: title,

View file

@ -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,27 +36,14 @@ final class const UserBottomSheet(
);
final client = ref.read(ClientController.provider.notifier);
void showMembershipDialog(MembershipAction action) => showDialog(
context: context,
builder: (context) => HookBuilder(
void showMembershipDialog(MembershipAction action) =>
context.showAdaptiveSheet(
(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)",
),
),
],
return SheetLayout(
title: Text(
"${toBeginningOfSentenceCase(action.name)} $userId",
),
actions: [
TextButton(
@ -79,6 +67,16 @@ final class const UserBottomSheet(
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)",
),
),
],
);
},
),