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:
@@ -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),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user