Add settings page (#46)

Fixes #37, fixes #47.

Reviewed-on: Nexus/nexus#46
This commit is contained in:
Henry Hiles 2026-07-17 20:36:41 -04:00 committed by Henry Hiles
commit 0ef9b25320
20 changed files with 970 additions and 28 deletions

View file

@ -0,0 +1,27 @@
import "dart:convert";
import "package:flutter_riverpod/flutter_riverpod.dart";
import "package:nexus/controllers/settings_file_controller.dart";
import "package:nexus/models/settings.dart";
class SettingsController extends AsyncNotifier<Settings> {
@override
Future<Settings> build() async {
final file = await ref.watch(SettingsFileController.provider.future);
try {
return Settings.fromJson(json.decode(await file.readAsString()));
} catch (_) {
return Settings();
}
}
Future<void> set(Settings settings) async {
state = AsyncData(settings);
final file = await ref.watch(SettingsFileController.provider.future);
await file.writeAsString(json.encode(settings.toJson()));
}
static final provider = AsyncNotifierProvider<SettingsController, Settings>(
SettingsController.new,
);
}

View file

@ -0,0 +1,23 @@
import "dart:io";
import "package:flutter_riverpod/flutter_riverpod.dart";
import "package:path/path.dart";
import "package:path_provider/path_provider.dart";
import "package:xdg_directories/xdg_directories.dart";
class SettingsFileController extends AsyncNotifier<File> {
@override
Future<File> build() async {
final directory = await switch (Platform.isLinux) {
true => Directory(
join(configHome.absolute.path, "nexus"),
).create(recursive: true),
false => getApplicationSupportDirectory(),
};
return File(join(directory.absolute.path, "config.json"));
}
static final provider = AsyncNotifierProvider<SettingsFileController, File>(
SettingsFileController.new,
);
}

View file

@ -0,0 +1,98 @@
import "dart:io";
import "package:fast_immutable_collections/fast_immutable_collections.dart";
import "package:flutter/material.dart";
import "package:flutter_riverpod/flutter_riverpod.dart";
import "package:intl/intl.dart";
import "package:nexus/controllers/settings_controller.dart";
import "package:nexus/models/settings_category.dart";
import "package:nexus/main.dart";
import "package:nexus/widgets/settings/dialog_list_tile.dart";
class SettingsSectionsController
extends AsyncNotifier<IMap<String, IList<SettingsCategory>>> {
@override
Future<IMap<String, IList<SettingsCategory>>> build() async {
final settings = await ref.watch(SettingsController.provider.future);
return .new({
"General": .new([
.new(
title: "Appearance",
icon: Icons.brush,
settings: .new([
.new(
title: "Theme",
description:
"Toggle between Light Mode, Dark Mode, and System themes.",
icon: Icons.contrast,
builder: (title, description, icon) => DialogListTile<ThemeMode>(
icon: Icon(icon),
title: title,
subtitle: Text(description),
initialValue: settings.theme,
options: ThemeMode.values,
getName: (option) => toBeginningOfSentenceCase(option.name),
onChanged: (value) => ref
.watch(SettingsController.provider.notifier)
.set(settings.copyWith(theme: value))
.onError(showError),
),
),
.new(
title: "Use Dynamic Theme",
icon: Icons.palette,
description:
"Toggle on or off Dynamic Theme. Only available on Android, Linux, Windows, or MacOS.",
builder: (title, description, icon) => SwitchListTile(
title: Text(title),
subtitle: Text(description),
secondary: Icon(icon),
value: settings.useDynamicTheming,
onChanged:
(Platform.isAndroid ||
Platform.isLinux ||
Platform.isMacOS ||
Platform.isWindows)
? (value) => ref
.watch(SettingsController.provider.notifier)
.set(settings.copyWith(useDynamicTheming: value))
.onError(showError)
: null,
),
),
]),
),
.new(
title: "Behavior",
icon: Icons.psychology,
settings: .new([
.new(
title: "Linux Mobile Mode",
description:
"Enables some fixes for Linux mobile, e.g. disabling dragging appbar for moving window.",
icon: Icons.construction,
builder: (title, description, icon) => SwitchListTile(
title: Text(title),
subtitle: Text(description),
secondary: Icon(icon),
value: settings.linuxMobileMode,
onChanged: Platform.isLinux
? (value) => ref
.watch(SettingsController.provider.notifier)
.set(settings.copyWith(linuxMobileMode: value))
.onError(showError)
: null,
),
),
]),
),
]),
});
}
static final provider =
AsyncNotifierProvider<
SettingsSectionsController,
IMap<String, IList<SettingsCategory>>
>(SettingsSectionsController.new);
}