Skip to content

Commit a4e473a

Browse files
committed
feat: full emotion logging w/ firestore
1 parent 9a7b5cf commit a4e473a

12 files changed

Lines changed: 484 additions & 65 deletions

File tree

lib/src/features/emotions/data/repositories/emotion_repository.dart

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import 'package:illemo/src/features/emotions/domain/entities/emotion_log.dart';
99
import 'package:illemo/src/features/emotions/domain/models/emotion_log_model.dart';
1010
import 'package:illemo/src/utils/shared_preferences_provider.dart';
1111
import 'package:riverpod_annotation/riverpod_annotation.dart';
12+
import 'package:shared_preferences/shared_preferences.dart';
1213

1314
part 'emotion_repository.g.dart';
1415

@@ -28,20 +29,26 @@ class EmotionRepository {
2829
/// Adds a new emotion log to Firestore.
2930
///
3031
/// Converts the [EmotionLog] entity to a map using [EmotionLogModel] before adding it.
31-
Future<void> addEmotionLog(EmotionLog emotionLog) async {
32-
await _firestore
33-
.collection(emotionsPath(userID))
34-
.add(EmotionLogModel.fromEntity(emotionLog).toMap());
32+
Future<EmotionLogID> addEmotionLog(EmotionLog emotionLog) async {
33+
final EmotionLogModel emotionLogModel = EmotionLogModel.fromEntity(emotionLog);
34+
final docRef = _firestore.collection(emotionsPath(userID)).doc(emotionLogModel.id);
35+
await docRef.set(emotionLogModel.toMap());
36+
return emotionLogModel.id;
3537
}
3638

3739
/// Updates an existing emotion log in Firestore.
3840
///
3941
/// Converts the [EmotionLog] entity to a map using [EmotionLogModel] before updating it.
4042
Future<void> updateEmotionLog(EmotionLogID id, EmotionLog emotionLog) async {
41-
await _firestore
42-
.collection(emotionsPath(userID))
43-
.doc(id)
44-
.update(EmotionLogModel.fromEntity(emotionLog, id: id).toMap());
43+
final docRef = _firestore.collection(emotionsPath(userID)).doc(id);
44+
final emotionLogModel = EmotionLogModel.fromEntity(emotionLog, id: id);
45+
final doc = await docRef.get();
46+
if (doc.exists) {
47+
await docRef.update(emotionLogModel.toMap());
48+
} else {
49+
await docRef.set(emotionLogModel.toMap());
50+
}
51+
log("Updated emotion log with ID: $id");
4552
}
4653

4754
/// Deletes an emotion log from Firestore by its document ID.
@@ -114,7 +121,7 @@ EmotionRepository emotionRepository(Ref ref) {
114121

115122
if (currentUser.isAnonymous) {
116123
log('Using local storage for anonymous user');
117-
final prefs = ref.watch(sharedPreferencesProvider).requireValue;
124+
final SharedPreferencesWithCache prefs = ref.watch(sharedPreferencesProvider).requireValue;
118125
return EmotionRepositoryLocal(userID: currentUser.uid, prefs: prefs);
119126
}
120127
log('Using firestore for authenticated user');

lib/src/features/emotions/data/repositories/emotion_repository.g.dart

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/src/features/emotions/data/repositories/emotion_repository_local.dart

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,11 @@ class EmotionRepositoryLocal implements EmotionRepository {
2828
///
2929
/// [emotionLog] - The emotion log to be added.
3030
@override
31-
Future<void> addEmotionLog(EmotionLog emotionLog) async {
31+
Future<EmotionLogID> addEmotionLog(EmotionLog emotionLog) async {
3232
final key = '${_collectionPath}_${emotionLog.date.weekday}';
33-
await prefs.setString(key, jsonEncode(EmotionLogModel.fromEntity(emotionLog).toMap()));
33+
final EmotionLogModel emotionLogModel = EmotionLogModel.fromEntity(emotionLog);
34+
await prefs.setString(key, jsonEncode(emotionLogModel.toMap()));
35+
return emotionLogModel.id;
3436
}
3537

3638
/// Updates an existing emotion log in the local storage.

lib/src/features/emotions/domain/entities/emotion_log.dart

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,38 @@ class EmotionLog extends Equatable {
2727

2828
@override
2929
bool get stringify => true;
30+
31+
/// Factory constructor to create an EmotionLog from a list of emotions.
32+
factory EmotionLog.fromEmotions({
33+
required List<Emotion> emotions,
34+
required DateTime date,
35+
EmotionLogID? id,
36+
}) {
37+
assert(emotions.isNotEmpty && emotions.length <= 3,
38+
'Emotions list must contain between 1 and 3 items.');
39+
return EmotionLog(
40+
emotion1: emotions[0],
41+
emotion2: emotions.length > 1 ? emotions[1] : null,
42+
emotion3: emotions.length > 2 ? emotions[2] : null,
43+
date: date,
44+
id: id,
45+
);
46+
}
47+
48+
/// Creates a copy of the current EmotionLog with updated values.
49+
EmotionLog copyWith({
50+
Emotion? emotion1,
51+
Emotion? emotion2,
52+
Emotion? emotion3,
53+
DateTime? date,
54+
EmotionLogID? id,
55+
}) {
56+
return EmotionLog(
57+
emotion1: emotion1 ?? this.emotion1,
58+
emotion2: emotion2 ?? this.emotion2,
59+
emotion3: emotion3 ?? this.emotion3,
60+
date: date ?? this.date,
61+
id: id ?? this.id,
62+
);
63+
}
3064
}

lib/src/features/emotions/presentation/controllers/emotion_picker_controller.dart

Whitespace-only changes.

lib/src/features/emotions/presentation/screens/dashboard.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ class DashboardScreen extends ConsumerWidget {
2121
child: todaysEmotionLog.isLoading
2222
? const CircularProgressIndicator.adaptive()
2323
: ElevatedButton(
24-
onPressed: () => context.push(EmotionPickerScreen.path),
24+
onPressed: () =>
25+
context.push(EmotionPickerScreen.path, extra: todaysEmotionLog.value),
2526
child: todaysEmotionLog.value != null
2627
? Text('You are feeling ${todaysEmotionLog.value!.emotion1} today.')
2728
: Text('Log your emotions!')),

lib/src/features/emotions/presentation/screens/emotion_picker.dart

Lines changed: 119 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@ import 'dart:developer';
22

33
import 'package:flutter/material.dart';
44
import 'package:flutter_riverpod/flutter_riverpod.dart';
5+
import 'package:go_router/go_router.dart';
56
import 'package:illemo/src/common_widgets/looping_listview.dart';
67
import 'package:illemo/src/features/emotions/domain/entities/emotion_log.dart';
78
import 'package:illemo/src/features/emotions/domain/models/category.dart';
89
import 'package:illemo/src/features/emotions/domain/models/emotion.dart';
9-
import 'package:illemo/src/features/emotions/service/emotion_today_service.dart';
10+
import 'package:illemo/src/features/emotions/presentation/screens/emotion_upload.dart';
1011

1112
class EmotionPickerScreen extends ConsumerStatefulWidget {
1213
const EmotionPickerScreen({super.key, this.todaysEmotionLog});
@@ -45,51 +46,134 @@ class _EmotionPickerScreenState extends ConsumerState<EmotionPickerScreen> {
4546
}
4647
}
4748

49+
void pushEmotion(Emotion emotion) {
50+
if (_selectedEmotions.length < 3) {
51+
setState(() {
52+
_selectedEmotions.add(emotion);
53+
currentEmotion = null;
54+
});
55+
}
56+
}
57+
58+
void _removeEmotion(int index) {
59+
setState(() {
60+
currentEmotion = _selectedEmotions.removeAt(index);
61+
});
62+
}
63+
64+
void _submitEmotions() {
65+
context.go(EmotionUpload.path, extra: {
66+
'emotionIDs': _selectedEmotions.map((e) => e.id).toList(),
67+
'id': widget.todaysEmotionLog?.id,
68+
});
69+
}
70+
4871
@override
4972
Widget build(BuildContext context) {
5073
return Scaffold(
51-
backgroundColor: Colors.black,
52-
appBar: AppBar(
53-
title: const Text(EmotionPickerScreen.title),
54-
),
55-
body: Stack(children: [
56-
_buildEmotionWheels(),
57-
Positioned(
58-
top: 0,
59-
left: 0,
60-
right: 0,
61-
child: IgnorePointer(
62-
child: Container(
63-
height: 200,
64-
decoration: BoxDecoration(
65-
gradient: LinearGradient(
66-
begin: Alignment.topCenter,
67-
end: Alignment.bottomCenter,
68-
colors: [Colors.black.withValues(alpha: 0.85), Colors.transparent],
74+
backgroundColor: Colors.black,
75+
appBar: AppBar(
76+
title: const Text(EmotionPickerScreen.title),
77+
),
78+
body: Stack(children: [
79+
_buildEmotionWheels(),
80+
Positioned(
81+
top: 0,
82+
left: 0,
83+
right: 0,
84+
child: IgnorePointer(
85+
child: Container(
86+
height: 200,
87+
decoration: BoxDecoration(
88+
gradient: LinearGradient(
89+
begin: Alignment.topCenter,
90+
end: Alignment.bottomCenter,
91+
colors: [Colors.black.withValues(alpha: 0.85), Colors.transparent],
92+
),
6993
),
7094
),
7195
),
7296
),
73-
),
74-
Positioned(
75-
bottom: 0,
76-
left: 0,
77-
right: 0,
78-
child: IgnorePointer(
79-
child: Container(
80-
height: 200,
81-
decoration: BoxDecoration(
82-
gradient: LinearGradient(
83-
begin: Alignment.bottomCenter,
84-
end: Alignment.topCenter,
85-
colors: [Colors.black.withValues(alpha: 0.85), Colors.transparent],
97+
Positioned(
98+
bottom: 0,
99+
left: 0,
100+
right: 0,
101+
child: IgnorePointer(
102+
child: Container(
103+
height: 200,
104+
decoration: BoxDecoration(
105+
gradient: LinearGradient(
106+
begin: Alignment.bottomCenter,
107+
end: Alignment.topCenter,
108+
colors: [Colors.black.withValues(alpha: 0.85), Colors.transparent],
109+
),
86110
),
87111
),
88112
),
89113
),
90-
),
91-
]),
92-
);
114+
if (_selectedEmotions.length >= 3)
115+
Positioned(
116+
top: 0,
117+
left: 0,
118+
right: 0,
119+
bottom: 0,
120+
child: Center(
121+
child: Container(
122+
color: Colors.black.withValues(alpha: (0.5)),
123+
),
124+
),
125+
),
126+
if (_selectedEmotions.length >= 3)
127+
Center(
128+
child: ElevatedButton(
129+
onPressed: _submitEmotions,
130+
child: const Text('Submit'),
131+
),
132+
),
133+
Positioned(
134+
top: 16,
135+
left: 16,
136+
child: Row(
137+
children: [
138+
for (int index = 0; index < _selectedEmotions.length; index++)
139+
Padding(
140+
padding: const EdgeInsets.only(right: 8),
141+
child: Container(
142+
width: 50,
143+
height: 50,
144+
decoration: BoxDecoration(
145+
color: _selectedEmotions[index].color,
146+
borderRadius: BorderRadius.circular(25),
147+
),
148+
child: IconButton(
149+
icon: const Icon(Icons.close),
150+
color: Colors.white,
151+
onPressed: () => _removeEmotion(index),
152+
),
153+
),
154+
),
155+
],
156+
),
157+
),
158+
]),
159+
floatingActionButton: Row(
160+
mainAxisAlignment: MainAxisAlignment.end,
161+
children: [
162+
if (currentEmotion != null && _selectedEmotions.length < 3)
163+
FloatingActionButton(
164+
heroTag: 'addEmotion',
165+
onPressed: () => pushEmotion(currentEmotion!),
166+
child: const Icon(Icons.add),
167+
),
168+
const SizedBox(width: 16),
169+
if (_selectedEmotions.isNotEmpty && _selectedEmotions.length < 3)
170+
FloatingActionButton(
171+
heroTag: 'submitEmotions',
172+
onPressed: _submitEmotions,
173+
child: const Icon(Icons.arrow_forward),
174+
),
175+
],
176+
));
93177
}
94178

95179
Widget _buildEmotionWheels() => ListView.builder(

0 commit comments

Comments
 (0)