Skip to content

Commit

Permalink
improve profile pictures download
Browse files Browse the repository at this point in the history
  • Loading branch information
BenjaminCanape committed Feb 4, 2024
1 parent c36fd6c commit 9a5a24b
Show file tree
Hide file tree
Showing 14 changed files with 162 additions and 84 deletions.
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import 'dart:typed_data';

import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import '../../user/view_model/profile_picture_view_model.dart';
import '../../../../data/repositories/activity_repository_impl.dart';
import '../../../../domain/entities/activity.dart';
import '../../../../data/repositories/user_repository_impl.dart';
import '../../../../main.dart';
import '../../../my_activities/screens/activity_details_screen.dart';
import 'state/activity_item_state.dart';
Expand All @@ -30,8 +28,10 @@ class ActivityItemViewModel extends StateNotifier<ActivityItemState> {
}

/// Get the profile picture of the user
Future<Uint8List?> getProfilePicture(String userId) async {
return ref.read(userRepositoryProvider).downloadProfilePicture(userId);
void getProfilePicture(String userId) async {
ref
.read(profilePictureViewModelProvider(userId).notifier)
.getProfilePicture(userId);
}

/// Retrieves the details of an activity.
Expand Down
59 changes: 36 additions & 23 deletions lib/presentation/common/activity/widgets/activity_comments.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import 'package:comment_box/comment/comment.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import '../../user/view_model/profile_picture_view_model.dart';

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

final currentUserPictureDataProvider =
FutureProvider.family<Uint8List?, Activity>((ref, activity) async {
FutureProvider.family<String?, Activity>((ref, activity) async {
final user = await StorageUtils.getUser();
final provider =
ref.read(activityItemViewModelProvider(activity.id).notifier);

return user != null ? provider.getProfilePicture(user.id) : null;
user != null ? provider.getProfilePicture(user.id) : null;
return user?.id;
});

final commentUserPictureDataProvider =
FutureProvider.family<Uint8List?, User>((ref, user) async {
FutureProvider.family<void, User>((ref, user) async {
final provider = ref.read(activityItemViewModelProvider(user.id).notifier);
return provider.getProfilePicture(user.id);
provider.getProfilePicture(user.id);
});

ActivityComments({
Expand All @@ -39,7 +41,10 @@ class ActivityComments extends HookConsumerWidget {
required this.formKey,
});

Widget buildCommentList(WidgetRef ref, List<ActivityComment> comments) {
Widget buildCommentList(
WidgetRef ref,
List<ActivityComment> comments,
) {
return Expanded(
child: ListView.builder(
itemCount: comments.length,
Expand All @@ -49,10 +54,13 @@ class ActivityComments extends HookConsumerWidget {
}

Widget buildCommentItem(WidgetRef ref, ActivityComment comment) {
final profilePicture = ref
.watch(profilePictureViewModelProvider(comment.user.id))
.profilePicture;
return Padding(
padding: const EdgeInsets.fromLTRB(2.0, 8.0, 2.0, 0.0),
child: ListTile(
leading: buildUserAvatar(ref, comment.user),
leading: buildUserAvatar(ref, comment.user, profilePicture),
title: GestureDetector(
child: Text(
UserUtils.getNameOrUsername(comment.user),
Expand Down Expand Up @@ -82,12 +90,11 @@ class ActivityComments extends HookConsumerWidget {
}

Widget buildCommentChild(
WidgetRef ref,
AppLocalizations appLocalizations,
ActivityItemCommentsViewModel provider,
List<ActivityComment> comments,
bool displayPreviousComments,
) {
WidgetRef ref,
AppLocalizations appLocalizations,
ActivityItemCommentsViewModel provider,
List<ActivityComment> comments,
bool displayPreviousComments) {
final lastComment = comments.isNotEmpty ? comments.last : null;

return Column(
Expand All @@ -101,7 +108,7 @@ class ActivityComments extends HookConsumerWidget {
);
}

Widget buildUserAvatar(WidgetRef ref, User user) {
Widget buildUserAvatar(WidgetRef ref, User user, Uint8List? profilePicture) {
return GestureDetector(
child: Container(
height: 50.0,
Expand All @@ -111,8 +118,9 @@ class ActivityComments extends HookConsumerWidget {
borderRadius: const BorderRadius.all(Radius.circular(50)),
),
child: ref.watch(commentUserPictureDataProvider(user)).when(
data: (pic) => pic != null
? CircleAvatar(radius: 50, backgroundImage: MemoryImage(pic))
data: (_) => profilePicture != null
? CircleAvatar(
radius: 50, backgroundImage: MemoryImage(profilePicture))
: UserUtils.personIcon,
loading: () => UserUtils.personIcon,
error: (_, __) => UserUtils.personIcon,
Expand All @@ -135,7 +143,17 @@ class ActivityComments extends HookConsumerWidget {
height: state.comments.isNotEmpty ? 210 : 80,
child: CommentBox(
userImage: currentUserPictureProvider.when(
data: (pic) => pic != null ? MemoryImage(pic) : null,
data: (userId) {
if (userId != null) {
final profilePicture = ref
.watch(profilePictureViewModelProvider(userId))
.profilePicture;
return profilePicture != null
? MemoryImage(profilePicture)
: null;
}
return null;
},
loading: () => null,
error: (_, __) => null,
),
Expand All @@ -145,13 +163,8 @@ class ActivityComments extends HookConsumerWidget {
backgroundColor: ColorUtils.white,
textColor: ColorUtils.mainMedium,
sendWidget: Icon(Icons.send_sharp, size: 30, color: ColorUtils.main),
child: buildCommentChild(
ref,
appLocalizations,
commentsProvider,
state.comments,
state.displayPreviousComments,
),
child: buildCommentChild(ref, appLocalizations, commentsProvider,
state.comments, state.displayPreviousComments),
),
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import 'dart:typed_data';

import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import '../../user/view_model/profile_picture_view_model.dart';

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

final futureDataProvider =
FutureProvider.family<Uint8List?, Activity>((ref, activity) async {
FutureProvider.family<void, Activity>((ref, activity) async {
final provider =
ref.read(activityItemViewModelProvider(activity.id).notifier);
String userId = activity.user.id;
return provider.getProfilePicture(userId);
provider.getProfilePicture(userId);
});

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

Widget buildProfilePicture(AsyncValue<Uint8List?> futureProvider) {
Widget buildProfilePicture(
AsyncValue<void> futureProvider, Uint8List? profilePicture) {
return futureProvider.when(
data: (profilePicture) {
data: (_) {
return ClipRRect(
borderRadius: BorderRadius.circular(50),
child: Container(
Expand All @@ -45,6 +47,9 @@ class ActivityItemUserInformation extends HookConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final futureProvider = ref.watch(futureDataProvider(activity));
final profilePicture = ref
.watch(profilePictureViewModelProvider(activity.user.id))
.profilePicture;
return Padding(
padding: const EdgeInsets.fromLTRB(16, 8, 16, 8),
child: Container(
Expand All @@ -60,7 +65,7 @@ class ActivityItemUserInformation extends HookConsumerWidget {
onPressed: () => UserUtils.goToProfile(activity.user),
child: Row(
children: [
buildProfilePicture(futureProvider),
buildProfilePicture(futureProvider, profilePicture),
const SizedBox(width: 20),
Flexible(
child: Text(
Expand Down
1 change: 1 addition & 0 deletions lib/presentation/common/core/utils/user_utils.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@

import 'package:flutter/material.dart';

import '../../../../domain/entities/user.dart';
Expand Down
13 changes: 5 additions & 8 deletions lib/presentation/common/core/widgets/infinite_scroll_list.dart
Original file line number Diff line number Diff line change
Expand Up @@ -35,29 +35,26 @@ class InfiniteScrollList extends HookConsumerWidget {

Future<void> loadMoreData(InfiniteScrollListState state,
InfiniteScrollListViewModel provider) async {
double newPos = scrollController.position.pixels;
var editData = false;
if (!state.isLoading && hasMoreData(state.data, total)) {
double position = scrollController.position.pixels;
provider.setIsLoading(true);
editData = true;

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

if (state.data is List<List<dynamic>>) {
provider.setData(
newData.list,
newPos,
);
provider.setData(newData.list);
} else {
provider.addData(newData.list, newPos);
provider.addData(newData.list);
}
} finally {
provider.setIsLoading(false);

if (editData) {
WidgetsBinding.instance.addPostFrameCallback((_) {
scrollController.jumpTo(newPos);
scrollController.jumpTo(position);
});
}
}
Expand Down Expand Up @@ -102,7 +99,7 @@ class InfiniteScrollList extends HookConsumerWidget {
}));

Future.delayed(const Duration(milliseconds: 10), () {
state.data.isNotEmpty ? '' : provider.setData(initialData, 0);
state.data.isNotEmpty ? '' : provider.setData(initialData);
});

return state.isLoading
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,8 @@ class InfiniteScrollListViewModel
}

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

/// Replace data in the state
Expand All @@ -36,23 +35,17 @@ class InfiniteScrollListViewModel
}

/// Add data in the state
void addData(List<dynamic> data, double pos) {
void addData(List<dynamic> data) {
var currentData = state.data;
currentData.addAll(data);
state = state.copyWith(
data: currentData, pageNumber: state.pageNumber + 1, position: pos);
state = state.copyWith(data: currentData, pageNumber: state.pageNumber + 1);
}

/// Set pageNumber in the state
void setPageNumber(int pageNumber) {
state = state.copyWith(pageNumber: pageNumber);
}

/// Set position in the state
void setPosition(double position) {
state = state.copyWith(position: position);
}

/// reset state
void reset() {
state = InfiniteScrollListState.initial();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,29 +3,21 @@ class InfiniteScrollListState {
final List<dynamic> data;
final bool isLoading;
final int pageNumber;
final double position;

const InfiniteScrollListState(
{required this.data,
required this.isLoading,
required this.pageNumber,
required this.position});
{required this.data, required this.isLoading, required this.pageNumber});

/// Factory method to create the initial state.
factory InfiniteScrollListState.initial() {
return const InfiniteScrollListState(
data: [], isLoading: false, pageNumber: 0, position: 0.0);
data: [], isLoading: false, pageNumber: 0);
}

InfiniteScrollListState copyWith(
{List<dynamic>? data,
bool? isLoading,
int? pageNumber,
double? position}) {
{List<dynamic>? data, bool? isLoading, int? pageNumber}) {
return InfiniteScrollListState(
data: data ?? this.data,
isLoading: isLoading ?? this.isLoading,
pageNumber: pageNumber ?? this.pageNumber,
position: position ?? this.position);
pageNumber: pageNumber ?? this.pageNumber);
}
}
8 changes: 6 additions & 2 deletions lib/presentation/common/user/screens/profile_screen.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import '../view_model/profile_picture_view_model.dart';

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

var profilePicture =
ref.watch(profilePictureViewModelProvider(user.id)).profilePicture;

var activitiesStateProvider = ref.watch(activitiesDataFutureProvider(user));

return state.isLoading
Expand All @@ -60,9 +64,9 @@ class ProfileScreen extends HookConsumerWidget {
alignment: Alignment.center,
width: 150,
height: 150,
child: state.profilePicture != null
child: profilePicture != null
? Image.memory(
state.profilePicture!,
profilePicture,
fit: BoxFit.cover,
)
: const Icon(Icons.person, size: 100),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import 'dart:typed_data';

import 'package:hooks_riverpod/hooks_riverpod.dart';

import '../../../../data/repositories/user_repository_impl.dart';
import 'state/profile_picture_state.dart';

/// Provider for the profile picture view model.
final profilePictureViewModelProvider = StateNotifierProvider.family<
ProfilePictureViewModel,
ProfilePictureState,
String>((ref, userId) => ProfilePictureViewModel(ref, userId));

class ProfilePictureViewModel extends StateNotifier<ProfilePictureState> {
late final Ref ref;
final String userId;

ProfilePictureViewModel(this.ref, this.userId)
: super(ProfilePictureState.initial());

Future<void> getProfilePicture(String userId) async {
if (state.loaded == false) {
ref.read(userRepositoryProvider).downloadProfilePicture(userId).then(
(value) =>
state = state.copyWith(profilePicture: value, loaded: true));
}
}

void editProfilePicture(Uint8List? image) {
state = state.copyWith(profilePicture: image);
}
}
Loading

0 comments on commit 9a5a24b

Please sign in to comment.