Skip to content

Commit 34061c8

Browse files
Add like feature (#31)
* add like feature on community activities * fix can open activity on profile * edit screenshot * dispose activity items providers when activity list is destroyed
1 parent a9ba57d commit 34061c8

12 files changed

Lines changed: 287 additions & 101 deletions

File tree

lib/data/api/activity_api.dart

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,4 +74,16 @@ class ActivityApi {
7474
data: request.toMap());
7575
return ActivityResponse.fromMap(response?.data);
7676
}
77+
78+
/// Like the activity
79+
static Future<void> like(String activityId) async {
80+
await ApiHelper.makeRequest('${ActivityApi.url}like', 'POST_FORM_DATA',
81+
data: {'id': activityId});
82+
}
83+
84+
/// Dislike the activity
85+
static Future<void> dislike(String activityId) async {
86+
await ApiHelper.makeRequest('${ActivityApi.url}dislike', 'POST_FORM_DATA',
87+
data: {'id': activityId});
88+
}
7789
}

lib/data/model/response/activity_response.dart

Lines changed: 33 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,12 @@ class ActivityResponse extends Equatable {
3636
/// The user concerned by the activity
3737
final UserResponse user;
3838

39+
/// The count of likes on the activity
40+
final double likesCount;
41+
42+
/// has current user liked ?
43+
final bool hasCurrentUserLiked;
44+
3945
/// Constructs an ActivityResponse object with the given parameters.
4046
const ActivityResponse(
4147
{required this.id,
@@ -46,7 +52,9 @@ class ActivityResponse extends Equatable {
4652
required this.speed,
4753
required this.time,
4854
required this.locations,
49-
required this.user});
55+
required this.user,
56+
required this.likesCount,
57+
required this.hasCurrentUserLiked});
5058

5159
@override
5260
List<Object?> get props => [
@@ -58,7 +66,9 @@ class ActivityResponse extends Equatable {
5866
speed,
5967
time,
6068
...locations,
61-
user
69+
user,
70+
likesCount,
71+
hasCurrentUserLiked
6272
];
6373

6474
/// Creates an ActivityResponse object from a JSON map.
@@ -79,10 +89,14 @@ class ActivityResponse extends Equatable {
7989
? double.parse(map['speed'])
8090
: map['speed'].toDouble(),
8191
time: map['time'].toDouble(),
92+
likesCount: map['likesCount'].toDouble(),
93+
hasCurrentUserLiked: map['hasCurrentUserLiked'],
8294
locations: (map['locations'] as List<dynamic>)
8395
.map<LocationResponse>((item) => LocationResponse.fromMap(item))
8496
.toList(),
85-
user: UserResponse.fromMap(map['user']));
97+
user: UserResponse.fromMap(
98+
map['user'],
99+
));
86100
}
87101

88102
/// Converts the ActivityResponse object to an Activity entity.
@@ -98,18 +112,21 @@ class ActivityResponse extends Equatable {
98112
..sort((a, b) => a.datetime.compareTo(b.datetime));
99113

100114
return Activity(
101-
id: id,
102-
type: type,
103-
startDatetime: startDatetime,
104-
endDatetime: endDatetime,
105-
distance: distance,
106-
speed: speed,
107-
time: time,
108-
locations: activityLocations,
109-
user: User(
110-
id: user.id,
111-
username: user.username,
112-
firstname: user.firstname,
113-
lastname: user.lastname));
115+
id: id,
116+
type: type,
117+
startDatetime: startDatetime,
118+
endDatetime: endDatetime,
119+
distance: distance,
120+
speed: speed,
121+
time: time,
122+
locations: activityLocations,
123+
likesCount: likesCount,
124+
hasCurrentUserLiked: hasCurrentUserLiked,
125+
user: User(
126+
id: user.id,
127+
username: user.username,
128+
firstname: user.firstname,
129+
lastname: user.lastname),
130+
);
114131
}
115132
}

lib/data/repositories/activity_repository_impl.dart

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,4 +53,14 @@ class ActivityRepositoryImpl extends ActivityRepository {
5353
final activityResponse = await ActivityApi.editActivity(request);
5454
return activityResponse.toEntity();
5555
}
56+
57+
@override
58+
Future<void> like(String id) async {
59+
await ActivityApi.like(id);
60+
}
61+
62+
@override
63+
Future<void> dislike(String id) async {
64+
await ActivityApi.dislike(id);
65+
}
5666
}

lib/domain/entities/activity.dart

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,12 @@ class Activity extends Equatable {
3333
// The user concerned by the activity
3434
final User user;
3535

36+
/// The count of likes on the activity
37+
final double likesCount;
38+
39+
/// has current user liked ?
40+
final bool hasCurrentUserLiked;
41+
3642
/// Constructs an Activity object with the given parameters.
3743
const Activity(
3844
{required this.id,
@@ -43,7 +49,26 @@ class Activity extends Equatable {
4349
required this.speed,
4450
required this.time,
4551
required this.locations,
46-
required this.user});
52+
required this.user,
53+
required this.likesCount,
54+
required this.hasCurrentUserLiked});
55+
56+
Activity copy(
57+
{ActivityType? type, double? likesCount, bool? hasCurrentUserLiked}) {
58+
return Activity(
59+
id: id,
60+
type: type ?? this.type,
61+
startDatetime: startDatetime,
62+
endDatetime: endDatetime,
63+
distance: distance,
64+
speed: speed,
65+
time: time,
66+
locations: locations,
67+
user: user,
68+
likesCount: likesCount ?? this.likesCount,
69+
hasCurrentUserLiked: hasCurrentUserLiked ?? this.hasCurrentUserLiked,
70+
);
71+
}
4772

4873
@override
4974
List<Object?> get props => [
@@ -55,6 +80,8 @@ class Activity extends Equatable {
5580
speed,
5681
time,
5782
...locations,
58-
user
83+
user,
84+
likesCount,
85+
hasCurrentUserLiked
5986
];
6087
}

lib/domain/repositories/activity_repository.dart

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,10 @@ abstract class ActivityRepository {
2323

2424
/// Edits an existing activity.
2525
Future<Activity> editActivity(ActivityRequest request);
26+
27+
/// Like the activity
28+
Future<void> like(String id);
29+
30+
/// Dislike the activity
31+
Future<void> dislike(String id);
2632
}
Lines changed: 61 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,77 @@
11
import 'dart:typed_data';
22

3+
import 'package:flutter/material.dart';
34
import 'package:hooks_riverpod/hooks_riverpod.dart';
5+
import 'package:run_flutter_run/data/repositories/activity_repository_impl.dart';
6+
import 'package:run_flutter_run/domain/entities/activity.dart';
47
import '../../../../data/repositories/user_repository_impl.dart';
8+
import '../../../../main.dart';
9+
import '../../../my_activities/screens/activity_details_screen.dart';
510
import 'state/activity_item_state.dart';
611

712
/// Provider for the activity item view model.
8-
final activityItemViewModelProvider =
9-
StateNotifierProvider.autoDispose<ActivityItemViewModel, ActivityItemState>(
10-
(ref) => ActivityItemViewModel(ref));
13+
final activityItemViewModelProvider = StateNotifierProvider.family<
14+
ActivityItemViewModel,
15+
ActivityItemState,
16+
String>((ref, activityId) => ActivityItemViewModel(ref, activityId));
1117

1218
/// View model for the activity item widget.
1319
class ActivityItemViewModel extends StateNotifier<ActivityItemState> {
14-
late final Ref ref;
20+
final String activityId;
21+
final Ref ref;
1522

16-
ActivityItemViewModel(this.ref) : super(ActivityItemState.initial());
23+
ActivityItemViewModel(this.ref, this.activityId)
24+
: super(ActivityItemState.initial());
25+
26+
void setActivity(Activity activity) {
27+
state = state.copyWith(activity: activity);
28+
}
1729

1830
Future<Uint8List?> getProfilePicture(String userId) async {
1931
return ref.read(userRepositoryProvider).downloadProfilePicture(userId);
2032
}
33+
34+
/// Like the activity.
35+
Future<void> like(Activity activity) async {
36+
await ref.read(activityRepositoryProvider).like(activity.id);
37+
setActivity(activity.copy(
38+
likesCount: activity.likesCount + 1, hasCurrentUserLiked: true));
39+
}
40+
41+
/// Dislike the activity.
42+
Future<void> dislike(Activity activity) async {
43+
await ref.read(activityRepositoryProvider).dislike(activity.id);
44+
setActivity(activity.copy(
45+
likesCount: activity.likesCount - 1, hasCurrentUserLiked: false));
46+
}
47+
48+
/// Retrieves the details of an activity.
49+
Future<Activity> getActivityDetails(Activity activity) async {
50+
try {
51+
final activityDetails = await ref
52+
.read(activityRepositoryProvider)
53+
.getActivityById(id: activity.id);
54+
return activityDetails;
55+
} catch (error) {
56+
// Handle error
57+
rethrow;
58+
}
59+
}
60+
61+
/// Navigates to the activity details screen.
62+
void goToActivity(Activity activityDetails) {
63+
navigatorKey.currentState?.push(
64+
PageRouteBuilder(
65+
transitionDuration: const Duration(milliseconds: 500),
66+
pageBuilder: (context, animation, secondaryAnimation) =>
67+
SlideTransition(
68+
position: Tween<Offset>(
69+
begin: const Offset(1.0, 0.0),
70+
end: Offset.zero,
71+
).animate(animation),
72+
child: ActivityDetailsScreen(activity: activityDetails),
73+
),
74+
),
75+
);
76+
}
2177
}
Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,17 @@
1+
import '../../../../../domain/entities/activity.dart';
2+
13
/// The state class for activity item.
24
class ActivityItemState {
3-
const ActivityItemState();
5+
final Activity? activity;
6+
7+
const ActivityItemState({this.activity});
48

59
/// Factory method to create the initial state.
610
factory ActivityItemState.initial() {
7-
return const ActivityItemState();
11+
return const ActivityItemState(activity: null);
12+
}
13+
14+
ActivityItemState copyWith({Activity? activity}) {
15+
return ActivityItemState(activity: activity ?? this.activity);
816
}
917
}

0 commit comments

Comments
 (0)