This commit is contained in:
2026-02-27 21:12:56 +08:00
commit a878084cbb
233 changed files with 22988 additions and 0 deletions

View File

@@ -0,0 +1,99 @@
import 'package:riverpod_annotation/riverpod_annotation.dart';
import '../../../../backend/api/commands.dart' as commands;
part 'settings_controller.g.dart';
class SettingsState {
const SettingsState({
required this.hostname,
required this.savePath,
required this.autoAccept,
required this.saveHistory,
required this.enableTls,
});
final String hostname;
final String savePath;
final bool autoAccept;
final bool saveHistory;
final bool enableTls;
SettingsState copyWith({
String? hostname,
String? savePath,
bool? autoAccept,
bool? saveHistory,
bool? enableTls,
}) {
return SettingsState(
hostname: hostname ?? this.hostname,
savePath: savePath ?? this.savePath,
autoAccept: autoAccept ?? this.autoAccept,
saveHistory: saveHistory ?? this.saveHistory,
enableTls: enableTls ?? this.enableTls,
);
}
}
@riverpod
class SettingsController extends _$SettingsController {
@override
Future<SettingsState> build() async {
final values = await Future.wait([
commands.getHostname(),
commands.getSavePath(),
commands.getAutoAccept(),
commands.getSaveHistory(),
commands.getEnableTls(),
]);
return SettingsState(
hostname: values[0] as String,
savePath: values[1] as String,
autoAccept: values[2] as bool,
saveHistory: values[3] as bool,
enableTls: values[4] as bool,
);
}
Future<void> updateHostname(String value) async {
await commands.setHostname(hostname: value);
final current = state.value;
if (current != null) {
state = AsyncData(current.copyWith(hostname: value));
}
}
Future<void> updateSavePath(String value) async {
await commands.setSavePath(savePath: value);
final current = state.value;
if (current != null) {
state = AsyncData(current.copyWith(savePath: value));
}
}
Future<void> updateAutoAccept(bool value) async {
await commands.setAutoAccept(autoAccept: value);
final current = state.value;
if (current != null) {
state = AsyncData(current.copyWith(autoAccept: value));
}
}
Future<void> updateSaveHistory(bool value) async {
await commands.setSaveHistory(saveHistory: value);
final current = state.value;
if (current != null) {
state = AsyncData(current.copyWith(saveHistory: value));
}
}
Future<void> updateEnableTls(bool value) async {
await commands.setEnableTls(enableTls: value);
final current = state.value;
if (current != null) {
state = AsyncData(current.copyWith(enableTls: value));
}
}
}

View File

@@ -0,0 +1,55 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'settings_controller.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint, type=warning
@ProviderFor(SettingsController)
final settingsControllerProvider = SettingsControllerProvider._();
final class SettingsControllerProvider
extends $AsyncNotifierProvider<SettingsController, SettingsState> {
SettingsControllerProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'settingsControllerProvider',
isAutoDispose: true,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$settingsControllerHash();
@$internal
@override
SettingsController create() => SettingsController();
}
String _$settingsControllerHash() =>
r'9a56637cd6a41c05c9c35b78430ff9fd9f9affe6';
abstract class _$SettingsController extends $AsyncNotifier<SettingsState> {
FutureOr<SettingsState> build();
@$mustCallSuper
@override
void runBuild() {
final ref = this.ref as $Ref<AsyncValue<SettingsState>, SettingsState>;
final element =
ref.element
as $ClassProviderElement<
AnyNotifier<AsyncValue<SettingsState>, SettingsState>,
AsyncValue<SettingsState>,
Object?,
Object?
>;
element.handleCreate(ref, build);
}
}

View File

@@ -0,0 +1,161 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../../theme/theme_mode_controller.dart';
import '../controller/settings_controller.dart';
class SettingsPage extends ConsumerWidget {
const SettingsPage({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final settingsAsync = ref.watch(settingsControllerProvider);
final themeMode = ref.watch(themeModeControllerProvider);
return Scaffold(
appBar: AppBar(title: const Text('Settings')),
body: settingsAsync.when(
loading: () => const Center(child: CircularProgressIndicator()),
error: (error, _) => Center(child: Text(error.toString())),
data: (settings) {
return ListView(
padding: const EdgeInsets.all(16),
children: [
Card(
child: Column(
children: [
ListTile(
leading: const Icon(Icons.brightness_6_rounded),
title: const Text('主题模式'),
subtitle: Text(themeMode.name),
trailing: SegmentedButton<ThemeMode>(
segments: const [
ButtonSegment(
value: ThemeMode.light,
icon: Icon(Icons.light_mode_rounded),
label: Text('Light'),
),
ButtonSegment(
value: ThemeMode.dark,
icon: Icon(Icons.dark_mode_rounded),
label: Text('Dark'),
),
ButtonSegment(
value: ThemeMode.system,
icon: Icon(Icons.settings_suggest_rounded),
label: Text('System'),
),
],
selected: {themeMode},
onSelectionChanged: (selection) => ref
.read(themeModeControllerProvider.notifier)
.setThemeMode(selection.first),
),
),
],
),
),
const SizedBox(height: 12),
Card(
child: Column(
children: [
ListTile(
leading: const Icon(Icons.badge_rounded),
title: const Text('Hostname'),
subtitle: Text(settings.hostname),
trailing: IconButton(
icon: const Icon(Icons.edit_rounded),
onPressed: () => _editText(
context: context,
title: 'Hostname',
initial: settings.hostname,
onSubmit: (value) => ref
.read(settingsControllerProvider.notifier)
.updateHostname(value),
),
),
),
const Divider(height: 1),
ListTile(
leading: const Icon(Icons.folder_rounded),
title: const Text('默认保存路径'),
subtitle: Text(settings.savePath),
trailing: IconButton(
icon: const Icon(Icons.edit_rounded),
onPressed: () => _editText(
context: context,
title: '保存路径',
initial: settings.savePath,
onSubmit: (value) => ref
.read(settingsControllerProvider.notifier)
.updateSavePath(value),
),
),
),
const Divider(height: 1),
SwitchListTile.adaptive(
secondary: const Icon(Icons.auto_mode_rounded),
title: const Text('自动接收'),
value: settings.autoAccept,
onChanged: (v) => ref
.read(settingsControllerProvider.notifier)
.updateAutoAccept(v),
),
SwitchListTile.adaptive(
secondary: const Icon(Icons.history_rounded),
title: const Text('保存历史记录'),
value: settings.saveHistory,
onChanged: (v) => ref
.read(settingsControllerProvider.notifier)
.updateSaveHistory(v),
),
SwitchListTile.adaptive(
secondary: const Icon(Icons.shield_rounded),
title: const Text('启用 TLS'),
value: settings.enableTls,
onChanged: (v) => ref
.read(settingsControllerProvider.notifier)
.updateEnableTls(v),
),
],
),
),
],
);
},
),
);
}
Future<void> _editText({
required BuildContext context,
required String title,
required String initial,
required Future<void> Function(String value) onSubmit,
}) async {
final controller = TextEditingController(text: initial);
final confirmed = await showDialog<bool>(
context: context,
builder: (_) {
return AlertDialog(
title: Text(title),
content: TextField(controller: controller),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(false),
child: const Text('取消'),
),
FilledButton(
onPressed: () => Navigator.of(context).pop(true),
child: const Text('保存'),
),
],
);
},
);
if (confirmed == true) {
await onSubmit(controller.text.trim());
}
}
}