Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

improve profile pictures download #43

Merged
merged 1 commit into from
Feb 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading