Skip to content

Commit a3c4ad2

Browse files
committed
feat: implement following view
1 parent 4aecd03 commit a3c4ad2

11 files changed

Lines changed: 374 additions & 98 deletions

File tree

lib/presentation/contests/widgets/contest_header.dart

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import 'package:flutter/material.dart';
22
import 'package:flutter_bloc/flutter_bloc.dart';
3+
import 'package:flutter_screenutil/flutter_screenutil.dart';
34
import 'package:flutter_svg/flutter_svg.dart';
45

56
import '../../../data/constants/assets.dart';
67
import '../../../data/constants/colors.dart';
8+
import '../../../data/constants/styles.dart';
79
import '../bloc/contests_bloc.dart';
810
import 'filter_sheet.dart';
911

@@ -15,12 +17,11 @@ class ContestHeader extends StatelessWidget {
1517
return AppBar(
1618
automaticallyImplyLeading: false,
1719
backgroundColor: AppColors.white,
18-
title: const Text(
20+
title: Text(
1921
'Contest',
20-
style: TextStyle(
21-
fontSize: 22,
22+
style: AppStyles.h4.copyWith(
23+
fontSize: 22.sp,
2224
fontWeight: FontWeight.w600,
23-
color: Colors.black,
2425
),
2526
),
2627
actions: [

lib/presentation/profile/bloc/profile_bloc.dart

Lines changed: 37 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@ import 'package:equatable/equatable.dart';
55
import 'package:flutter_bloc/flutter_bloc.dart';
66
import 'package:freezed_annotation/freezed_annotation.dart';
77

8+
import '../../../data/constants/strings.dart';
89
import '../../../data/services/local/storage_service.dart';
910
import '../../../domain/models/activity_details.dart';
1011
import '../../../domain/models/following.dart';
11-
import '../../../domain/models/submission.dart';
1212
import '../../../domain/models/submission_status.dart';
1313
import '../../../domain/models/user.dart';
1414
import '../../../domain/repositories/user_repository.dart';
@@ -22,40 +22,31 @@ class ProfileBloc extends Bloc<ProfileEvent, ProfileState> {
2222
on<FetchDetails>(_onFetchDetails);
2323
on<UpdateYear>(_onUpdateYear);
2424
on<UpdateMonth>(_onUpdateMonth);
25+
on<ShowFollowing>(_onShowFollowing);
2526
}
2627

2728
void _onFetchDetails(FetchDetails event, Emitter<ProfileState> emit) async {
2829
if (!state.isLoading) emit(state.copyWith(isLoading: true));
2930

3031
// Fetch user details
31-
final User? _user;
3232
if (event.userId.isEmpty) {
3333
_user = StorageService.user;
3434
} else {
3535
_user = await UserRepository.fetchUserDetails(uid: event.userId);
3636
}
3737

38-
final _followingList = await UserRepository.getFollowingList();
38+
_followingList = await UserRepository.getFollowingList();
3939

4040
final _subStats = await UserRepository.getSubmissionStatusData(_user!.id!);
4141

42-
final _submission = _user.recentSubmissions;
42+
final _activityDetails =
43+
await UserRepository.getActivityDetails(_user!.id!);
4344

44-
final _activityDetails = await UserRepository.getActivityDetails(_user.id!);
45-
46-
final _activity = <DateTime, dynamic>{};
4745
for (final activity in _activityDetails ?? <ActivityDetails>[]) {
4846
if (activity.createdAt == null) continue;
4947
_activity[activity.createdAt!] = activity.correct;
5048
}
5149

52-
final _mostRecentSubmission = <Submission>[];
53-
for (var index = 0;
54-
index < (_submission?.length ?? 0) && index < 2;
55-
index++) {
56-
_mostRecentSubmission.add(_submission![index]);
57-
}
58-
5950
bool? _isFollowing;
6051
for (final follower in _followingList ?? <Following>[]) {
6152
if (follower.id == event.userId) {
@@ -71,12 +62,11 @@ class ProfileBloc extends Bloc<ProfileEvent, ProfileState> {
7162
user: _user,
7263
following: _followingList,
7364
submissionStatus: _subStats,
74-
submission: _submission,
75-
activity: _activity,
7665
personalProfile: event.userId.isEmpty,
7766
isFollowing: _isFollowing,
7867
currentYear: _currentYear,
7968
currentTriplet: _currentTriplet,
69+
showFollowing: false,
8070
));
8171
}
8272

@@ -107,6 +97,32 @@ class ProfileBloc extends Bloc<ProfileEvent, ProfileState> {
10797
));
10898
}
10999

100+
void _onShowFollowing(ShowFollowing event, Emitter<ProfileState> emit) async {
101+
if (event.toShow) {
102+
_followingList = await UserRepository.getFollowingList();
103+
}
104+
emit(state.copyWith(
105+
following: _followingList,
106+
showFollowing: event.toShow,
107+
));
108+
}
109+
110+
static Future follow(String userId) async {
111+
final statuscode = await UserRepository.followUser(userId);
112+
113+
if (statuscode != 200) {
114+
throw Exception(AppStrings.genericError);
115+
}
116+
}
117+
118+
static Future unfollow(String userId) async {
119+
final statuscode = await UserRepository.unfollowUser(userId);
120+
121+
if (statuscode != 200) {
122+
throw Exception(AppStrings.genericError);
123+
}
124+
}
125+
110126
// Index -> Index%4
111127
static List<String> monthTriplet(int index) {
112128
switch (index) {
@@ -125,7 +141,8 @@ class ProfileBloc extends Bloc<ProfileEvent, ProfileState> {
125141
}
126142

127143
// Cell Color for Acceptance Graph
128-
static Color getCellColor(int index) {
144+
Color getCellColor(DateTime date) {
145+
final index = _activity[date] ?? 0;
129146
if (index < 0) {
130147
return Color.fromRGBO(255, 0, 0, math.min(-0.2 * index, 1));
131148
} else if (index > 0) {
@@ -138,4 +155,7 @@ class ProfileBloc extends Bloc<ProfileEvent, ProfileState> {
138155

139156
int _currentYear = DateTime.now().year;
140157
int _currentTriplet = DateTime.now().month ~/ 3;
158+
List<Following>? _followingList;
159+
User? _user;
160+
final Map<DateTime, dynamic> _activity = <DateTime, dynamic>{};
141161
}

lib/presentation/profile/bloc/profile_event.dart

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,12 @@ class UpdateMonth extends ProfileEvent {
3333
@override
3434
List<Object?> get props => [increment];
3535
}
36+
37+
class ShowFollowing extends ProfileEvent {
38+
const ShowFollowing({required this.toShow});
39+
40+
final bool toShow;
41+
42+
@override
43+
List<Object?> get props => [toShow];
44+
}

lib/presentation/profile/bloc/profile_state.dart

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,10 @@ class ProfileState with _$ProfileState {
66
@Default(true) bool isLoading,
77
@Default(true) bool personalProfile,
88
@Default(false) bool isFollowing,
9+
@Default(false) bool showFollowing,
910
User? user,
1011
List<Following>? following,
1112
SubmissionStatus? submissionStatus,
12-
List<Submission>? submission,
13-
Map<DateTime, dynamic>? activity,
1413
int? currentYear,
1514
int? currentTriplet,
1615
}) = _ProfileState;
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import 'package:flutter/material.dart';
2+
import 'package:flutter_screenutil/flutter_screenutil.dart';
3+
4+
import '../../../data/constants/colors.dart';
5+
6+
/// [Button] is the model for the Follow and Following Buttons
7+
class Button extends StatelessWidget {
8+
const Button({
9+
required this.text,
10+
required this.isFilled,
11+
this.showTrailingButton = false,
12+
this.trailing,
13+
this.onTap,
14+
Key? key,
15+
}) : assert(!(showTrailingButton ^ (trailing != null)), ''),
16+
super(key: key);
17+
final String text;
18+
final bool isFilled;
19+
final bool showTrailingButton;
20+
final Widget? trailing;
21+
final VoidCallback? onTap;
22+
23+
@override
24+
Widget build(BuildContext context) {
25+
return GestureDetector(
26+
onTap: onTap,
27+
child: Padding(
28+
padding: EdgeInsets.symmetric(
29+
horizontal: 6.w,
30+
vertical: 5.h,
31+
),
32+
child: Container(
33+
decoration: BoxDecoration(
34+
color: isFilled ? AppColors.primary : AppColors.white,
35+
border: Border.all(
36+
color: AppColors.primary,
37+
),
38+
borderRadius: BorderRadius.all(
39+
Radius.circular(2.r),
40+
),
41+
),
42+
padding: EdgeInsets.all(8.r),
43+
child: Text.rich(
44+
TextSpan(
45+
children: [
46+
TextSpan(
47+
text: text,
48+
style: TextStyle(
49+
fontSize: 16.sp,
50+
color: isFilled ? AppColors.white : AppColors.primary,
51+
),
52+
),
53+
if (showTrailingButton)
54+
WidgetSpan(
55+
child: trailing!,
56+
)
57+
],
58+
),
59+
),
60+
),
61+
),
62+
);
63+
}
64+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import 'package:flutter/material.dart';
2+
3+
import 'button.dart';
4+
5+
class FollowButton extends StatelessWidget {
6+
const FollowButton({this.onTap, Key? key}) : super(key: key);
7+
final VoidCallback? onTap;
8+
9+
@override
10+
Widget build(BuildContext context) {
11+
return Button(
12+
text: 'FOLLOW',
13+
isFilled: false,
14+
onTap: onTap,
15+
);
16+
}
17+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import 'package:flutter/material.dart';
2+
import 'package:flutter_screenutil/flutter_screenutil.dart';
3+
4+
import '../../../data/constants/colors.dart';
5+
import 'button.dart';
6+
7+
class FollowingButton extends StatelessWidget {
8+
const FollowingButton({this.onTap, Key? key}) : super(key: key);
9+
final VoidCallback? onTap;
10+
11+
@override
12+
Widget build(BuildContext context) {
13+
return Button(
14+
text: 'FOLLOWING',
15+
isFilled: true,
16+
onTap: onTap,
17+
showTrailingButton: true,
18+
trailing: Icon(
19+
Icons.check,
20+
size: 16.r,
21+
color: AppColors.white,
22+
),
23+
);
24+
}
25+
}

lib/presentation/profile/profile_screen.dart

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import '../../data/constants/styles.dart';
66
import 'bloc/profile_bloc.dart';
77
import 'widgets/acceptance_graph.dart';
88
import 'widgets/accuracy_display.dart';
9+
import 'widgets/following_view.dart';
910
import 'widgets/loading_state.dart';
1011
import 'widgets/profile_header.dart';
1112
import 'widgets/question_solved.dart';
@@ -21,10 +22,13 @@ class ProfileScreen extends StatelessWidget {
2122
child: BlocBuilder<ProfileBloc, ProfileState>(
2223
// When Loading state changes
2324
buildWhen: (previous, current) =>
24-
previous.isLoading ^ current.isLoading,
25+
previous.isLoading ^ current.isLoading ||
26+
previous.showFollowing ^ current.showFollowing,
2527
builder: (context, state) {
2628
if (state.isLoading) return const ProfileLoadingState();
2729

30+
if (state.showFollowing) return const FollowingView();
31+
2832
return ListView(
2933
children: [
3034
const ProfileHeader(),

lib/presentation/profile/widgets/acceptance_graph.dart

Lines changed: 12 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ class AcceptanceGraph extends StatelessWidget {
1818
(previous.currentTriplet != current.currentTriplet);
1919
},
2020
builder: (context, state) {
21+
final _bloc = context.read<ProfileBloc>();
2122
var day = 0;
2223

2324
final firstWeekDay = util.DateUtils.fistDayofMonth(
@@ -66,9 +67,7 @@ class AcceptanceGraph extends StatelessWidget {
6667
),
6768
IconButton(
6869
onPressed: () {
69-
context
70-
.read<ProfileBloc>()
71-
.add(const UpdateYear(increment: true));
70+
_bloc.add(const UpdateYear(increment: true));
7271
},
7372
icon: Icon(
7473
Icons.chevron_right,
@@ -84,9 +83,7 @@ class AcceptanceGraph extends StatelessWidget {
8483
children: [
8584
IconButton(
8685
onPressed: () {
87-
context
88-
.read<ProfileBloc>()
89-
.add(const UpdateMonth(increment: false));
86+
_bloc.add(const UpdateMonth(increment: false));
9087
},
9188
icon: Icon(
9289
Icons.chevron_left,
@@ -107,9 +104,7 @@ class AcceptanceGraph extends StatelessWidget {
107104
).toList(),
108105
IconButton(
109106
onPressed: () {
110-
context
111-
.read<ProfileBloc>()
112-
.add(const UpdateMonth(increment: true));
107+
_bloc.add(const UpdateMonth(increment: true));
113108
},
114109
icon: Icon(
115110
Icons.chevron_right,
@@ -154,12 +149,8 @@ class AcceptanceGraph extends StatelessWidget {
154149
width: 20.r,
155150
height: 20.r,
156151
margin: EdgeInsets.all(2.r),
157-
color: ProfileBloc.getCellColor(state.activity?[
158-
util.DateUtils.getDateTime(
159-
day,
160-
state.currentTriplet!,
161-
state.currentYear!)] ??
162-
0),
152+
color: _bloc.getCellColor(util.DateUtils.getDateTime(
153+
day, state.currentTriplet!, state.currentYear!)),
163154
);
164155
}),
165156
],
@@ -174,12 +165,9 @@ class AcceptanceGraph extends StatelessWidget {
174165
width: 20.r,
175166
height: 20.r,
176167
margin: EdgeInsets.all(2.r),
177-
color: ProfileBloc.getCellColor(state.activity?[
178-
util.DateUtils.getDateTime(
179-
day,
180-
state.currentTriplet!,
181-
state.currentYear!)] ??
182-
0),
168+
color: _bloc.getCellColor(
169+
util.DateUtils.getDateTime(day,
170+
state.currentTriplet!, state.currentYear!)),
183171
);
184172
}),
185173
);
@@ -197,12 +185,9 @@ class AcceptanceGraph extends StatelessWidget {
197185
width: 20.r,
198186
height: 20.r,
199187
margin: EdgeInsets.all(2.r),
200-
color: ProfileBloc.getCellColor(state.activity?[
201-
util.DateUtils.getDateTime(
202-
day,
203-
state.currentTriplet!,
204-
state.currentYear!)] ??
205-
0),
188+
color: _bloc.getCellColor(
189+
util.DateUtils.getDateTime(day,
190+
state.currentTriplet!, state.currentYear!)),
206191
);
207192
}),
208193
...List.generate(7 - lastWeekDays, (_) {

0 commit comments

Comments
 (0)