mirror of
https://github.com/kuhyx/wake-alarm.git
synced 2026-11-11 12:07:31 +01:00
phone_app: warm-tint the unified-design-system palette
Adds theme.dart (dark-only ColorScheme + AppStatusColors success/warning extension), wired into main.dart, home_screen.dart, and settings_screen.dart. Fixes DESIGN_AUDIT_TODO violations: raw Colors.* replaced with theme roles, off-grid spacing, sub-16px text, and icon-contrast dimming.
This commit is contained in:
parent
870a71665d
commit
191fa48b9d
@ -1,6 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:wake_alarm_sync/screens/home_screen.dart';
|
||||
import 'package:wake_alarm_sync/ui/theme.dart';
|
||||
|
||||
/// Entry point.
|
||||
void main() => runApp(const WakeAlarmSyncApp());
|
||||
@ -14,12 +15,7 @@ class WakeAlarmSyncApp extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
title: 'Wake Alarm Sync',
|
||||
theme: ThemeData.dark(useMaterial3: true).copyWith(
|
||||
colorScheme: ColorScheme.fromSeed(
|
||||
seedColor: Colors.orange,
|
||||
brightness: Brightness.dark,
|
||||
),
|
||||
),
|
||||
theme: buildAppTheme(),
|
||||
home: const HomeScreen(),
|
||||
);
|
||||
}
|
||||
|
||||
@ -6,6 +6,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:wake_alarm_sync/screens/settings_screen.dart';
|
||||
import 'package:wake_alarm_sync/services/alarm_sync_service.dart';
|
||||
import 'package:wake_alarm_sync/services/sync_settings.dart';
|
||||
import 'package:wake_alarm_sync/ui/theme.dart';
|
||||
|
||||
/// Main screen: displays the current alarm time and syncs it to the PC via
|
||||
/// the shared crdt_sync transport.
|
||||
@ -131,7 +132,11 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||
title: const Text('Wake Alarm Sync'),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.settings),
|
||||
icon: Icon(
|
||||
Icons.settings,
|
||||
// Dimmed relative to the adjacent title text (rule 28).
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
onPressed: () async {
|
||||
await Navigator.push<void>(
|
||||
context,
|
||||
@ -195,16 +200,22 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: _status.contains('Error')
|
||||
? Colors.redAccent
|
||||
: Colors.greenAccent,
|
||||
? Theme.of(context).colorScheme.error
|
||||
: Theme.of(
|
||||
context,
|
||||
).extension<AppStatusColors>()!.success,
|
||||
),
|
||||
),
|
||||
if (!_configured) ...[
|
||||
const SizedBox(height: 16),
|
||||
const Text(
|
||||
Text(
|
||||
'Open Settings (gear icon) to connect GitHub.',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(color: Colors.orange),
|
||||
style: TextStyle(
|
||||
color: Theme.of(
|
||||
context,
|
||||
).extension<AppStatusColors>()!.warning,
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
|
||||
@ -6,6 +6,7 @@ import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
import 'package:wake_alarm_sync/services/github_device_auth.dart';
|
||||
import 'package:wake_alarm_sync/services/sync_settings.dart';
|
||||
import 'package:wake_alarm_sync/ui/theme.dart';
|
||||
|
||||
/// Settings screen for the GitHub connection used to sync the alarm.
|
||||
///
|
||||
@ -108,7 +109,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
: const Icon(Icons.link),
|
||||
label: const Text('Connect GitHub'),
|
||||
style: ElevatedButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(vertical: 14),
|
||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||
textStyle: const TextStyle(fontSize: 16),
|
||||
),
|
||||
)
|
||||
@ -124,18 +125,26 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
_status,
|
||||
style: TextStyle(
|
||||
color: _status.contains('Error')
|
||||
? Colors.redAccent
|
||||
: Colors.greenAccent,
|
||||
? Theme.of(context).colorScheme.error
|
||||
: Theme.of(
|
||||
context,
|
||||
).extension<AppStatusColors>()!.success,
|
||||
),
|
||||
),
|
||||
const Divider(height: 32),
|
||||
const Text(
|
||||
Text(
|
||||
'PC config — create this file:\n'
|
||||
'~/.config/wake_alarm/sync.json\n\n'
|
||||
'{ "token": "<same token>" }\n\n'
|
||||
'The token needs "repo" scope so the PC can read '
|
||||
'kuhyx/syncs at shutdown.',
|
||||
style: TextStyle(fontSize: 12, color: Colors.grey),
|
||||
// Was 12px/Colors.grey — below the 16px reading floor (rule
|
||||
// 20) and off-palette (rule 4); this is real setup
|
||||
// instructions, not decorative chrome.
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
@ -151,6 +160,8 @@ class _StatusCard extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
final success = Theme.of(context).extension<AppStatusColors>()!.success;
|
||||
return Card(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
@ -158,9 +169,11 @@ class _StatusCard extends StatelessWidget {
|
||||
children: [
|
||||
Icon(
|
||||
connected ? Icons.check_circle : Icons.cancel,
|
||||
color: connected ? Colors.greenAccent : Colors.redAccent,
|
||||
// Dimmed relative to the adjacent label text (rule 28).
|
||||
color: (connected ? success : colorScheme.error)
|
||||
.withValues(alpha: 0.8),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
connected ? 'Connected to GitHub' : 'Not connected',
|
||||
@ -242,7 +255,10 @@ class _DeviceCodeDialogState extends State<_DeviceCodeDialog> {
|
||||
],
|
||||
)
|
||||
else
|
||||
Text(_error!, style: const TextStyle(color: Colors.red)),
|
||||
Text(
|
||||
_error!,
|
||||
style: TextStyle(color: Theme.of(context).colorScheme.error),
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
|
||||
86
phone_app/lib/ui/theme.dart
Normal file
86
phone_app/lib/ui/theme.dart
Normal file
@ -0,0 +1,86 @@
|
||||
/// The app's design tokens: one identical palette shared across every one
|
||||
/// of kuhy's apps (Flutter, web, Python/Tkinter alike) — see the
|
||||
/// `unified-design-system` skill (`~/.claude/skills/unified-design-system/`)
|
||||
/// for the frozen token table this file implements. Built from an explicit
|
||||
/// `ColorScheme`, not `ColorScheme.fromSeed`, because the shared palette is
|
||||
/// hand-picked, not algorithmically derived from one seed color.
|
||||
library;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// Builds the app's single dark `ThemeData` from the shared token set.
|
||||
ThemeData buildAppTheme() {
|
||||
const colorScheme = ColorScheme.dark(
|
||||
surface: Color(0xFF211D1B), // ink
|
||||
surfaceContainerHighest: Color(0xFF38312E), // ink-raised-2
|
||||
surfaceContainerHigh: Color(0xFF2B2624), // ink-raised-1
|
||||
onSurface: Color(0xFFECEAE9), // text-on-dark
|
||||
onSurfaceVariant: Color(0xFFAAA09A), // muted-on-dark
|
||||
outline: Color(0xFF463E3A), // line-dark
|
||||
primary: Color(0xFFB8862E), // accent
|
||||
onPrimary: Color(0xFF211D1B), // on-fill — filled surfaces use dark text
|
||||
// secondary/tertiary mirror primary — the shared palette has one accent,
|
||||
// not a separate secondary hue. Leaving these undefined lets Flutter
|
||||
// fall back to stock Material teal on any widget that reaches for
|
||||
// secondaryContainer (Chip, SegmentedButton, etc.) — confirmed as a
|
||||
// real, live bug on two other apps sharing this template.
|
||||
secondary: Color(0xFFB8862E),
|
||||
onSecondary: Color(0xFF211D1B),
|
||||
secondaryContainer: Color(0xFF463E3A),
|
||||
onSecondaryContainer: Color(0xFFB8862E),
|
||||
tertiary: Color(0xFFB8862E),
|
||||
onTertiary: Color(0xFF211D1B),
|
||||
tertiaryContainer: Color(0xFF463E3A),
|
||||
onTertiaryContainer: Color(0xFFB8862E),
|
||||
error: Color(0xFFE2585F), // danger
|
||||
onError: Color(0xFF211D1B), // on-fill
|
||||
);
|
||||
return ThemeData(
|
||||
useMaterial3: true,
|
||||
colorScheme: colorScheme,
|
||||
scaffoldBackgroundColor: colorScheme.surface,
|
||||
extensions: const [AppStatusColors.dark],
|
||||
// No custom shadow/elevation exists elsewhere in this app — the one
|
||||
// Card (_StatusCard) is the sole place M3's default elevation would
|
||||
// otherwise introduce a shadow in an all-dark UI (rule 26).
|
||||
cardTheme: const CardThemeData(elevation: 0),
|
||||
);
|
||||
}
|
||||
|
||||
/// Semantic status colors M3's [ColorScheme] has no role for (it only has
|
||||
/// `error`). Used instead of the ad hoc `Colors.greenAccent`/`Colors.orange`
|
||||
/// literals previously scattered across both screens.
|
||||
@immutable
|
||||
class AppStatusColors extends ThemeExtension<AppStatusColors> {
|
||||
/// Creates a status-color set.
|
||||
const AppStatusColors({required this.success, required this.warning});
|
||||
|
||||
/// The shared dark-theme instance — success/warning from the unified
|
||||
/// palette (danger already exists as `colorScheme.error`).
|
||||
static const dark = AppStatusColors(
|
||||
success: Color(0xFF8A9A3C),
|
||||
warning: Color(0xFFE0A63C),
|
||||
);
|
||||
|
||||
/// Positive/connected status (e.g. "Connected to GitHub").
|
||||
final Color success;
|
||||
|
||||
/// Caution/pending status (e.g. "Open Settings to connect GitHub").
|
||||
final Color warning;
|
||||
|
||||
@override
|
||||
AppStatusColors copyWith({Color? success, Color? warning}) =>
|
||||
AppStatusColors(
|
||||
success: success ?? this.success,
|
||||
warning: warning ?? this.warning,
|
||||
);
|
||||
|
||||
@override
|
||||
AppStatusColors lerp(AppStatusColors? other, double t) {
|
||||
if (other is! AppStatusColors) return this;
|
||||
return AppStatusColors(
|
||||
success: Color.lerp(success, other.success, t) ?? success,
|
||||
warning: Color.lerp(warning, other.warning, t) ?? warning,
|
||||
);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user