import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../core/providers/model_download_provider.dart'; class ModelDownloadScreen extends ConsumerWidget { const ModelDownloadScreen({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { final scanState = ref.watch(modelScanProvider); return Scaffold( body: SafeArea( child: Padding( padding: const EdgeInsets.all(32), child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Icon( Icons.auto_awesome, size: 64, color: Theme.of(context).colorScheme.primary, ), const SizedBox(height: 24), Text( 'On-device AI', style: Theme.of(context).textTheme.headlineSmall, textAlign: TextAlign.center, ), const SizedBox(height: 12), Text( 'DailyYou uses an on-device AI model to spot patterns in your ' 'moods and behaviour. Your data never leaves your phone.', style: Theme.of(context).textTheme.bodyMedium?.copyWith( color: Theme.of(context).colorScheme.onSurfaceVariant, ), textAlign: TextAlign.center, ), const SizedBox(height: 32), _InstructionCard(), const SizedBox(height: 24), if (scanState is AsyncLoading) const Center(child: CircularProgressIndicator()) else ...[ if (scanState is AsyncError) Padding( padding: const EdgeInsets.only(bottom: 12), child: Text( 'Model not found at expected path.', style: Theme.of(context).textTheme.bodySmall?.copyWith( color: Theme.of(context).colorScheme.error, ), textAlign: TextAlign.center, ), ), FilledButton.icon( onPressed: () => ref.read(modelScanProvider.notifier).scan(), icon: const Icon(Icons.search), label: const Text('Scan for model'), ), const SizedBox(height: 12), OutlinedButton( onPressed: () => ref .read(modelScanProvider.notifier) .skipToApp(), child: const Text('Continue without AI'), ), ], ], ), ), ), ); } } class _InstructionCard extends StatelessWidget { @override Widget build(BuildContext context) { final cs = Theme.of(context).colorScheme; return Card( child: Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('How to get the model', style: Theme.of(context).textTheme.titleSmall), const SizedBox(height: 10), _Step( n: '1', text: 'Download gemma-2b-it-cpu-int4.bin from Kaggle ' '(requires a free Google account and licence acceptance).', ), _Step( n: '2', text: 'Transfer the file to the device using adb:\n' 'adb push gemma-2b-it-cpu-int4.bin ' r'$(adb shell run-as net.stefwill.dailyou ' r'printenv DATA_DIR)/files/', ), _Step( n: '3', text: 'Tap "Scan for model" once the transfer is complete.', ), const SizedBox(height: 8), Text( 'The model is ~1.3 GB and only needs to be transferred once.', style: Theme.of(context).textTheme.labelSmall?.copyWith( color: cs.outline, ), ), ], ), ), ); } } class _Step extends StatelessWidget { const _Step({required this.n, required this.text}); final String n; final String text; @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.only(bottom: 8), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ CircleAvatar( radius: 10, backgroundColor: Theme.of(context).colorScheme.primaryContainer, child: Text(n, style: Theme.of(context).textTheme.labelSmall?.copyWith( color: Theme.of(context).colorScheme.onPrimaryContainer, )), ), const SizedBox(width: 10), Expanded( child: Text(text, style: Theme.of(context).textTheme.bodySmall), ), ], ), ); } }