Skip to content

Commit b28e951

Browse files
committed
feat(RPC): update RPC to get data for heatmap
1 parent 7db7ab3 commit b28e951

5 files changed

Lines changed: 124 additions & 10 deletions

File tree

src/lib/features/user/service/user_service.dart

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,14 @@ class UserService {
88
Future<UserProfileModel> fetchUserProfile() async {
99
// Mimic API call delay for smooth state switching
1010
try{
11+
final user = _supabase.auth.currentUser;
12+
if (user == null) {
13+
throw Exception("Không tìm thấy phiên đăng nhập. Hãy đăng nhập lại");
14+
}
1115
final response = await _supabase.rpc('get_user_profile_stats');
16+
if(response == null){
17+
throw Exception("Không thể lấy thông tin người dùng. Hãy thử lại sau");
18+
}
1219
response['id'] = _supabase.auth.currentUser!.id;
1320
return UserProfileModel.fromJson(response);
1421
}

src/lib/features/user/view/user_profile_view.dart

Lines changed: 102 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import 'package:flutter/material.dart';
22
import 'package:provider/provider.dart';
3+
import 'package:flutter_heatmap_calendar/flutter_heatmap_calendar.dart';
34
import '../viewmodel/user_profile_viewmodel.dart';
45
import 'widgets/profile_header.dart';
56
import 'widgets/stat_card.dart';
@@ -44,7 +45,7 @@ class UserProfileView extends StatelessWidget {
4445
duration: const Duration(milliseconds: 300),
4546
child: vm.isLoading
4647
? Center(
47-
key: ValueKey('loading'),
48+
key: const ValueKey('loading'),
4849
child: CircularProgressIndicator(
4950
color: Theme.of(context).colorScheme.primary,
5051
),
@@ -55,11 +56,11 @@ class UserProfileView extends StatelessWidget {
5556
child: Text("Error loading profile"),
5657
)
5758
: Builder(
58-
builder: (innerContext) {
59-
vm.syncThemeWithProfile(innerContext);
60-
return _buildProfileContent(innerContext, vm);
61-
},
62-
),
59+
builder: (innerContext) {
60+
vm.syncThemeWithProfile(innerContext);
61+
return _buildProfileContent(innerContext, vm);
62+
},
63+
),
6364
);
6465
},
6566
),
@@ -93,7 +94,7 @@ class UserProfileView extends StatelessWidget {
9394
child: StatCard(
9495
value: user.streaks.toString(),
9596
label: 'Streaks',
96-
onTap: () {},
97+
onTap: () => _showHeatmapBottomSheet(context),
9798
),
9899
),
99100
],
@@ -114,7 +115,7 @@ class UserProfileView extends StatelessWidget {
114115
activeColor: Theme.of(context).colorScheme.surface,
115116
activeTrackColor: Theme.of(context).colorScheme.primary,
116117
inactiveThumbColor:
117-
Theme.of(context).colorScheme.onSurfaceVariant,
118+
Theme.of(context).colorScheme.onSurfaceVariant,
118119
inactiveTrackColor: Theme.of(context).colorScheme.outline,
119120
onChanged: (val) => vm.toggleNotification(val),
120121
),
@@ -153,4 +154,97 @@ class UserProfileView extends StatelessWidget {
153154
),
154155
);
155156
}
157+
158+
void _showHeatmapBottomSheet(BuildContext context) {
159+
// mock data for testing
160+
final now = DateTime.now();
161+
final today = DateTime(now.year, now.month, now.day);
162+
163+
final Map<DateTime, int> mockHeatmapData = {
164+
today.subtract(const Duration(days: 1)): 3,
165+
today.subtract(const Duration(days: 2)): 7,
166+
today.subtract(const Duration(days: 3)): 4,
167+
today.subtract(const Duration(days: 4)): 8,
168+
today.subtract(const Duration(days: 5)): 2,
169+
today.subtract(const Duration(days: 8)): 5,
170+
};
171+
172+
showModalBottomSheet(
173+
context: context,
174+
isScrollControlled: true,
175+
backgroundColor: Theme.of(context).colorScheme.surface,
176+
shape: const RoundedRectangleBorder(
177+
borderRadius: BorderRadius.vertical(top: Radius.circular(24)),
178+
),
179+
builder: (bottomSheetContext) {
180+
return SafeArea(
181+
child: Padding(
182+
padding: const EdgeInsets.all(24.0),
183+
child: Column(
184+
mainAxisSize: MainAxisSize.min,
185+
crossAxisAlignment: CrossAxisAlignment.start,
186+
children: [
187+
Center(
188+
child: Container(
189+
width: 40,
190+
height: 4,
191+
decoration: BoxDecoration(
192+
color: Theme.of(context).colorScheme.outline,
193+
borderRadius: BorderRadius.circular(2),
194+
),
195+
),
196+
),
197+
const SizedBox(height: 24),
198+
199+
Text(
200+
'Bản đồ hoạt động',
201+
style: TextStyle(
202+
fontSize: 20,
203+
fontWeight: FontWeight.bold,
204+
color: Theme.of(context).colorScheme.onSurface,
205+
),
206+
),
207+
const SizedBox(height: 8),
208+
Text(
209+
'Giữ vững phong độ nhé! 🔥',
210+
style: TextStyle(
211+
color: Theme.of(context).colorScheme.onSurfaceVariant,
212+
),
213+
),
214+
const SizedBox(height: 24),
215+
216+
Center(
217+
child: HeatMap(
218+
datasets: mockHeatmapData,
219+
colorMode: ColorMode.opacity,
220+
showText: false,
221+
scrollable: true,
222+
size: 30,
223+
224+
colorsets: {
225+
1: Theme.of(context).colorScheme.primary.withValues(alpha: 0.2),
226+
3: Theme.of(context).colorScheme.primary.withValues(alpha: 0.4),
227+
5: Theme.of(context).colorScheme.primary.withValues(alpha: 0.6),
228+
7: Theme.of(context).colorScheme.primary.withValues(alpha: 0.8),
229+
9: Theme.of(context).colorScheme.primary,
230+
},
231+
onClick: (value) {
232+
// Khi bấm vào 1 ô vuông, hiện số task hoàn thành ngày đó
233+
ScaffoldMessenger.of(context).showSnackBar(
234+
SnackBar(
235+
content: Text('Đã hoàn thành $value công việc'),
236+
behavior: SnackBarBehavior.floating,
237+
),
238+
);
239+
},
240+
),
241+
),
242+
const SizedBox(height: 16),
243+
],
244+
),
245+
),
246+
);
247+
},
248+
);
249+
}
156250
}

src/lib/features/user/viewmodel/user_profile_viewmodel.dart

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,12 @@ class UserProfileViewModel extends ChangeNotifier {
3636
_lastAppliedAppearance = null;
3737
} catch (e) {
3838
debugPrint("Error loading profile: $e");
39-
_user = _buildMockUser();
39+
if(useMockData){
40+
_user = _buildMockUser();
41+
}
42+
else{
43+
_user = null;
44+
}
4045
} finally {
4146
_isLoading = false;
4247
notifyListeners();
@@ -47,7 +52,6 @@ class UserProfileViewModel extends ChangeNotifier {
4752
return UserProfileModel(
4853
id: 'mock-user-001',
4954
name: 'Alex Thompson',
50-
// Valid URL so profile header can test normal network-avatar path.
5155
avatarUrl: 'https://i.pravatar.cc/300?img=12',
5256
appearance: 'Dark',
5357
tasksDone: 24,

src/pubspec.lock

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,14 @@ packages:
198198
url: "https://pub.dev"
199199
source: hosted
200200
version: "6.0.0"
201+
flutter_heatmap_calendar:
202+
dependency: "direct main"
203+
description:
204+
name: flutter_heatmap_calendar
205+
sha256: "0933071844cd604b938e38358984244292ba1ba8f4b4b6f0f091f5927a23e958"
206+
url: "https://pub.dev"
207+
source: hosted
208+
version: "1.0.5"
201209
flutter_lints:
202210
dependency: "direct dev"
203211
description:

src/pubspec.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ dependencies:
4242
flutter_ringtone_player: ^4.0.0+4
4343
image_picker: ^1.2.1
4444
shared_preferences: ^2.2.2
45+
flutter_heatmap_calendar: ^1.0.5
4546

4647
dev_dependencies:
4748
flutter_test:

0 commit comments

Comments
 (0)