treewide: small refactors

This commit is contained in:
Henry Hiles 2026-10-15 20:10:44 -04:00
commit 8d944831f5
Signed by: Henry-Hiles
SSH key fingerprint: SHA256:VKQUdS31Q90KvX7EkKMHMBpUspcmItAh86a+v7PGiIs
10 changed files with 63 additions and 63 deletions

View file

@ -41,7 +41,7 @@ extension BuildEventOptions on Event {
.read(PinnedIdsController.provider(roomId)) .read(PinnedIdsController.provider(roomId))
.contains(eventId); .contains(eventId);
void showReasonDialog({ void showReasonSheet({
required String title, required String title,
required String description, required String description,
required String action, required String action,
@ -235,7 +235,7 @@ extension BuildEventOptions on Event {
) && ) &&
!eventId.isFake) !eventId.isFake)
PopupMenuItem( PopupMenuItem(
onTap: () => showReasonDialog( onTap: () => showReasonSheet(
title: "Delete Message", title: "Delete Message",
description: description:
"Are you sure you want to delete this message? " "Are you sure you want to delete this message? "
@ -260,7 +260,7 @@ extension BuildEventOptions on Event {
), ),
PopupMenuItem( PopupMenuItem(
onTap: () => showReasonDialog( onTap: () => showReasonSheet(
title: "Report", title: "Report",
description: description:
"Report this event to your server administrators, " "Report this event to your server administrators, "

View file

@ -1,6 +1,9 @@
extension GetLocalpart on String { extension GetLocalpart on String {
String get localpart => length > 1 ? substring(1).split(":").first : "?"; String get localpart => length > 1 ? substring(1).split(":").first : "?";
/// Whether this looks like a valid Matrix user ID, e.g. `@user:example.com`.
bool get isValidUserId => RegExp(r"^@[^:\s]+:[^\s]+$").hasMatch(this);
String? get serverName { String? get serverName {
final index = indexOf(":"); final index = indexOf(":");
return index == -1 ? null : substring(index + 1); return index == -1 ? null : substring(index + 1);

View file

@ -7,8 +7,8 @@ import "package:m3e_card_list/m3e_card_list.dart";
import "package:nexus/helpers/launch_helper.dart"; import "package:nexus/helpers/launch_helper.dart";
import "package:package_info_plus/package_info_plus.dart"; import "package:package_info_plus/package_info_plus.dart";
extension ShowAboutDialog on BuildContext { extension ShowAboutSheet on BuildContext {
Future<void> showAboutDialog(WidgetRef ref) async { Future<void> showAboutSheet(WidgetRef ref) async {
final packageInfo = await PackageInfo.fromPlatform(); final packageInfo = await PackageInfo.fromPlatform();
if (mounted) { if (mounted) {

View file

@ -0,0 +1,28 @@
import "package:material_ui/material_ui.dart";
extension ShowErrorSnackBar on ScaffoldMessengerState {
/// Shows [message] in a snackbar styled as an error.
ScaffoldFeatureController<SnackBar, SnackBarClosedReason> showErrorSnackBar(
String message, {
({String label, VoidCallback onPressed})? action,
}) {
final colorScheme = Theme.of(context).colorScheme;
return showSnackBar(
.new(
backgroundColor: colorScheme.errorContainer,
content: Text(
message,
style: .new(color: colorScheme.onErrorContainer),
),
action: action == null
? null
: .new(
label: action.label,
onPressed: action.onPressed,
textColor: colorScheme.onErrorContainer,
),
),
);
}
}

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_error_snack_bar.dart";
extension WithProgressSnackbar on BuildContext { extension WithProgressSnackbar on BuildContext {
/// Shows [progress] in a snackbar while [action] runs, and shows any error /// Shows [progress] in a snackbar while [action] runs, and shows any error
@ -10,8 +11,6 @@ extension WithProgressSnackbar on BuildContext {
Future<T> Function() action, Future<T> Function() action,
) async { ) async {
final messenger = ScaffoldMessenger.of(this); final messenger = ScaffoldMessenger.of(this);
final colorScheme = Theme.of(this).colorScheme;
final snackbar = messenger.showSnackBar( final snackbar = messenger.showSnackBar(
.new(content: Text(progress), duration: const .new(days: 1)), .new(content: Text(progress), duration: const .new(days: 1)),
); );
@ -19,15 +18,7 @@ extension WithProgressSnackbar on BuildContext {
try { try {
return await action(); return await action();
} catch (error) { } catch (error) {
messenger.showSnackBar( messenger.showErrorSnackBar(error.toString());
.new(
backgroundColor: colorScheme.errorContainer,
content: Text(
error.toString(),
style: .new(color: colorScheme.onErrorContainer),
),
),
);
return null; return null;
} finally { } finally {

View file

@ -6,6 +6,7 @@ import "package:hooks_riverpod/hooks_riverpod.dart";
import "package:nexus/controllers/auth_url.dart"; import "package:nexus/controllers/auth_url.dart";
import "package:nexus/controllers/client.dart"; import "package:nexus/controllers/client.dart";
import "package:nexus/controllers/client_id.dart"; import "package:nexus/controllers/client_id.dart";
import "package:nexus/helpers/extensions/show_error_snack_bar.dart";
import "package:nexus/helpers/launch_helper.dart"; import "package:nexus/helpers/launch_helper.dart";
import "package:nexus/main.dart"; import "package:nexus/main.dart";
import "package:nexus/models/gomuks_exception.dart"; import "package:nexus/models/gomuks_exception.dart";
@ -27,14 +28,8 @@ class const SelectServerPage({super.key}) extends HookConsumerWidget {
Future<void> setHomeserver(Uri? newHomeserver) async { Future<void> setHomeserver(Uri? newHomeserver) async {
if (newHomeserver == null) { if (newHomeserver == null) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showErrorSnackBar(
SnackBar( "Failed to parse homeserver URL. Are you sure you typed it correctly?",
content: Text(
"Failed to parse homeserver URL. Are you sure you typed it correctly?",
style: .new(color: theme.colorScheme.onErrorContainer),
),
backgroundColor: theme.colorScheme.errorContainer,
),
); );
} }
isLoading.value = true; isLoading.value = true;
@ -58,14 +53,8 @@ class const SelectServerPage({super.key}) extends HookConsumerWidget {
} on GomuksException catch (error) { } on GomuksException catch (error) {
if (error.errorCode != "M_UNRECOGNIZED") rethrow; if (error.errorCode != "M_UNRECOGNIZED") rethrow;
if (context.mounted) { if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showErrorSnackBar(
SnackBar( "${url.host} doesn't support OAuth login, which Nexus requires. Try a different homeserver, or ask your homeserver's admin to enable OAuth.",
content: Text(
"${url.host} doesn't support OAuth login, which Nexus requires. Try a different homeserver, or ask your homeserver's admin to enable OAuth.",
style: .new(color: theme.colorScheme.onErrorContainer),
),
backgroundColor: theme.colorScheme.errorContainer,
),
); );
} }
return; return;
@ -109,18 +98,11 @@ class const SelectServerPage({super.key}) extends HookConsumerWidget {
} }
if (newUrl == null) { if (newUrl == null) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showErrorSnackBar(
SnackBar( "Homeserver verification failed.",
content: Text( action: (
"Homeserver verification failed.", label: "Attempt log in anyways",
style: .new(color: theme.colorScheme.onErrorContainer), onPressed: () => tryLogin(newHomeserver!).onError(showError),
),
action: SnackBarAction(
onPressed: () => tryLogin(newHomeserver!).onError(showError),
label: "Attempt log in anyways",
textColor: theme.colorScheme.onErrorContainer,
),
backgroundColor: theme.colorScheme.errorContainer,
), ),
); );
} else { } else {

View file

@ -7,7 +7,7 @@ import "package:m3e_card_list/m3e_card_list.dart";
import "package:navigation_rail_m3e/navigation_rail_m3e.dart"; import "package:navigation_rail_m3e/navigation_rail_m3e.dart";
import "package:nexus/controllers/settings_sections.dart"; import "package:nexus/controllers/settings_sections.dart";
import "package:nexus/helpers/extensions/better_when.dart"; import "package:nexus/helpers/extensions/better_when.dart";
import "package:nexus/helpers/extensions/show_about_dialog.dart"; import "package:nexus/helpers/extensions/show_about_sheet.dart";
import "package:nexus/widgets/pages/settings_category.dart"; import "package:nexus/widgets/pages/settings_category.dart";
import "package:nexus/widgets/divider_text.dart"; import "package:nexus/widgets/divider_text.dart";
import "package:nexus/widgets/highlight_wrapper.dart"; import "package:nexus/widgets/highlight_wrapper.dart";
@ -106,7 +106,7 @@ class const SettingsPage({super.key}) extends ConsumerWidget {
actionsPadding: .symmetric(horizontal: 12), actionsPadding: .symmetric(horizontal: 12),
actions: [ actions: [
IconButton( IconButton(
onPressed: () => context.showAboutDialog(ref), onPressed: () => context.showAboutSheet(ref),
icon: Icon(Icons.info_outline), icon: Icon(Icons.info_outline),
), ),
], ],

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/get_localpart.dart";
import "package:nexus/helpers/extensions/with_progress_snackbar.dart";
import "package:nexus/helpers/extensions/show_adaptive_sheet.dart"; import "package:nexus/helpers/extensions/show_adaptive_sheet.dart";
import "package:nexus/widgets/sheet_layout.dart"; import "package:nexus/widgets/sheet_layout.dart";
import "package:flutter/services.dart"; import "package:flutter/services.dart";
@ -58,7 +60,7 @@ final class const RoomMenuButton(
final userIdController = useTextEditingController(); final userIdController = useTextEditingController();
final reasonController = useTextEditingController(); final reasonController = useTextEditingController();
final text = useValueListenable(userIdController).text.trim(); final text = useValueListenable(userIdController).text.trim();
final valid = RegExp(r"^@[^:\s]+:[^\s]+$").hasMatch(text); final valid = text.isValidUserId;
void submit() { void submit() {
if (!valid) return; if (!valid) return;
@ -130,19 +132,12 @@ final class const RoomMenuButton(
child: Text("Cancel"), child: Text("Cancel"),
), ),
TextButton( TextButton(
onPressed: () async { onPressed: () {
context.withProgressSnackbar(
"Leaving room...",
() => client.leaveRoom(room!.metadata!.id),
);
Navigator.of(context).pop(); Navigator.of(context).pop();
final snackbar = ScaffoldMessenger.of(context)
.showSnackBar(
.new(
content: Text("Leaving room..."),
duration: Duration(days: 1),
),
);
await client
.leaveRoom(room!.metadata!.id)
.onError(showError)
.whenComplete(snackbar.close);
}, },
child: Text("Leave"), child: Text("Leave"),
), ),

View file

@ -1,5 +1,6 @@
import "package:flutter_hooks/flutter_hooks.dart"; import "package:flutter_hooks/flutter_hooks.dart";
import "package:material_ui/material_ui.dart"; import "package:material_ui/material_ui.dart";
import "package:nexus/helpers/extensions/get_localpart.dart";
import "package:nexus/helpers/extensions/create_room.dart"; import "package:nexus/helpers/extensions/create_room.dart";
import "package:nexus/widgets/sheet_layout.dart"; import "package:nexus/widgets/sheet_layout.dart";
@ -12,7 +13,7 @@ class const StartDmSheet({final String? userId, final String? name, super.key})
final userIdController = useTextEditingController(); final userIdController = useTextEditingController();
final text = useValueListenable(userIdController).text.trim(); final text = useValueListenable(userIdController).text.trim();
final target = userId ?? text; final target = userId ?? text;
final valid = RegExp(r"^@[^:\s]+:[^\s]+$").hasMatch(target); final valid = target.isValidUserId;
final encrypted = useState(true); final encrypted = useState(true);
final reasonController = useTextEditingController(); final reasonController = useTextEditingController();

View file

@ -36,7 +36,7 @@ final class const UserBottomSheet(
); );
final client = ref.read(ClientController.provider.notifier); final client = ref.read(ClientController.provider.notifier);
void showMembershipDialog(MembershipAction action) => void showMembershipSheet(MembershipAction action) =>
context.showAdaptiveSheet( context.showAdaptiveSheet(
(context) => HookBuilder( (context) => HookBuilder(
builder: (context) { builder: (context) {
@ -229,7 +229,7 @@ final class const UserBottomSheet(
children: [ children: [
if (member.status == .join || member.status == .invite) if (member.status == .join || member.status == .invite)
M3EButton.icon( M3EButton.icon(
onPressed: () => showMembershipDialog(.kick), onPressed: () => showMembershipSheet(.kick),
shape: .square, shape: .square,
icon: Icon(Icons.sports_martial_arts), icon: Icon(Icons.sports_martial_arts),
label: Text("Kick"), label: Text("Kick"),
@ -244,7 +244,7 @@ final class const UserBottomSheet(
), ),
M3EButton.icon( M3EButton.icon(
onPressed: () => showMembershipDialog(.ban), onPressed: () => showMembershipSheet(.ban),
shape: .square, shape: .square,
icon: Icon(Icons.gavel), icon: Icon(Icons.gavel),
label: Text("Ban"), label: Text("Ban"),