Add accent colour picker, fix empty user card when accepting friend request, add button to transfer allocations
All checks were successful
Release / meta (push) Successful in 8s
Release / linux-build (push) Successful in 56s
Release / web-build (push) Successful in 2m15s
Release / android-build (push) Successful in 6m47s
Release / release-master (push) Successful in 19s
Release / release-dev (push) Successful in 21s

This commit is contained in:
2026-01-06 00:21:19 +00:00
parent d5083e1cc7
commit 06bed86a49
11 changed files with 427 additions and 45 deletions

View File

@@ -3,8 +3,10 @@ import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:http/http.dart' as http;
import 'package:mileograph_flutter/services/accent_color_service.dart';
import 'package:mileograph_flutter/services/distance_unit_service.dart';
import 'package:mileograph_flutter/services/endpoint_service.dart';
import 'package:mileograph_flutter/services/theme_mode_service.dart';
import 'package:mileograph_flutter/services/data_service.dart';
import 'package:provider/provider.dart';
@@ -18,6 +20,18 @@ class SettingsPage extends StatefulWidget {
class _SettingsPageState extends State<SettingsPage> {
late final TextEditingController _endpointController;
bool _saving = false;
static const List<Color> _accentPalette = [
Colors.red,
Colors.pink,
Colors.orange,
Colors.amber,
Colors.green,
Colors.teal,
Colors.blue,
Colors.indigo,
Colors.purple,
Colors.cyan,
];
@override
void initState() {
@@ -130,7 +144,12 @@ class _SettingsPageState extends State<SettingsPage> {
Widget build(BuildContext context) {
final endpointService = context.watch<EndpointService>();
final distanceUnitService = context.watch<DistanceUnitService>();
if (!endpointService.isLoaded || !distanceUnitService.isLoaded) {
final accentService = context.watch<AccentColorService>();
final themeModeService = context.watch<ThemeModeService>();
if (!endpointService.isLoaded ||
!distanceUnitService.isLoaded ||
!accentService.isLoaded ||
!themeModeService.isLoaded) {
return const Scaffold(
body: Center(child: CircularProgressIndicator()),
);
@@ -184,6 +203,73 @@ class _SettingsPageState extends State<SettingsPage> {
},
),
const SizedBox(height: 24),
Text(
'Accent colour',
style: Theme.of(context).textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.w700,
),
),
const SizedBox(height: 8),
Text(
'Choose your preferred accent colour or use system colours.',
style: Theme.of(context).textTheme.bodySmall,
),
const SizedBox(height: 12),
Wrap(
spacing: 12,
runSpacing: 8,
crossAxisAlignment: WrapCrossAlignment.center,
children: [
OutlinedButton.icon(
onPressed:
accentService.useSystem ? null : () => accentService.setUseSystem(true),
icon: const Icon(Icons.phone_android),
label: const Text('Use system colours'),
),
..._accentPalette.map(
(color) => _AccentSwatchButton(
color: color,
selected:
!accentService.useSystem &&
accentService.seedColor.toARGB32() == color.toARGB32(),
onTap: () => accentService.setSeedColor(color),
),
),
],
),
const SizedBox(height: 24),
Text(
'Theme mode',
style: Theme.of(context).textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.w700,
),
),
const SizedBox(height: 8),
SegmentedButton<ThemeMode>(
segments: const [
ButtonSegment(
value: ThemeMode.system,
icon: Icon(Icons.settings_suggest),
label: Text('System'),
),
ButtonSegment(
value: ThemeMode.light,
icon: Icon(Icons.light_mode),
label: Text('Light'),
),
ButtonSegment(
value: ThemeMode.dark,
icon: Icon(Icons.dark_mode),
label: Text('Dark'),
),
],
selected: {themeModeService.mode},
onSelectionChanged: (selection) {
final mode = selection.first;
themeModeService.setMode(mode);
},
),
const SizedBox(height: 24),
Text(
'API endpoint',
style: Theme.of(context).textTheme.titleMedium?.copyWith(
@@ -241,3 +327,54 @@ class _SettingsPageState extends State<SettingsPage> {
);
}
}
class _AccentSwatchButton extends StatelessWidget {
const _AccentSwatchButton({
required this.color,
required this.selected,
required this.onTap,
});
final Color color;
final bool selected;
final VoidCallback onTap;
@override
Widget build(BuildContext context) {
final borderColor = selected
? Theme.of(context).colorScheme.onSurface
: Colors.black26;
return InkWell(
onTap: onTap,
customBorder: const CircleBorder(),
child: Container(
width: 40,
height: 40,
decoration: BoxDecoration(
color: color,
shape: BoxShape.circle,
border: Border.all(
color: borderColor,
width: selected ? 3 : 1,
),
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.15),
blurRadius: 4,
offset: const Offset(0, 2),
),
],
),
child: selected
? const Center(
child: Icon(
Icons.check,
size: 18,
color: Colors.white,
),
)
: null,
),
);
}
}