Skip to content

Commit 6defbfd

Browse files
committed
refactor: add date extension, use stless widgets
1 parent 9faeac9 commit 6defbfd

8 files changed

Lines changed: 211 additions & 45 deletions

File tree

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import 'package:illemo/src/features/authentication/domain/app_user.dart';
77
import 'package:illemo/src/features/emotions/data/repositories/emotion_repository_local.dart';
88
import 'package:illemo/src/features/emotions/domain/entities/emotion_log.dart';
99
import 'package:illemo/src/features/emotions/domain/models/emotion_log_model.dart';
10+
import 'package:illemo/src/utils/date.dart';
1011
import 'package:illemo/src/utils/shared_preferences_provider.dart';
1112
import 'package:riverpod_annotation/riverpod_annotation.dart';
1213
import 'package:shared_preferences/shared_preferences.dart';
@@ -86,7 +87,7 @@ class EmotionRepository {
8687
Stream<EmotionLog?> getEmotionLogToday() {
8788
return _firestore
8889
.collection(emotionsPath(userID))
89-
.where('date', isEqualTo: DateTime.now().toIso8601String().split('T').first)
90+
.where('date', isEqualTo: DateTime.now().date)
9091
.limit(1)
9192
.snapshots()
9293
.map((querySnapshot) {
@@ -108,12 +109,11 @@ class EmotionRepository {
108109
Query query = _firestore.collection(emotionsPath(userID));
109110

110111
if (startDate != null) {
111-
query =
112-
query.where('date', isGreaterThanOrEqualTo: startDate.toIso8601String().split('T').first);
112+
query = query.where('date', isGreaterThanOrEqualTo: startDate.date);
113113
}
114114

115115
if (endDate != null) {
116-
query = query.where('date', isLessThanOrEqualTo: endDate.toIso8601String().split('T').first);
116+
query = query.where('date', isLessThanOrEqualTo: endDate.date);
117117
}
118118
return query.snapshots().map((snapshot) {
119119
return snapshot.docs

lib/src/features/emotions/domain/models/emotion_log_model.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import 'package:flutter/foundation.dart';
22
import 'package:illemo/src/features/emotions/domain/entities/emotion_log.dart';
33
import 'package:illemo/src/features/emotions/domain/models/emotion.dart';
4+
import 'package:illemo/src/utils/date.dart';
45
import 'package:uuid/uuid.dart';
56

67
typedef EmotionLogID = String;
@@ -36,7 +37,7 @@ class EmotionLogModel {
3637
emotion1: entity.emotion1.id,
3738
emotion2: entity.emotion2?.id,
3839
emotion3: entity.emotion3?.id,
39-
date: entity.date.toIso8601String().split('T').first,
40+
date: entity.date.date,
4041
timestamp: timestamp ?? DateTime.now().millisecondsSinceEpoch,
4142
);
4243
}

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import 'package:illemo/src/constants/app_sizes.dart';
55
import 'package:illemo/src/features/emotions/data/providers/emotion_calendar.dart';
66
import 'package:illemo/src/features/emotions/presentation/widgets/emotion_calendar.dart';
77
import 'package:illemo/src/routing/app_router.dart';
8+
import 'package:illemo/src/utils/date.dart';
89

910
class CalendarScreen extends ConsumerWidget {
1011
const CalendarScreen({super.key, this.date});
@@ -36,7 +37,7 @@ class CalendarScreen extends ConsumerWidget {
3637
if (selectedDate != null) {
3738
if (context.mounted) {
3839
context.pushReplacementNamed(AppRoute.calendarDate.name,
39-
pathParameters: {'date': selectedDate.toIso8601String().split('T').first});
40+
pathParameters: {'date': selectedDate.date});
4041
}
4142
}
4243
},

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

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,9 @@ class EmotionUpload extends ConsumerWidget {
3030
);
3131
}
3232

33-
Widget _buildLoading() {
34-
return const Center(
35-
child: CircularProgressIndicator.adaptive(),
36-
);
37-
}
33+
Widget _buildLoading() => const Center(
34+
child: CircularProgressIndicator.adaptive(),
35+
);
3836

3937
Widget _buildSuccess({required onPressed}) {
4038
return Center(

lib/src/features/emotions/presentation/widgets/emotion_calendar.dart

Lines changed: 55 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
22
import 'package:illemo/src/constants/app_sizes.dart';
33
import 'package:illemo/src/features/emotions/domain/entities/emotion_log.dart';
44
import 'package:illemo/src/features/emotions/presentation/widgets/calendar_day.dart';
5+
import 'package:illemo/src/utils/date.dart';
56

67
class EmotionCalendar extends StatelessWidget {
78
const EmotionCalendar({super.key, required this.emotionLogs, required this.currentDate});
@@ -25,10 +26,34 @@ class EmotionCalendar extends StatelessWidget {
2526
];
2627
static const weekdays = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
2728

28-
Widget _buildHeader(DateTime currentDate) {
29+
@override
30+
Widget build(BuildContext context) {
31+
return Column(
32+
children: [
33+
HeaderWidget(
34+
currentDate: currentDate,
35+
),
36+
const WeekdayLabelsWidget(),
37+
Expanded(
38+
child: CalendarWidget(
39+
emotionLogs: emotionLogs,
40+
currentDate: currentDate,
41+
)),
42+
],
43+
);
44+
}
45+
}
46+
47+
class HeaderWidget extends StatelessWidget {
48+
const HeaderWidget({super.key, required this.currentDate});
49+
50+
final DateTime currentDate;
51+
52+
@override
53+
Widget build(BuildContext context) {
2954
final month = currentDate.month;
3055
final year = currentDate.year;
31-
final monthName = months[month - 1];
56+
final monthName = EmotionCalendar.months[month - 1];
3257

3358
return Padding(
3459
padding: const EdgeInsets.all(Sizes.p8),
@@ -38,35 +63,42 @@ class EmotionCalendar extends StatelessWidget {
3863
),
3964
);
4065
}
66+
}
67+
68+
class WeekdayLabelsWidget extends StatelessWidget {
69+
const WeekdayLabelsWidget({super.key});
4170

42-
Widget _buildWeekdayLabels() {
43-
return Row(
44-
mainAxisAlignment: MainAxisAlignment.spaceAround,
45-
children: weekdays.map((day) {
46-
return Expanded(
47-
child: Center(
48-
child: Text(
49-
day,
50-
style: TextStyle(fontWeight: FontWeight.bold),
71+
@override
72+
Widget build(BuildContext context) => Row(
73+
mainAxisAlignment: MainAxisAlignment.spaceAround,
74+
children: EmotionCalendar.weekdays.map((day) {
75+
return Expanded(
76+
child: Center(
77+
child: Text(
78+
day,
79+
style: TextStyle(fontWeight: FontWeight.bold),
80+
),
5181
),
52-
),
53-
);
54-
}).toList(),
55-
);
56-
}
82+
);
83+
}).toList(),
84+
);
85+
}
5786

58-
Widget _buildCalendar(
59-
List<EmotionLog> emotionLogs,
60-
DateTime currentDate,
61-
) {
87+
class CalendarWidget extends StatelessWidget {
88+
const CalendarWidget({super.key, required this.emotionLogs, required this.currentDate});
89+
90+
final List<EmotionLog> emotionLogs;
91+
final DateTime currentDate;
92+
93+
@override
94+
Widget build(BuildContext context) {
6295
final daysInMonth = DateUtils.getDaysInMonth(currentDate.year, currentDate.month);
6396
final firstDayOfMonth = DateTime(currentDate.year, currentDate.month, 1);
6497
final weekdayOfFirstDay = firstDayOfMonth.weekday;
6598

6699
// Create a set of dates with emotion logs for efficient lookup
67-
// Have to truncate the time part of the date because 1 hour keeps getting randomly added
68100
final Map<String, EmotionLog> emotionLogDates = {
69-
for (var log in emotionLogs) log.date.toIso8601String().split('T').first: log
101+
for (var log in emotionLogs) log.date.date: log
70102
};
71103

72104
// Generate the leading empty days
@@ -75,7 +107,7 @@ class EmotionCalendar extends StatelessWidget {
75107
// Generate the days of the month
76108
final days = List.generate(daysInMonth, (index) {
77109
final day = firstDayOfMonth.add(Duration(days: index, hours: 0));
78-
final emotionLog = emotionLogDates[day.toIso8601String().split('T').first];
110+
final emotionLog = emotionLogDates[day.date];
79111
return CalendarDay(
80112
date: day,
81113
emotionLog: emotionLog,
@@ -92,15 +124,4 @@ class EmotionCalendar extends StatelessWidget {
92124
children: calendarDays,
93125
);
94126
}
95-
96-
@override
97-
Widget build(BuildContext context) {
98-
return Column(
99-
children: [
100-
_buildHeader(currentDate),
101-
_buildWeekdayLabels(),
102-
Expanded(child: _buildCalendar(emotionLogs, currentDate)),
103-
],
104-
);
105-
}
106127
}
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
import 'dart:developer';
2+
3+
import 'package:cloud_firestore/cloud_firestore.dart';
4+
import 'package:flutter_riverpod/flutter_riverpod.dart';
5+
import 'package:illemo/src/features/authentication/data/firebase_auth_repository.dart';
6+
import 'package:illemo/src/features/authentication/domain/app_user.dart';
7+
import 'package:illemo/src/features/streak/data/streak_repository.dart';
8+
import 'package:illemo/src/features/streak/domain/streak.dart';
9+
import 'package:illemo/src/features/streak/domain/streak_model.dart';
10+
import 'package:illemo/src/utils/shared_preferences_provider.dart';
11+
import 'package:riverpod_annotation/riverpod_annotation.dart';
12+
import 'package:shared_preferences/shared_preferences.dart';
13+
14+
part 'streak_repository_local.g.dart';
15+
16+
/// A repository for handling a user's [Streak] data.
17+
///
18+
/// [userID] is the ID of the user for whom the streaks are managed.
19+
/// [prefs] is the shared preferences with cache.
20+
class StreakRepositoryLocal implements StreakRepository {
21+
StreakRepositoryLocal({
22+
required this.userID,
23+
required this.prefs,
24+
});
25+
26+
/// Returns the Firestore path for the streaks collection of a specific user.
27+
///
28+
/// [uid] is the user ID for which the path is generated.
29+
static String streaksPath(String uid) => 'users/$uid/streaks';
30+
31+
final FirebaseFirestore _firestore = FirebaseFirestore.instance;
32+
@override
33+
final UserID userID;
34+
@override
35+
final SharedPreferencesWithCache prefs;
36+
37+
/// Creates a new streak in Firestore.
38+
///
39+
/// [streak] is the streak entity to be added.
40+
/// [id] is an optional parameter for the streak ID. If not provided, the ID from the streak entity will be used.
41+
@override
42+
Future<void> addStreak(Streak streak, {StreakID? id}) async {
43+
final docRef = _firestore.collection(streaksPath(userID)).doc(id ?? streak.id);
44+
await docRef.set(StreakModel.fromEntity(streak, id: id).toMap());
45+
log('Added new streak: $streak, $id');
46+
}
47+
48+
/// Reads a streak from Firestore by its ID.
49+
///
50+
/// [id] is the ID of the streak to be fetched.
51+
/// Returns a [Streak] entity if found, otherwise returns null.
52+
@override
53+
Future<Streak?> getStreak(StreakID id) async {
54+
final docRef = _firestore.collection(streaksPath(userID)).doc(id);
55+
final docSnapshot = await docRef.get();
56+
if (docSnapshot.exists) {
57+
return StreakModel.fromMap(docSnapshot.data()!).toEntity();
58+
}
59+
return null;
60+
}
61+
62+
/// Returns a stream of a streak from Firestore by its ID.
63+
///
64+
/// [id] is the ID of the streak to be fetched.
65+
/// Returns a stream of [Streak] entity if found, otherwise returns null.
66+
@override
67+
Stream<Streak?> getStreakStream(StreakID id) {
68+
final docRef = _firestore.collection(streaksPath(userID)).doc(id);
69+
return docRef.snapshots().map((docSnapshot) {
70+
if (docSnapshot.exists) {
71+
return StreakModel.fromMap(docSnapshot.data()!).toEntity();
72+
}
73+
return null;
74+
});
75+
}
76+
77+
/// Updates an existing streak in Firestore.
78+
///
79+
/// [streak] is the streak entity to be updated.
80+
/// [id] is an optional parameter for the streak ID. If not provided, the ID from the streak entity will be used.
81+
@override
82+
Future<void> updateStreak(Streak streak, {StreakID? id}) async {
83+
final docRef = _firestore.collection(streaksPath(userID)).doc(id ?? streak.id);
84+
await docRef.update(StreakModel.fromEntity(streak, id: id).toMap());
85+
log('Updated streak: $streak');
86+
}
87+
88+
/// Deletes a streak from Firestore by its ID.
89+
///
90+
/// [id] is the ID of the streak to be deleted.
91+
@override
92+
Future<void> deleteStreak(StreakID id) async {
93+
final docRef = _firestore.collection(streaksPath(userID)).doc(id);
94+
await docRef.delete();
95+
log('Deleted streak: $id');
96+
}
97+
}
98+
99+
/// Provider for [StreakRepository].
100+
///
101+
/// Requires [UserID] userID.
102+
@riverpod
103+
StreakRepository streakRepository(Ref ref) {
104+
final currentUser = ref.watch(firebaseAuthProvider).currentUser;
105+
if (currentUser == null) {
106+
throw AssertionError('User can\'t be null when fetching emotions');
107+
}
108+
109+
final SharedPreferencesWithCache prefs = ref.watch(sharedPreferencesProvider).requireValue;
110+
return StreakRepository(userID: currentUser.uid, prefs: prefs);
111+
}

lib/src/features/streak/data/streak_repository_local.g.dart

Lines changed: 31 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/src/utils/date.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
extension Date on DateTime {
2+
String get date => toIso8601String().split('T').first;
3+
}

0 commit comments

Comments
 (0)