feat: wire up app shell, log mood screen, and fix Android build

- Bootstrap ProviderScope in main.dart and move App to app.dart with light/dark theming
- Add initial LogMoodScreen with mood entry flow and shared activity/mood widgets
- Add kIsWeb guard in app_database.dart for drift web support
- Enable Android core library desugaring required by flutter_local_notifications 21.x
- Guard all async notifier state assignments with ref.mounted checks to prevent
  use-after-dispose errors when providers are auto-disposed mid-operation
This commit is contained in:
2026-04-26 12:00:14 +10:00
parent 33d1713f51
commit 59597c7a4f
12 changed files with 337 additions and 22 deletions
+154
View File
@@ -0,0 +1,154 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../core/providers/mood_provider.dart';
import '../../core/providers/activity_provider.dart';
import '../../shared/widgets/mood_selector.dart';
import '../../shared/widgets/activity_grid.dart';
class LogMoodScreen extends ConsumerStatefulWidget {
const LogMoodScreen({super.key});
@override
ConsumerState<LogMoodScreen> createState() => _LogMoodScreenState();
}
class _LogMoodScreenState extends ConsumerState<LogMoodScreen> {
int _selectedMood = 3;
final _noteController = TextEditingController();
bool _saving = false;
static const _moodLabels = ['Awful', 'Bad', 'Okay', 'Good', 'Great'];
static const _moodEmojis = ['😞', '😔', '😐', '😊', '😄'];
static const _moodColors = [
Color(0xFFE57373),
Color(0xFFFFB74D),
Color(0xFFFFD54F),
Color(0xFF81C784),
Color(0xFF4DB6AC),
];
@override
void dispose() {
_noteController.dispose();
super.dispose();
}
Future<void> _save() async {
setState(() => _saving = true);
final selectedActivities = ref.read(selectedActivitiesProvider);
await ref.read(moodEntryNotifierProvider.notifier).addEntry(
moodLevel: _selectedMood,
note: _noteController.text.trim().isEmpty
? null
: _noteController.text.trim(),
activityIds: selectedActivities.toList(),
);
if (mounted) {
ref.read(selectedActivitiesProvider.notifier).clear();
_noteController.clear();
setState(() {
_saving = false;
_selectedMood = 3;
});
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Mood logged!')),
);
}
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final moodColor = _moodColors[_selectedMood - 1];
return Scaffold(
backgroundColor: theme.colorScheme.surface,
appBar: AppBar(
title: const Text('How are you feeling?'),
centerTitle: true,
backgroundColor: Colors.transparent,
elevation: 0,
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// ── Mood selector ──────────────────────────────────────
Center(
child: MoodSelector(
selected: _selectedMood,
emojis: _moodEmojis,
labels: _moodLabels,
colors: _moodColors,
onChanged: (v) => setState(() => _selectedMood = v),
),
),
const SizedBox(height: 32),
// ── Current mood label ─────────────────────────────────
Center(
child: AnimatedSwitcher(
duration: const Duration(milliseconds: 200),
child: Text(
_moodLabels[_selectedMood - 1],
key: ValueKey(_selectedMood),
style: theme.textTheme.headlineMedium?.copyWith(
color: moodColor,
fontWeight: FontWeight.w600,
),
),
),
),
const SizedBox(height: 32),
// ── Activities ─────────────────────────────────────────
Text('Activities', style: theme.textTheme.titleMedium),
const SizedBox(height: 12),
const ActivityGrid(),
const SizedBox(height: 24),
// ── Note ───────────────────────────────────────────────
Text('Note', style: theme.textTheme.titleMedium),
const SizedBox(height: 8),
TextField(
controller: _noteController,
maxLines: 3,
decoration: InputDecoration(
hintText: 'Add a note (optional)...',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
),
filled: true,
fillColor: theme.colorScheme.surfaceContainerHighest,
),
),
const SizedBox(height: 32),
// ── Save button ────────────────────────────────────────
SizedBox(
width: double.infinity,
height: 52,
child: FilledButton(
onPressed: _saving ? null : _save,
style: FilledButton.styleFrom(
backgroundColor: moodColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(14),
),
),
child: _saving
? const CircularProgressIndicator(color: Colors.white)
: const Text(
'Save',
style: TextStyle(
fontSize: 16, fontWeight: FontWeight.w600),
),
),
),
],
),
),
);
}
}