mirror of
https://github.com/kuhyx/wake-alarm.git
synced 2026-11-10 19:27:02 +01:00
- Sick days screen (own name, sick icon, cat-2 fill): tap today or a later day to mark it sick, tap again to clear; past days inert. Writes the sickdays wire format (state sick/cleared + actor, one HLC per write) to sickdays-sync/devices via crdt_sync syncLog. - Mutual exclusion in both pickers: each syncs the other pool on open and refuses a date it holds, naming the pool; an unreadable pool refuses all. - Native SickDays cache (fail closed via DayCache); AlarmReceiver skips the ring with reason "sick_day" (no markFired, no ring_started); StickCheck verdict sick_day, streak skips sick days; stakes card says so. - Pre-alarm pool sync: allow-while-idle alarm at 21:00 and A-30 (request code 1006) runs a headless engine (poolSyncMain) that pulls both pools from RTDB and rewrites both native caches, so a PC mark arrives with the app closed. The ring decision stays a cache read. - JVM unit tests (JUnit 4.13.2) for DayCache, RingDecision, PoolSyncSchedule, StickCheck; Dart tests; Dart->Python interop check. pylint hook skipped: verify.py imports sickdays, not in the hook env until the sickdays-v0.1.0 tag exists; run by hand in a sickdays venv (10.00/10). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xfj1sxKH9VBPqpcAni8Jry
124 lines
4.7 KiB
Dart
124 lines
4.7 KiB
Dart
/// 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, DayMarkColors.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,
|
|
);
|
|
}
|
|
}
|
|
|
|
/// The fill of a marked day on the two day-pool calendars.
|
|
///
|
|
/// Free days keep `accent` (the theme's primary). Sick days take `cat-2`
|
|
/// from the shared categorical ramp: hue-separated from the warm accent /
|
|
/// success / warning / danger wedge by design, so a sick day can never be
|
|
/// read as a free day (accent), a warning or an error. Text on it is
|
|
/// `on-fill` (`ink`, 4.7:1), never `text-on-dark` (3.0:1).
|
|
@immutable
|
|
class DayMarkColors extends ThemeExtension<DayMarkColors> {
|
|
/// Creates a day-mark color set.
|
|
const DayMarkColors({required this.sick, required this.onSick});
|
|
|
|
/// The shared dark-theme instance.
|
|
static const dark = DayMarkColors(
|
|
sick: Color(0xFF398FC0), // cat-2
|
|
onSick: Color(0xFF211D1B), // on-fill
|
|
);
|
|
|
|
/// A sick day's fill.
|
|
final Color sick;
|
|
|
|
/// Text on [sick].
|
|
final Color onSick;
|
|
|
|
@override
|
|
DayMarkColors copyWith({Color? sick, Color? onSick}) =>
|
|
DayMarkColors(sick: sick ?? this.sick, onSick: onSick ?? this.onSick);
|
|
|
|
@override
|
|
DayMarkColors lerp(DayMarkColors? other, double t) {
|
|
if (other is! DayMarkColors) return this;
|
|
return DayMarkColors(
|
|
sick: Color.lerp(sick, other.sick, t) ?? sick,
|
|
onSick: Color.lerp(onSick, other.onSick, t) ?? onSick,
|
|
);
|
|
}
|
|
}
|