Skip to content

Commit 051a026

Browse files
improve profile pictures download (#43)
1 parent c36fd6c commit 051a026

14 files changed

+162
-84
lines changed

lib/presentation/common/activity/view_model/activity_item_view_model.dart

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
1-
import 'dart:typed_data';
2-
31
import 'package:flutter/material.dart';
42
import 'package:hooks_riverpod/hooks_riverpod.dart';
3+
import '../../user/view_model/profile_picture_view_model.dart';
54
import '../../../../data/repositories/activity_repository_impl.dart';
65
import '../../../../domain/entities/activity.dart';
7-
import '../../../../data/repositories/user_repository_impl.dart';
86
import '../../../../main.dart';
97
import '../../../my_activities/screens/activity_details_screen.dart';
108
import 'state/activity_item_state.dart';
@@ -30,8 +28,10 @@ class ActivityItemViewModel extends StateNotifier<ActivityItemState> {
3028
}
3129

3230
/// Get the profile picture of the user
33-
Future<Uint8List?> getProfilePicture(String userId) async {
34-
return ref.read(userRepositoryProvider).downloadProfilePicture(userId);
31+
void getProfilePicture(String userId) async {
32+
ref
33+
.read(profilePictureViewModelProvider(userId).notifier)
34+
.getProfilePicture(userId);
3535
}
3636

3737
/// Retrieves the details of an activity.

lib/presentation/common/activity/widgets/activity_comments.dart

Lines changed: 36 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import 'package:comment_box/comment/comment.dart';
44
import 'package:flutter/material.dart';
55
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
66
import 'package:hooks_riverpod/hooks_riverpod.dart';
7+
import '../../user/view_model/profile_picture_view_model.dart';
78

89
import '../../../../core/utils/storage_utils.dart';
910
import '../../../../domain/entities/activity.dart';
@@ -19,18 +20,19 @@ class ActivityComments extends HookConsumerWidget {
1920
final GlobalKey<FormState> formKey;
2021

2122
final currentUserPictureDataProvider =
22-
FutureProvider.family<Uint8List?, Activity>((ref, activity) async {
23+
FutureProvider.family<String?, Activity>((ref, activity) async {
2324
final user = await StorageUtils.getUser();
2425
final provider =
2526
ref.read(activityItemViewModelProvider(activity.id).notifier);
2627

27-
return user != null ? provider.getProfilePicture(user.id) : null;
28+
user != null ? provider.getProfilePicture(user.id) : null;
29+
return user?.id;
2830
});
2931

3032
final commentUserPictureDataProvider =
31-
FutureProvider.family<Uint8List?, User>((ref, user) async {
33+
FutureProvider.family<void, User>((ref, user) async {
3234
final provider = ref.read(activityItemViewModelProvider(user.id).notifier);
33-
return provider.getProfilePicture(user.id);
35+
provider.getProfilePicture(user.id);
3436
});
3537

3638
ActivityComments({
@@ -39,7 +41,10 @@ class ActivityComments extends HookConsumerWidget {
3941
required this.formKey,
4042
});
4143

42-
Widget buildCommentList(WidgetRef ref, List<ActivityComment> comments) {
44+
Widget buildCommentList(
45+
WidgetRef ref,
46+
List<ActivityComment> comments,
47+
) {
4348
return Expanded(
4449
child: ListView.builder(
4550
itemCount: comments.length,
@@ -49,10 +54,13 @@ class ActivityComments extends HookConsumerWidget {
4954
}
5055

5156
Widget buildCommentItem(WidgetRef ref, ActivityComment comment) {
57+
final profilePicture = ref
58+
.watch(profilePictureViewModelProvider(comment.user.id))
59+
.profilePicture;
5260
return Padding(
5361
padding: const EdgeInsets.fromLTRB(2.0, 8.0, 2.0, 0.0),
5462
child: ListTile(
55-
leading: buildUserAvatar(ref, comment.user),
63+
leading: buildUserAvatar(ref, comment.user, profilePicture),
5664
title: GestureDetector(
5765
child: Text(
5866
UserUtils.getNameOrUsername(comment.user),
@@ -82,12 +90,11 @@ class ActivityComments extends HookConsumerWidget {
8290
}
8391

8492
Widget buildCommentChild(
85-
WidgetRef ref,
86-
AppLocalizations appLocalizations,
87-
ActivityItemCommentsViewModel provider,
88-
List<ActivityComment> comments,
89-
bool displayPreviousComments,
90-
) {
93+
WidgetRef ref,
94+
AppLocalizations appLocalizations,
95+
ActivityItemCommentsViewModel provider,
96+
List<ActivityComment> comments,
97+
bool displayPreviousComments) {
9198
final lastComment = comments.isNotEmpty ? comments.last : null;
9299

93100
return Column(
@@ -101,7 +108,7 @@ class ActivityComments extends HookConsumerWidget {
101108
);
102109
}
103110

104-
Widget buildUserAvatar(WidgetRef ref, User user) {
111+
Widget buildUserAvatar(WidgetRef ref, User user, Uint8List? profilePicture) {
105112
return GestureDetector(
106113
child: Container(
107114
height: 50.0,
@@ -111,8 +118,9 @@ class ActivityComments extends HookConsumerWidget {
111118
borderRadius: const BorderRadius.all(Radius.circular(50)),
112119
),
113120
child: ref.watch(commentUserPictureDataProvider(user)).when(
114-
data: (pic) => pic != null
115-
? CircleAvatar(radius: 50, backgroundImage: MemoryImage(pic))
121+
data: (_) => profilePicture != null
122+
? CircleAvatar(
123+
radius: 50, backgroundImage: MemoryImage(profilePicture))
116124
: UserUtils.personIcon,
117125
loading: () => UserUtils.personIcon,
118126
error: (_, __) => UserUtils.personIcon,
@@ -135,7 +143,17 @@ class ActivityComments extends HookConsumerWidget {
135143
height: state.comments.isNotEmpty ? 210 : 80,
136144
child: CommentBox(
137145
userImage: currentUserPictureProvider.when(
138-
data: (pic) => pic != null ? MemoryImage(pic) : null,
146+
data: (userId) {
147+
if (userId != null) {
148+
final profilePicture = ref
149+
.watch(profilePictureViewModelProvider(userId))
150+
.profilePicture;
151+
return profilePicture != null
152+
? MemoryImage(profilePicture)
153+
: null;
154+
}
155+
return null;
156+
},
139157
loading: () => null,
140158
error: (_, __) => null,
141159
),
@@ -145,13 +163,8 @@ class ActivityComments extends HookConsumerWidget {
145163
backgroundColor: ColorUtils.white,
146164
textColor: ColorUtils.mainMedium,
147165
sendWidget: Icon(Icons.send_sharp, size: 30, color: ColorUtils.main),
148-
child: buildCommentChild(
149-
ref,
150-
appLocalizations,
151-
commentsProvider,
152-
state.comments,
153-
state.displayPreviousComments,
154-
),
166+
child: buildCommentChild(ref, appLocalizations, commentsProvider,
167+
state.comments, state.displayPreviousComments),
155168
),
156169
);
157170
}

lib/presentation/common/activity/widgets/activity_item_user_informations.dart

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import 'dart:typed_data';
22

33
import 'package:flutter/material.dart';
44
import 'package:hooks_riverpod/hooks_riverpod.dart';
5+
import '../../user/view_model/profile_picture_view_model.dart';
56

67
import '../../../../domain/entities/activity.dart';
78
import '../../core/utils/color_utils.dart';
@@ -13,18 +14,19 @@ class ActivityItemUserInformation extends HookConsumerWidget {
1314
final Activity activity;
1415

1516
final futureDataProvider =
16-
FutureProvider.family<Uint8List?, Activity>((ref, activity) async {
17+
FutureProvider.family<void, Activity>((ref, activity) async {
1718
final provider =
1819
ref.read(activityItemViewModelProvider(activity.id).notifier);
1920
String userId = activity.user.id;
20-
return provider.getProfilePicture(userId);
21+
provider.getProfilePicture(userId);
2122
});
2223

2324
ActivityItemUserInformation({super.key, required this.activity});
2425

25-
Widget buildProfilePicture(AsyncValue<Uint8List?> futureProvider) {
26+
Widget buildProfilePicture(
27+
AsyncValue<void> futureProvider, Uint8List? profilePicture) {
2628
return futureProvider.when(
27-
data: (profilePicture) {
29+
data: (_) {
2830
return ClipRRect(
2931
borderRadius: BorderRadius.circular(50),
3032
child: Container(
@@ -45,6 +47,9 @@ class ActivityItemUserInformation extends HookConsumerWidget {
4547
@override
4648
Widget build(BuildContext context, WidgetRef ref) {
4749
final futureProvider = ref.watch(futureDataProvider(activity));
50+
final profilePicture = ref
51+
.watch(profilePictureViewModelProvider(activity.user.id))
52+
.profilePicture;
4853
return Padding(
4954
padding: const EdgeInsets.fromLTRB(16, 8, 16, 8),
5055
child: Container(
@@ -60,7 +65,7 @@ class ActivityItemUserInformation extends HookConsumerWidget {
6065
onPressed: () => UserUtils.goToProfile(activity.user),
6166
child: Row(
6267
children: [
63-
buildProfilePicture(futureProvider),
68+
buildProfilePicture(futureProvider, profilePicture),
6469
const SizedBox(width: 20),
6570
Flexible(
6671
child: Text(

lib/presentation/common/core/utils/user_utils.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
12
import 'package:flutter/material.dart';
23

34
import '../../../../domain/entities/user.dart';

lib/presentation/common/core/widgets/infinite_scroll_list.dart

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -35,29 +35,26 @@ class InfiniteScrollList extends HookConsumerWidget {
3535

3636
Future<void> loadMoreData(InfiniteScrollListState state,
3737
InfiniteScrollListViewModel provider) async {
38-
double newPos = scrollController.position.pixels;
3938
var editData = false;
4039
if (!state.isLoading && hasMoreData(state.data, total)) {
40+
double position = scrollController.position.pixels;
4141
provider.setIsLoading(true);
4242
editData = true;
4343

4444
try {
4545
final newData = await loadData(state.pageNumber);
4646

4747
if (state.data is List<List<dynamic>>) {
48-
provider.setData(
49-
newData.list,
50-
newPos,
51-
);
48+
provider.setData(newData.list);
5249
} else {
53-
provider.addData(newData.list, newPos);
50+
provider.addData(newData.list);
5451
}
5552
} finally {
5653
provider.setIsLoading(false);
5754

5855
if (editData) {
5956
WidgetsBinding.instance.addPostFrameCallback((_) {
60-
scrollController.jumpTo(newPos);
57+
scrollController.jumpTo(position);
6158
});
6259
}
6360
}
@@ -102,7 +99,7 @@ class InfiniteScrollList extends HookConsumerWidget {
10299
}));
103100

104101
Future.delayed(const Duration(milliseconds: 10), () {
105-
state.data.isNotEmpty ? '' : provider.setData(initialData, 0);
102+
state.data.isNotEmpty ? '' : provider.setData(initialData);
106103
});
107104

108105
return state.isLoading

lib/presentation/common/core/widgets/view_model/infinite_scroll_list_view_model.dart

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,8 @@ class InfiniteScrollListViewModel
2525
}
2626

2727
/// Set data in the state
28-
void setData(List<dynamic> data, double pos) {
29-
state = state.copyWith(
30-
data: data, pageNumber: state.pageNumber + 1, position: pos);
28+
void setData(List<dynamic> data) {
29+
state = state.copyWith(data: data, pageNumber: state.pageNumber + 1);
3130
}
3231

3332
/// Replace data in the state
@@ -36,23 +35,17 @@ class InfiniteScrollListViewModel
3635
}
3736

3837
/// Add data in the state
39-
void addData(List<dynamic> data, double pos) {
38+
void addData(List<dynamic> data) {
4039
var currentData = state.data;
4140
currentData.addAll(data);
42-
state = state.copyWith(
43-
data: currentData, pageNumber: state.pageNumber + 1, position: pos);
41+
state = state.copyWith(data: currentData, pageNumber: state.pageNumber + 1);
4442
}
4543

4644
/// Set pageNumber in the state
4745
void setPageNumber(int pageNumber) {
4846
state = state.copyWith(pageNumber: pageNumber);
4947
}
5048

51-
/// Set position in the state
52-
void setPosition(double position) {
53-
state = state.copyWith(position: position);
54-
}
55-
5649
/// reset state
5750
void reset() {
5851
state = InfiniteScrollListState.initial();

lib/presentation/common/core/widgets/view_model/state/infinite_scroll_list_state.dart

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,29 +3,21 @@ class InfiniteScrollListState {
33
final List<dynamic> data;
44
final bool isLoading;
55
final int pageNumber;
6-
final double position;
76

87
const InfiniteScrollListState(
9-
{required this.data,
10-
required this.isLoading,
11-
required this.pageNumber,
12-
required this.position});
8+
{required this.data, required this.isLoading, required this.pageNumber});
139

1410
/// Factory method to create the initial state.
1511
factory InfiniteScrollListState.initial() {
1612
return const InfiniteScrollListState(
17-
data: [], isLoading: false, pageNumber: 0, position: 0.0);
13+
data: [], isLoading: false, pageNumber: 0);
1814
}
1915

2016
InfiniteScrollListState copyWith(
21-
{List<dynamic>? data,
22-
bool? isLoading,
23-
int? pageNumber,
24-
double? position}) {
17+
{List<dynamic>? data, bool? isLoading, int? pageNumber}) {
2518
return InfiniteScrollListState(
2619
data: data ?? this.data,
2720
isLoading: isLoading ?? this.isLoading,
28-
pageNumber: pageNumber ?? this.pageNumber,
29-
position: position ?? this.position);
21+
pageNumber: pageNumber ?? this.pageNumber);
3022
}
3123
}

lib/presentation/common/user/screens/profile_screen.dart

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import 'package:flutter/material.dart';
22
import 'package:hooks_riverpod/hooks_riverpod.dart';
3+
import '../view_model/profile_picture_view_model.dart';
34

45
import '../../../../domain/entities/activity.dart';
56
import '../../../../domain/entities/enum/friend_request_status.dart';
@@ -40,6 +41,9 @@ class ProfileScreen extends HookConsumerWidget {
4041
var provider = ref.watch(profileViewModelProvider(user.id).notifier);
4142
final futureProvider = ref.watch(futureDataProvider(user));
4243

44+
var profilePicture =
45+
ref.watch(profilePictureViewModelProvider(user.id)).profilePicture;
46+
4347
var activitiesStateProvider = ref.watch(activitiesDataFutureProvider(user));
4448

4549
return state.isLoading
@@ -60,9 +64,9 @@ class ProfileScreen extends HookConsumerWidget {
6064
alignment: Alignment.center,
6165
width: 150,
6266
height: 150,
63-
child: state.profilePicture != null
67+
child: profilePicture != null
6468
? Image.memory(
65-
state.profilePicture!,
69+
profilePicture,
6670
fit: BoxFit.cover,
6771
)
6872
: const Icon(Icons.person, size: 100),
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import 'dart:typed_data';
2+
3+
import 'package:hooks_riverpod/hooks_riverpod.dart';
4+
5+
import '../../../../data/repositories/user_repository_impl.dart';
6+
import 'state/profile_picture_state.dart';
7+
8+
/// Provider for the profile picture view model.
9+
final profilePictureViewModelProvider = StateNotifierProvider.family<
10+
ProfilePictureViewModel,
11+
ProfilePictureState,
12+
String>((ref, userId) => ProfilePictureViewModel(ref, userId));
13+
14+
class ProfilePictureViewModel extends StateNotifier<ProfilePictureState> {
15+
late final Ref ref;
16+
final String userId;
17+
18+
ProfilePictureViewModel(this.ref, this.userId)
19+
: super(ProfilePictureState.initial());
20+
21+
Future<void> getProfilePicture(String userId) async {
22+
if (state.loaded == false) {
23+
ref.read(userRepositoryProvider).downloadProfilePicture(userId).then(
24+
(value) =>
25+
state = state.copyWith(profilePicture: value, loaded: true));
26+
}
27+
}
28+
29+
void editProfilePicture(Uint8List? image) {
30+
state = state.copyWith(profilePicture: image);
31+
}
32+
}

0 commit comments

Comments
 (0)