Skip to content

Commit

Permalink
feat(dynamic_user_avatar): move user avatar color refreshing in vm
Browse files Browse the repository at this point in the history
This commit moves the user avatar color refreshing and the fetching
of centauri points in the dynamic user avatar view model
  • Loading branch information
CHOOSEIT committed May 26, 2024
1 parent 7850437 commit 0e38ef4
Show file tree
Hide file tree
Showing 21 changed files with 87 additions and 219 deletions.
16 changes: 7 additions & 9 deletions lib/models/ui/user_avatar_details.dart
Original file line number Diff line number Diff line change
@@ -1,22 +1,20 @@
import "package:flutter/foundation.dart";
import "package:proxima/models/database/user/user_firestore.dart";
import "package:proxima/models/database/user/user_id_firestore.dart";

/// A class that stores data for a user avatar UI.
@immutable
class UserAvatarDetails {
final String displayName;
final UserIdFirestore? userID;
final int? centauriPoints;

/// Creates a [UserAvatarDetails] object.
/// [displayName] is the user's display name, of which
/// the first letter is displayed on the avatar.
/// [userID] is the user's ID. Can be null
/// to allow creating a user avatar without a user which
/// is useful for loading states.
/// [centauriPoints] is the user's centauri points.
/// The [centauriPoints] parameter can be null, which is useful for loading states.
const UserAvatarDetails({
required this.displayName,
required this.userID,
required this.centauriPoints,
});

/// Converts a [UserFirestore] object, [user], to a [UserAvatarDetails] object.
Expand All @@ -25,22 +23,22 @@ class UserAvatarDetails {
) {
return UserAvatarDetails(
displayName: user.data.displayName,
userID: user.uid,
centauriPoints: user.data.centauriPoints,
);
}

@override
bool operator ==(Object other) {
return other is UserAvatarDetails &&
other.displayName == displayName &&
other.userID == userID;
other.centauriPoints == centauriPoints;
}

@override
int get hashCode {
return Object.hash(
displayName,
userID,
centauriPoints,
);
}
}
10 changes: 6 additions & 4 deletions lib/viewmodels/challenge_view_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ import "package:proxima/models/ui/challenge_details.dart";
import "package:proxima/services/database/challenge_repository_service.dart";
import "package:proxima/services/database/post_repository_service.dart";
import "package:proxima/services/sensors/geolocation_service.dart";
import "package:proxima/viewmodels/dynamic_user_avatar_view_model.dart";
import "package:proxima/viewmodels/login_view_model.dart";
import "package:proxima/viewmodels/map/map_pin_view_model.dart";
import "package:proxima/viewmodels/user_centauri_points_view_model.dart";

/// This viewmodel is used to fetch the list of challenges that are displayed in
/// the challenge feed. It fetches the challenges from the database and sorts
Expand Down Expand Up @@ -101,9 +101,11 @@ class ChallengeViewModel
ref.read(mapPinViewModelProvider.notifier).refresh();

// Refresh the user centauri points after challenge completion
ref
.read(userCentauriPointsViewModelProvider(currentUser).notifier)
.refresh();
// Note: null is the current user id as represented in dynamicUserAvatarViewModelProvider
// So we have to refresh both [currentUser] and the null user
for (final user in [null, currentUser]) {
ref.read(dynamicUserAvatarViewModelProvider(user).notifier).refresh();
}
}

return pointsAwarded;
Expand Down
16 changes: 12 additions & 4 deletions lib/viewmodels/dynamic_user_avatar_view_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ import "package:proxima/models/ui/user_avatar_details.dart";
import "package:proxima/services/database/user_repository_service.dart";
import "package:proxima/viewmodels/login_view_model.dart";

// TODO: Remove the fact that the current user is "null" and accept a non-nullable user id.
/// View model for the dynamic user avatar.
/// This view model is used to fetch the user's display name given its id.
/// If the id is null, the current user's display name is fetched.
/// This view model is used to fetch the user's display name and centauri points
/// given its id. If the id is null, the current user's information is fetched.
class DynamicUserAvatarViewModel extends AutoDisposeFamilyAsyncNotifier<
UserAvatarDetails, UserIdFirestore?> {
DynamicUserAvatarViewModel();
Expand All @@ -35,11 +36,18 @@ class DynamicUserAvatarViewModel extends AutoDisposeFamilyAsyncNotifier<

return UserAvatarDetails.fromUser(user);
}

/// Refresh the user's information.
Future<void> refresh() async {
state = const AsyncValue.loading();

state = await AsyncValue.guard(() => build(arg));
}
}

//TODO: Extend to fetch the user's avatar image.
/// Flexible provider allowing to retrieve the user's display name given its id.
/// If the id is null, the current user's display name is fetched.
/// Flexible provider allowing to retrieve the user's display name and centauri points
/// given its id. If the id is null, the current user's information is fetched.
final dynamicUserAvatarViewModelProvider = AsyncNotifierProvider.autoDispose
.family<DynamicUserAvatarViewModel, UserAvatarDetails, UserIdFirestore?>(
() => DynamicUserAvatarViewModel(),
Expand Down
38 changes: 0 additions & 38 deletions lib/viewmodels/user_centauri_points_view_model.dart

This file was deleted.

10 changes: 0 additions & 10 deletions lib/viewmodels/users_ranking_view_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import "package:proxima/models/ui/ranking/ranking_details.dart";
import "package:proxima/models/ui/ranking/ranking_element_details.dart";
import "package:proxima/services/database/user_repository_service.dart";
import "package:proxima/viewmodels/login_view_model.dart";
import "package:proxima/viewmodels/user_centauri_points_view_model.dart";

/// Provides a refreshable async list of users that are sorted by
/// descending score of centauri points (called their rank).
Expand Down Expand Up @@ -68,15 +67,6 @@ class UsersRankingViewModel extends AutoDisposeAsyncNotifier<RankingDetails> {
userRankElementDetails: currentUserRankingDetails,
);

// Refresh the centauri points of the top users and the current user
for (final user in topUsersFromDb + [currentUser]) {
ref
.read(
userCentauriPointsViewModelProvider(user.uid).notifier,
)
.refresh();
}

return state;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import "package:proxima/views/components/content/user_avatar/user_avatar.dart";
class DynamicUserAvatar extends ConsumerWidget {
static const _loadingDetails = UserAvatarDetails(
displayName: "",
userID: null,
centauriPoints: null,
);

final UserIdFirestore? uid;
Expand Down
6 changes: 1 addition & 5 deletions lib/views/components/content/user_avatar/user_avatar.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import "package:flutter/material.dart";
import "package:hooks_riverpod/hooks_riverpod.dart";
import "package:proxima/models/ui/user_avatar_details.dart";
import "package:proxima/utils/user_avatar_color.dart";
import "package:proxima/viewmodels/user_centauri_points_view_model.dart";

/// A widget that displays the user's avatar.
/// It provides a [onTap] parameter to handle the user's tap,
Expand All @@ -28,9 +27,6 @@ class UserAvatar extends ConsumerWidget {

@override
Widget build(BuildContext context, WidgetRef ref) {
final userCentauriAsync =
ref.watch(userCentauriPointsViewModelProvider(details.userID));

final content = Stack(
children: [
Center(
Expand All @@ -56,7 +52,7 @@ class UserAvatar extends ConsumerWidget {
);

final backgroundColor = centauriToUserAvatarColor(
userCentauriAsync.value,
details.centauriPoints,
Theme.of(context).brightness,
);

Expand Down
10 changes: 3 additions & 7 deletions lib/views/components/content/user_profile_pop_up.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import "package:flutter/material.dart";
import "package:proxima/models/database/user/user_id_firestore.dart";
import "package:proxima/models/ui/user_avatar_details.dart";
import "package:proxima/views/components/content/user_avatar/user_avatar.dart";
import "package:proxima/views/components/content/user_avatar/dynamic_user_avatar.dart";
import "package:proxima/views/helpers/key_value_list_builder.dart";

/// A pop-up displaying a user's profile, i.e. their display name, username,
Expand Down Expand Up @@ -29,11 +28,8 @@ class UserProfilePopUp extends StatelessWidget {
.addPair("Username", username)
.addPair("Score", "$centauriPoints Centauri");

final userAvatar = UserAvatar(
details: UserAvatarDetails(
displayName: displayName,
userID: userID,
),
final userAvatar = DynamicUserAvatar(
uid: userID,
radius: 15,
);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import "package:flutter/material.dart";
import "package:hooks_riverpod/hooks_riverpod.dart";
import "package:proxima/models/database/user/user_id_firestore.dart";
import "package:proxima/models/ui/user_avatar_details.dart";
import "package:proxima/services/conversion/human_time_service.dart";
import "package:proxima/views/components/content/user_avatar/user_avatar.dart";
import "package:proxima/views/components/content/user_avatar/dynamic_user_avatar.dart";
import "package:proxima/views/components/content/user_profile_pop_up.dart";

/// This widget is used to display the info bar in the post card.
Expand Down Expand Up @@ -82,11 +81,8 @@ class PostCardHeader extends ConsumerWidget {
return Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
UserAvatar(
details: UserAvatarDetails(
displayName: posterDisplayName,
userID: posterUserID,
),
DynamicUserAvatar(
uid: posterUserID,
radius: 12,
onTap: () => onTap(context),
key: avatarKey,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ class RankingCard extends StatelessWidget {

final userDetails = UserAvatarDetails(
displayName: rankingElementDetails.userDisplayName,
userID: rankingElementDetails.userID,
centauriPoints: rankingElementDetails.centauriPoints,
);
final userInfo = Padding(
padding: const EdgeInsets.all(8),
Expand Down
7 changes: 3 additions & 4 deletions lib/views/pages/profile/components/user_account.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import "package:flutter/material.dart";
import "package:proxima/models/database/user/user_firestore.dart";
import "package:proxima/models/ui/user_avatar_details.dart";
import "package:proxima/views/components/content/user_avatar/user_avatar.dart";
import "package:proxima/views/components/content/user_avatar/dynamic_user_avatar.dart";

/// This widget display the user info in the profile page
class UserAccount extends StatelessWidget {
Expand Down Expand Up @@ -34,8 +33,8 @@ class UserAccount extends StatelessWidget {
"${userData.username} · ${userData.centauriPoints} Centauri",
);

final profilePicture = UserAvatar(
details: UserAvatarDetails.fromUser(user),
final profilePicture = DynamicUserAvatar(
uid: user.uid,
radius: 20,
);

Expand Down
16 changes: 11 additions & 5 deletions test/mocks/overrides/override_dynamic_user_avatar_view_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,25 +11,31 @@ class MockDynamicUserAvatarViewModel
extends AutoDisposeFamilyAsyncNotifier<UserAvatarDetails, UserIdFirestore?>
implements DynamicUserAvatarViewModel {
final Future<UserAvatarDetails> Function(UserIdFirestore? arg) _build;
final Future<void> Function() _onRefresh;

MockDynamicUserAvatarViewModel({
Future<UserAvatarDetails> Function(UserIdFirestore? arg)? build,
}) : _build = build ??
Future<void> Function()? onRefresh,
}) : _build = build ??
((_) async => const UserAvatarDetails(
displayName: "",
userID: null,
));
centauriPoints: null,
)),
_onRefresh = onRefresh ?? (() async {});

@override
Future<UserAvatarDetails> build(UserIdFirestore? arg) => _build(arg);

@override
Future<void> refresh() => _onRefresh();
}

final mockDynamicUserAvatarViewModelTestLoginUserOverride = [
dynamicUserAvatarViewModelProvider.overrideWith(
() => MockDynamicUserAvatarViewModel(
build: (userUID) async => UserAvatarDetails(
displayName: testingLoginUser.displayName!,
userID: testingUserFirestoreId,
centauriPoints: testingUserData.centauriPoints,
),
),
),
Expand All @@ -40,7 +46,7 @@ final mockDynamicUserAvatarViewModelEmptyDisplayNameOverride = [
() => MockDynamicUserAvatarViewModel(
build: (userUID) async => const UserAvatarDetails(
displayName: "",
userID: null,
centauriPoints: null,
),
),
),
Expand Down
42 changes: 0 additions & 42 deletions test/mocks/overrides/override_user_centauri_points_view_model.dart

This file was deleted.

Loading

0 comments on commit 0e38ef4

Please sign in to comment.