|
|
@@ -1,6 +1,7 @@
|
|
|
import 'package:flutter/material.dart';
|
|
|
import 'package:provider/provider.dart';
|
|
|
|
|
|
+import '../../core/theme/accent_choice.dart';
|
|
|
import '../../core/theme/app_colors.dart';
|
|
|
import '../../core/theme/theme_controller.dart';
|
|
|
import '../../core/theme/theme_preference.dart';
|
|
|
@@ -113,6 +114,30 @@ class SettingsScreen extends StatelessWidget {
|
|
|
color: colors.inkMuted,
|
|
|
),
|
|
|
),
|
|
|
+ const SizedBox(height: 8),
|
|
|
+ Divider(color: colors.border, height: 24),
|
|
|
+ Row(
|
|
|
+ children: [
|
|
|
+ Text(
|
|
|
+ 'Accent',
|
|
|
+ style: theme.textTheme.titleMedium,
|
|
|
+ ),
|
|
|
+ const Spacer(),
|
|
|
+ Text(
|
|
|
+ themeController.accent.label,
|
|
|
+ style: theme.textTheme.labelMedium?.copyWith(
|
|
|
+ color: colors.inkMuted,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ],
|
|
|
+ ),
|
|
|
+ const SizedBox(height: 10),
|
|
|
+ _AccentSelector(
|
|
|
+ selected: themeController.accent,
|
|
|
+ onSelected: (choice) {
|
|
|
+ themeController.setAccent(choice);
|
|
|
+ },
|
|
|
+ ),
|
|
|
if (themeController.preference ==
|
|
|
ThemePreference.automatic) ...[
|
|
|
const SizedBox(height: 8),
|
|
|
@@ -145,6 +170,13 @@ class SettingsScreen extends StatelessWidget {
|
|
|
),
|
|
|
),
|
|
|
],
|
|
|
+ Divider(color: colors.border, height: 24),
|
|
|
+ _BatteryToggleRow(
|
|
|
+ value: themeController.showBattery,
|
|
|
+ onChanged: (value) {
|
|
|
+ themeController.setShowBattery(value);
|
|
|
+ },
|
|
|
+ ),
|
|
|
Divider(color: colors.border, height: 30),
|
|
|
Row(
|
|
|
children: [
|
|
|
@@ -274,7 +306,7 @@ class SettingsScreen extends StatelessWidget {
|
|
|
const SizedBox(height: 28),
|
|
|
Center(
|
|
|
child: Text(
|
|
|
- 'CarDash 0.2.0 · Android 10+',
|
|
|
+ 'CarDash 0.3.0 · Android 10+',
|
|
|
style: theme.textTheme.bodySmall?.copyWith(
|
|
|
color: colors.inkFaint,
|
|
|
),
|
|
|
@@ -427,6 +459,156 @@ class _ThemeSelector extends StatelessWidget {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+class _AccentSelector extends StatelessWidget {
|
|
|
+ const _AccentSelector({required this.selected, required this.onSelected});
|
|
|
+
|
|
|
+ final AccentChoice selected;
|
|
|
+ final ValueChanged<AccentChoice> onSelected;
|
|
|
+
|
|
|
+ @override
|
|
|
+ Widget build(BuildContext context) {
|
|
|
+ final colors = context.colors;
|
|
|
+ final brightness = Theme.of(context).brightness;
|
|
|
+
|
|
|
+ return Wrap(
|
|
|
+ spacing: 10,
|
|
|
+ runSpacing: 10,
|
|
|
+ children: [
|
|
|
+ for (final choice in AccentChoice.values)
|
|
|
+ Semantics(
|
|
|
+ label: '${choice.label} accent',
|
|
|
+ selected: choice == selected,
|
|
|
+ button: true,
|
|
|
+ child: Material(
|
|
|
+ color: Colors.transparent,
|
|
|
+ shape: const CircleBorder(),
|
|
|
+ child: InkWell(
|
|
|
+ customBorder: const CircleBorder(),
|
|
|
+ onTap: () => onSelected(choice),
|
|
|
+ child: Padding(
|
|
|
+ padding: const EdgeInsets.all(3),
|
|
|
+ child: AnimatedContainer(
|
|
|
+ duration: const Duration(milliseconds: 180),
|
|
|
+ width: 30,
|
|
|
+ height: 30,
|
|
|
+ decoration: BoxDecoration(
|
|
|
+ shape: BoxShape.circle,
|
|
|
+ color: choice.accentFor(brightness),
|
|
|
+ border: Border.all(
|
|
|
+ color: choice == selected ? colors.ink : colors.border,
|
|
|
+ width: choice == selected ? 2 : 1,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ alignment: Alignment.center,
|
|
|
+ child: choice == selected
|
|
|
+ ? Icon(
|
|
|
+ Icons.check_rounded,
|
|
|
+ size: 16,
|
|
|
+ color: choice.inkFor(brightness),
|
|
|
+ )
|
|
|
+ : null,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ],
|
|
|
+ );
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+class _SettingsSwitch extends StatelessWidget {
|
|
|
+ const _SettingsSwitch({
|
|
|
+ required this.value,
|
|
|
+ required this.onChanged,
|
|
|
+ required this.semanticsLabel,
|
|
|
+ });
|
|
|
+
|
|
|
+ final bool value;
|
|
|
+ final ValueChanged<bool> onChanged;
|
|
|
+ final String semanticsLabel;
|
|
|
+
|
|
|
+ @override
|
|
|
+ Widget build(BuildContext context) {
|
|
|
+ final colors = context.colors;
|
|
|
+ return Semantics(
|
|
|
+ label: semanticsLabel,
|
|
|
+ child: Switch(
|
|
|
+ value: value,
|
|
|
+ onChanged: onChanged,
|
|
|
+ thumbColor: WidgetStateProperty.resolveWith<Color?>(
|
|
|
+ (states) => states.contains(WidgetState.selected)
|
|
|
+ ? colors.accentInk
|
|
|
+ : colors.inkFaint,
|
|
|
+ ),
|
|
|
+ trackColor: WidgetStateProperty.resolveWith<Color?>(
|
|
|
+ (states) => states.contains(WidgetState.selected)
|
|
|
+ ? colors.accent
|
|
|
+ : colors.surfaceStrong,
|
|
|
+ ),
|
|
|
+ trackOutlineColor: WidgetStatePropertyAll(colors.border),
|
|
|
+ ),
|
|
|
+ );
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+class _BatteryToggleRow extends StatelessWidget {
|
|
|
+ const _BatteryToggleRow({required this.value, required this.onChanged});
|
|
|
+
|
|
|
+ final bool value;
|
|
|
+ final ValueChanged<bool> onChanged;
|
|
|
+
|
|
|
+ @override
|
|
|
+ Widget build(BuildContext context) {
|
|
|
+ final colors = context.colors;
|
|
|
+ final theme = Theme.of(context);
|
|
|
+
|
|
|
+ return Row(
|
|
|
+ children: [
|
|
|
+ Container(
|
|
|
+ width: 38,
|
|
|
+ height: 38,
|
|
|
+ decoration: BoxDecoration(
|
|
|
+ color: colors.surfaceStrong,
|
|
|
+ borderRadius: BorderRadius.circular(13),
|
|
|
+ border: Border.all(color: colors.border),
|
|
|
+ ),
|
|
|
+ alignment: Alignment.center,
|
|
|
+ child: AppIcon(
|
|
|
+ 'assets/icons/battery-charging.svg',
|
|
|
+ size: 18,
|
|
|
+ color: colors.inkMuted,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ const SizedBox(width: 14),
|
|
|
+ Expanded(
|
|
|
+ child: Column(
|
|
|
+ crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
+ children: [
|
|
|
+ Text('Battery & charging', style: theme.textTheme.bodyLarge),
|
|
|
+ const SizedBox(height: 2),
|
|
|
+ Text(
|
|
|
+ 'Show the battery level and charging bolt.',
|
|
|
+ style: theme.textTheme.bodySmall?.copyWith(
|
|
|
+ color: colors.inkMuted,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ],
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ const SizedBox(width: 8),
|
|
|
+ _SettingsSwitch(
|
|
|
+ value: value,
|
|
|
+ semanticsLabel: value
|
|
|
+ ? 'Hide battery and charging status'
|
|
|
+ : 'Show battery and charging status',
|
|
|
+ onChanged: onChanged,
|
|
|
+ ),
|
|
|
+ ],
|
|
|
+ );
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
class _ActionRow extends StatelessWidget {
|
|
|
const _ActionRow({
|
|
|
required this.iconAsset,
|
|
|
@@ -572,31 +754,18 @@ class _ShortcutToggleRow extends StatelessWidget {
|
|
|
),
|
|
|
),
|
|
|
const SizedBox(width: 8),
|
|
|
- Semantics(
|
|
|
- label: enabled
|
|
|
+ _SettingsSwitch(
|
|
|
+ value: enabled,
|
|
|
+ semanticsLabel: enabled
|
|
|
? 'Hide ${slot.label} on Home'
|
|
|
: 'Show ${slot.label} on Home',
|
|
|
- child: Switch(
|
|
|
- value: enabled,
|
|
|
- onChanged: (value) async {
|
|
|
- final error = await launcher.setSlotEnabled(slot, value);
|
|
|
- if (error != null && context.mounted) {
|
|
|
- ScaffoldMessenger.of(context)
|
|
|
- .showSnackBar(SnackBar(content: Text(error)));
|
|
|
- }
|
|
|
- },
|
|
|
- thumbColor: WidgetStateProperty.resolveWith<Color?>(
|
|
|
- (states) => states.contains(WidgetState.selected)
|
|
|
- ? colors.accentInk
|
|
|
- : colors.inkFaint,
|
|
|
- ),
|
|
|
- trackColor: WidgetStateProperty.resolveWith<Color?>(
|
|
|
- (states) => states.contains(WidgetState.selected)
|
|
|
- ? colors.accent
|
|
|
- : colors.surfaceStrong,
|
|
|
- ),
|
|
|
- trackOutlineColor: WidgetStatePropertyAll(colors.border),
|
|
|
- ),
|
|
|
+ onChanged: (value) async {
|
|
|
+ final error = await launcher.setSlotEnabled(slot, value);
|
|
|
+ if (error != null && context.mounted) {
|
|
|
+ ScaffoldMessenger.of(context)
|
|
|
+ .showSnackBar(SnackBar(content: Text(error)));
|
|
|
+ }
|
|
|
+ },
|
|
|
),
|
|
|
],
|
|
|
);
|