Files
app-flutter/lib/pages/settings.dart
T
mikkeli 317de3425a
/ crosscheck (push) Successful in 1s
CI / Analyze & Test (push) Failing after 5s
CI / Debug APK (push) Has been skipped
Checklist v4 from contracts: step 31, each wheel by hand
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DDwfd6Ki8UfG4hK6W5YpJz
2026-10-15 22:30:40 +09:00

123 lines
4.2 KiB
Dart

// Settings: the few things that are chosen, gathered in one place. Build-time choices
// are shown so it is clear where they come from, not edited here.
import 'package:flutter/material.dart';
import '../actions/server_url.dart';
import '../link/boundary.dart';
import '../link/net.dart';
import '../shell_scope.dart';
import '../theme/light.dart';
import '../theme/palette.dart';
import '../theme/sun.dart';
class SettingsPage extends StatelessWidget {
const SettingsPage({
super.key,
required this.boundary,
required this.usb,
required this.themes,
});
final Boundary boundary;
final UsbLink usb;
final Themes themes;
@override
Widget build(BuildContext context) {
final p = AppTheme.of(context);
Widget section(String t) => Padding(
padding: const EdgeInsets.fromLTRB(16, 20, 16, 4),
child: Text(
t,
style: Theme.of(context).textTheme.labelLarge
?.copyWith(color: p.subdued),
),
);
return ListenableBuilder(
listenable: Listenable.merge([boundary, themes]),
builder: (context, _) => Scaffold(
appBar: AppBar(
leading: const MenuButton(),
title: const Text('Settings'),
),
body: ListView(
children: [
section('Connection'),
ListTile(
leading: const Icon(Icons.lan),
title: const Text('Gateway address'),
subtitle: Text(
boundary.url,
style: const TextStyle(fontFamily: 'monospace'),
),
trailing: const Icon(Icons.edit),
onTap: () => editUrl(context, boundary, usb),
),
section('Display'),
ListTile(
leading: Icon(themes.isDark ? Icons.dark_mode : Icons.light_mode),
title: const Text('Dark or bright'),
subtitle: ValueListenableBuilder(
valueListenable: themes.lux,
builder: (context, lux, _) {
final sun = themes.sunElevationNow;
final now = lux != null
? 'now ${lux.toStringAsFixed(lux < 10 ? 1 : 0)} lux; dark at '
'${LightSwitch.darkAt.round()} or less, bright from ${LightSwitch.brightAt.round()}'
: sun != null
? 'now ${sun.round()}° above the horizon at the last GNSS position; '
'dark below ${darkBelowDeg.round()}°'
: 'no GNSS position yet';
return Text(
'Auto follows ${themes.autoSource} ($now). '
'The status bar button holds one; a long press there is auto again.',
);
},
),
),
Padding(
padding: const EdgeInsets.fromLTRB(72, 0, 16, 8),
child: SegmentedButton<VariantMode>(
segments: const [
ButtonSegment(
value: VariantMode.auto,
icon: Icon(Icons.brightness_auto),
label: Text('Auto'),
),
ButtonSegment(
value: VariantMode.dark,
icon: Icon(Icons.dark_mode),
label: Text('Dark'),
),
ButtonSegment(
value: VariantMode.bright,
icon: Icon(Icons.light_mode),
label: Text('Bright'),
),
],
selected: {themes.mode},
showSelectedIcon: false,
onSelectionChanged: (s) => themes.mode = s.single,
),
),
ListTile(
leading: const Icon(Icons.palette),
title: const Text('Theme'),
subtitle: Text(
'${themes.name} · set at build time: --dart-define=YUKART_THEME=<name>',
),
),
ListTile(
leading: const Icon(Icons.home),
title: const Text('Landing view'),
subtitle: Text(
'${landingView.name} · set at build time: --dart-define=YUKART_HOME=dash|signals',
),
),
],
),
),
);
}
}