home_page.dart 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323
  1. import 'package:flutter/material.dart';
  2. import 'package:go_router/go_router.dart';
  3. class HomePage extends StatelessWidget {
  4. const HomePage({super.key});
  5. @override
  6. Widget build(BuildContext context) {
  7. final theme = Theme.of(context);
  8. return Scaffold(
  9. body: DecoratedBox(
  10. decoration: const BoxDecoration(
  11. gradient: LinearGradient(
  12. begin: Alignment.topLeft,
  13. end: Alignment.bottomRight,
  14. colors: [Color(0xFFF3EDE0), Color(0xFFE9E0CF), Color(0xFFDDE8E0)],
  15. ),
  16. ),
  17. child: SafeArea(
  18. child: ListView(
  19. padding: const EdgeInsets.fromLTRB(20, 16, 20, 24),
  20. children: [
  21. Text(
  22. 'TC Mobile Lab',
  23. style: theme.textTheme.titleMedium?.copyWith(
  24. color: theme.colorScheme.primary,
  25. letterSpacing: 0.6,
  26. ),
  27. ),
  28. const SizedBox(height: 10),
  29. Text(
  30. 'Audio tricorder for structured signal analysis.',
  31. style: theme.textTheme.displaySmall,
  32. ),
  33. const SizedBox(height: 12),
  34. Text(
  35. 'Record a sample, let the probe chain inspect it, then hand the evidence to the AI analyst for hypothesis, validation, and conclusion.',
  36. style: theme.textTheme.bodyLarge,
  37. ),
  38. const SizedBox(height: 24),
  39. Card(
  40. child: Padding(
  41. padding: const EdgeInsets.all(22),
  42. child: Column(
  43. crossAxisAlignment: CrossAxisAlignment.start,
  44. children: [
  45. Text(
  46. 'Quick Start',
  47. style: theme.textTheme.headlineMedium,
  48. ),
  49. const SizedBox(height: 12),
  50. _SignalChip(
  51. label: 'Local probe chain',
  52. tone: theme.colorScheme.secondary,
  53. ),
  54. const SizedBox(height: 10),
  55. _SignalChip(
  56. label: 'Remote AI analyst',
  57. tone: theme.colorScheme.tertiary,
  58. ),
  59. const SizedBox(height: 18),
  60. Row(
  61. children: [
  62. Expanded(
  63. child: FilledButton(
  64. onPressed: () => context.goNamed('capture'),
  65. child: const Text('Start Recording'),
  66. ),
  67. ),
  68. const SizedBox(width: 12),
  69. Expanded(
  70. child: OutlinedButton(
  71. onPressed: () => context.goNamed('observation'),
  72. child: const Text('Import Sample'),
  73. ),
  74. ),
  75. ],
  76. ),
  77. ],
  78. ),
  79. ),
  80. ),
  81. const SizedBox(height: 18),
  82. Row(
  83. children: [
  84. Expanded(
  85. child: _StatusCard(
  86. title: 'Current Mode',
  87. value: 'Idle',
  88. note: 'Ready for local probe',
  89. accent: theme.colorScheme.primary,
  90. ),
  91. ),
  92. const SizedBox(width: 14),
  93. Expanded(
  94. child: _StatusCard(
  95. title: 'Pipeline',
  96. value: 'Probe',
  97. note: 'No active experiment',
  98. accent: theme.colorScheme.secondary,
  99. ),
  100. ),
  101. ],
  102. ),
  103. const SizedBox(height: 18),
  104. Card(
  105. child: Padding(
  106. padding: const EdgeInsets.all(22),
  107. child: Column(
  108. crossAxisAlignment: CrossAxisAlignment.start,
  109. children: [
  110. Text('Lab Sections', style: theme.textTheme.titleLarge),
  111. const SizedBox(height: 14),
  112. Wrap(
  113. spacing: 10,
  114. runSpacing: 10,
  115. children: [
  116. _NavChip(
  117. label: 'Observation',
  118. onTap: () => context.goNamed('observation'),
  119. ),
  120. _NavChip(
  121. label: 'Experiment',
  122. onTap: () => context.goNamed('experiment'),
  123. ),
  124. _NavChip(
  125. label: 'Conclusion',
  126. onTap: () => context.goNamed('conclusion'),
  127. ),
  128. _NavChip(
  129. label: 'History',
  130. onTap: () => context.goNamed('history'),
  131. ),
  132. _NavChip(
  133. label: 'Settings',
  134. onTap: () => context.goNamed('settings'),
  135. ),
  136. ],
  137. ),
  138. ],
  139. ),
  140. ),
  141. ),
  142. const SizedBox(height: 18),
  143. Card(
  144. child: Padding(
  145. padding: const EdgeInsets.all(22),
  146. child: Column(
  147. crossAxisAlignment: CrossAxisAlignment.start,
  148. children: [
  149. Text('First MVP flow', style: theme.textTheme.titleLarge),
  150. const SizedBox(height: 16),
  151. const _FlowStep(
  152. index: '01',
  153. title: 'Capture Observation',
  154. body:
  155. 'Record or import audio and store the observation metadata.',
  156. ),
  157. const _FlowStep(
  158. index: '02',
  159. title: 'Run Probe Chain',
  160. body:
  161. 'Generate onset, spectral, and periodicity evidence on device.',
  162. ),
  163. const _FlowStep(
  164. index: '03',
  165. title: 'Ask AI Analyst',
  166. body:
  167. 'Send structured evidence for hypothesis and experiment planning.',
  168. ),
  169. const _FlowStep(
  170. index: '04',
  171. title: 'Review Conclusion',
  172. body:
  173. 'Inspect findings, contradictions, and next-step suggestions.',
  174. ),
  175. ],
  176. ),
  177. ),
  178. ),
  179. ],
  180. ),
  181. ),
  182. ),
  183. );
  184. }
  185. }
  186. class _NavChip extends StatelessWidget {
  187. const _NavChip({required this.label, required this.onTap});
  188. final String label;
  189. final VoidCallback onTap;
  190. @override
  191. Widget build(BuildContext context) {
  192. return ActionChip(
  193. label: Text(label),
  194. onPressed: onTap,
  195. avatar: const Icon(Icons.arrow_outward, size: 18),
  196. );
  197. }
  198. }
  199. class _StatusCard extends StatelessWidget {
  200. const _StatusCard({
  201. required this.title,
  202. required this.value,
  203. required this.note,
  204. required this.accent,
  205. });
  206. final String title;
  207. final String value;
  208. final String note;
  209. final Color accent;
  210. @override
  211. Widget build(BuildContext context) {
  212. final theme = Theme.of(context);
  213. return Card(
  214. child: Padding(
  215. padding: const EdgeInsets.all(18),
  216. child: Column(
  217. crossAxisAlignment: CrossAxisAlignment.start,
  218. children: [
  219. Container(
  220. width: 14,
  221. height: 14,
  222. decoration: BoxDecoration(
  223. color: accent,
  224. borderRadius: BorderRadius.circular(999),
  225. ),
  226. ),
  227. const SizedBox(height: 12),
  228. Text(title, style: theme.textTheme.titleMedium),
  229. const SizedBox(height: 6),
  230. Text(value, style: theme.textTheme.headlineMedium),
  231. const SizedBox(height: 6),
  232. Text(note, style: theme.textTheme.bodyMedium),
  233. ],
  234. ),
  235. ),
  236. );
  237. }
  238. }
  239. class _SignalChip extends StatelessWidget {
  240. const _SignalChip({required this.label, required this.tone});
  241. final String label;
  242. final Color tone;
  243. @override
  244. Widget build(BuildContext context) {
  245. return Container(
  246. padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
  247. decoration: BoxDecoration(
  248. color: tone.withValues(alpha: 0.12),
  249. borderRadius: BorderRadius.circular(999),
  250. ),
  251. child: Text(
  252. label,
  253. style: Theme.of(context).textTheme.titleMedium?.copyWith(color: tone),
  254. ),
  255. );
  256. }
  257. }
  258. class _FlowStep extends StatelessWidget {
  259. const _FlowStep({
  260. required this.index,
  261. required this.title,
  262. required this.body,
  263. });
  264. final String index;
  265. final String title;
  266. final String body;
  267. @override
  268. Widget build(BuildContext context) {
  269. final theme = Theme.of(context);
  270. return Padding(
  271. padding: const EdgeInsets.only(bottom: 16),
  272. child: Row(
  273. crossAxisAlignment: CrossAxisAlignment.start,
  274. children: [
  275. Container(
  276. width: 44,
  277. height: 44,
  278. alignment: Alignment.center,
  279. decoration: BoxDecoration(
  280. color: theme.colorScheme.primary,
  281. borderRadius: BorderRadius.circular(16),
  282. ),
  283. child: Text(
  284. index,
  285. style: theme.textTheme.titleMedium?.copyWith(color: Colors.white),
  286. ),
  287. ),
  288. const SizedBox(width: 14),
  289. Expanded(
  290. child: Padding(
  291. padding: const EdgeInsets.only(top: 2),
  292. child: Column(
  293. crossAxisAlignment: CrossAxisAlignment.start,
  294. children: [
  295. Text(title, style: theme.textTheme.titleMedium),
  296. const SizedBox(height: 4),
  297. Text(body, style: theme.textTheme.bodyMedium),
  298. ],
  299. ),
  300. ),
  301. ),
  302. ],
  303. ),
  304. );
  305. }
  306. }