Compare commits

..
Author SHA1 Message Date
88cdcf9863
use compat bridge for settings sidebar 2026-08-23 14:02:25 -04:00
e2f25749e0
fix fetching spec versions early 2026-08-23 14:00:11 -04:00
3 changed files with 92 additions and 66 deletions

View file

@ -1,4 +1,5 @@
import "dart:io"; import "dart:io";
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:flutter_riverpod/flutter_riverpod.dart"; import "package:flutter_riverpod/flutter_riverpod.dart";
@ -8,6 +9,7 @@ import "package:nexus/controllers/account_data.dart";
import "package:nexus/controllers/client.dart"; import "package:nexus/controllers/client.dart";
import "package:nexus/controllers/client_state.dart"; import "package:nexus/controllers/client_state.dart";
import "package:nexus/controllers/settings.dart"; import "package:nexus/controllers/settings.dart";
import "package:nexus/controllers/spec_versions.dart";
import "package:nexus/models/account_data.dart"; import "package:nexus/models/account_data.dart";
import "package:nexus/models/settings_category.dart"; import "package:nexus/models/settings_category.dart";
import "package:nexus/main.dart"; import "package:nexus/main.dart";
@ -18,9 +20,6 @@ class SettingsSectionsController
@override @override
Future<IMap<String, IList<SettingsCategory>>> build() async { Future<IMap<String, IList<SettingsCategory>>> build() async {
final settings = await ref.watch(SettingsController.provider.future); final settings = await ref.watch(SettingsController.provider.future);
final specVersionsResponse = await ref
.watch(ClientController.provider.notifier)
.getSpecVersions();
return .new({ return .new({
"General": .new([ "General": .new([
@ -49,8 +48,7 @@ class SettingsSectionsController
.new( .new(
title: "Use Dynamic Theme", title: "Use Dynamic Theme",
icon: Icons.palette, icon: Icons.palette,
description: description: "Toggle on or off Dynamic Theme. Only available on Android, Linux, Windows, or MacOS.",
"Toggle on or off Dynamic Theme. Only available on Android, Linux, Windows, or MacOS.",
builder: (title, description, icon) => SwitchListTile( builder: (title, description, icon) => SwitchListTile(
title: Text(title), title: Text(title),
subtitle: Text(description), subtitle: Text(description),
@ -76,8 +74,7 @@ class SettingsSectionsController
settings: .new([ settings: .new([
.new( .new(
title: "Linux Mobile Mode", title: "Linux Mobile Mode",
description: description: "Enables some fixes for Linux mobile, e.g. disabling dragging appbar for moving window.",
"Enables some fixes for Linux mobile, e.g. disabling dragging appbar for moving window.",
icon: Icons.construction, icon: Icons.construction,
builder: (title, description, icon) => SwitchListTile( builder: (title, description, icon) => SwitchListTile(
title: Text(title), title: Text(title),
@ -104,40 +101,51 @@ class SettingsSectionsController
settings: .new([ settings: .new([
.new( .new(
title: "Invite Blocking", title: "Invite Blocking",
description: description: "Block invites, either completely, or block only invites from users without shared private rooms (depends on server support).",
"Block invites, either completely, or block only invites from users without shared private rooms (depends on server support).",
builder: (title, description, icon) => Consumer( builder: (title, description, icon) => Consumer(
builder: (context, ref, _) => builder: (context, ref, _) {
DialogListTile<DefaultInviteAction>( final specVersionsResponse = ref.watch(
icon: Icon(icon), SpecVersionsController.provider,
title: title, );
subtitle: Text(description), return DialogListTile<DefaultInviteAction>(
initialValue: ref icon: Icon(icon),
.watch(AccountDataController.provider) title: title,
.invitePermissionConfig subtitle: Text(description),
.defaultAction, initialValue: ref
options: specVersionsResponse.unstableFeatures.msc4494 .watch(AccountDataController.provider)
? DefaultInviteAction.values .invitePermissionConfig
: IList( .defaultAction,
DefaultInviteAction.values, options:
).remove(.denyPublic).toList(), specVersionsResponse.maybeWhen(
getName: (option) => switch (option) { data: (data) => data.unstableFeatures.msc4494,
.allow => "Allow", orElse: () => true,
.deny => "Deny", )
.denyPublic => "Deny public", ? DefaultInviteAction.values
}, : IList(DefaultInviteAction.values)
onChanged: (value) => ref .remove(.denyPublic)
.watch(ClientController.provider.notifier) .toList(),
.setAccountData( getName: (option) => switch (option) {
.new( .allow => "Allow",
type: AccountData.invitePermissionConfigKey, .deny => "Deny",
content: InvitePermissionConfig( .denyPublic => "Deny public",
defaultAction: value, },
), onChanged: specVersionsResponse.maybeWhen(
), data: (_) =>
) (value) => ref
.onError(showError), .watch(ClientController.provider.notifier)
.setAccountData(
.new(
type: AccountData.invitePermissionConfigKey,
content: InvitePermissionConfig(
defaultAction: value,
),
),
)
.onError(showError),
orElse: () => null,
), ),
);
},
), ),
icon: Icons.person_off, icon: Icons.person_off,
), ),
@ -156,9 +164,8 @@ class SettingsSectionsController
final colorScheme = Theme.of(context).colorScheme; final colorScheme = Theme.of(context).colorScheme;
return M3EButton.icon( return M3EButton.icon(
onPressed: () async { onPressed: () async {
Navigator.of( Navigator.of(context)
context, .popUntil((route) => route.isFirst);
).popUntil((route) => route.isFirst);
await WidgetsBinding.instance.endOfFrame; await WidgetsBinding.instance.endOfFrame;

View file

@ -0,0 +1,15 @@
import "package:hooks_riverpod/hooks_riverpod.dart";
import "package:nexus/controllers/client.dart";
import "package:nexus/models/spec_versions_response.dart";
class SpecVersionsController extends AsyncNotifier<SpecVersionsResponse> {
@override
Future<SpecVersionsResponse> build() =>
ref.watch(ClientController.provider.notifier).getSpecVersions();
static final provider =
AsyncNotifierProvider.autoDispose<
SpecVersionsController,
SpecVersionsResponse
>(SpecVersionsController.new);
}

View file

@ -170,30 +170,34 @@ class const SettingsPage({super.key}) extends ConsumerWidget {
) )
: Row( : Row(
children: [ children: [
NavigationRailM3E( MaterialUiCompatibilityBridge(
type: .alwaysExpand, child: NavigationRailM3E(
trailing: searchBar, type: .alwaysExpand,
scrollable: true, trailing: searchBar,
sections: sections scrollable: true,
.mapTo( sections: sections
(categoryGroup, categories) => .mapTo(
NavigationRailM3ESection( (
header: DividerText(categoryGroup), categoryGroup,
destinations: categories categories,
.map( ) => NavigationRailM3ESection(
(category) => header: DividerText(categoryGroup),
NavigationRailM3EDestination( destinations: categories
icon: Icon(category.icon), .map(
label: category.title, (category) =>
), NavigationRailM3EDestination(
) icon: Icon(category.icon),
.toList(), label: category.title,
), ),
) )
.toList(), .toList(),
selectedIndex: selected.value, ),
onDestinationSelected: (value) => )
selected.value = value, .toList(),
selectedIndex: selected.value,
onDestinationSelected: (value) =>
selected.value = value,
),
), ),
VerticalDivider(), VerticalDivider(),
Expanded( Expanded(