Skip to content

Commit 1648c8c

Browse files
committed
feat,rfac: implement filter logic, refactor contest screen
1 parent 2d40a8d commit 1648c8c

6 files changed

Lines changed: 215 additions & 60 deletions

File tree

lib/data/constants/assets.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ class AppAssets {
1414
static const String filter = '$_iconsRoot/filter.svg';
1515
static const String bell = '$_iconsRoot/bell.svg';
1616
static const String selectedBell = '$_iconsRoot/selected_bell.svg';
17+
static const String clock = '$_iconsRoot/clock.svg';
1718

1819
// Images
1920
static const String circle1 = '$_imagesRoot/circle1.svg';

lib/data/constants/strings.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ class AppStrings {
33
static const String hiveBoxName = 'app_box';
44
static const String authTokenKey = 'auth_token';
55
static const String userKey = 'user';
6+
static const String filterKey = 'filter';
67

78
// Error
89
static const String genericError = 'Something went wrong';

lib/presentation/contests/bloc/contests_bloc.dart

Lines changed: 131 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@ import 'package:equatable/equatable.dart';
22
import 'package:flutter_bloc/flutter_bloc.dart';
33
import 'package:freezed_annotation/freezed_annotation.dart';
44

5+
import '../../../data/constants/strings.dart';
6+
import '../../../data/services/local/storage_service.dart';
57
import '../../../domain/models/contest.dart';
8+
import '../../../domain/models/contest_filter.dart';
69
import '../../../domain/repositories/cp_repository.dart';
710

811
part 'contests_event.dart';
@@ -17,12 +20,139 @@ class ContestsBloc extends Bloc<ContestsEvent, ContestsState> {
1720

1821
void _fetchContests(FetchContests event, Emitter<ContestsState> emit) async {
1922
final contest = await CPRepository.contestList();
20-
emit(state.copyWith(contest: contest, isLoading: false));
23+
_ongoing = [...?contest?.ongoing];
24+
_upcoming = [...?contest?.upcoming];
25+
applyFilter();
26+
final contests = [..._filteredUpcoming, ..._filteredOngoing];
27+
emit(state.copyWith(contests: contests, isLoading: false));
2128
}
2229

2330
void _updateFilter(UpdateFilter event, Emitter<ContestsState> emit) {}
2431

32+
void applyFilter() {
33+
_filteredOngoing = [];
34+
_filteredUpcoming = [];
35+
if (_filter!.ongoing ?? false) {
36+
_filteredOngoing.addAll(
37+
_ongoing.where(
38+
(element) => _filter!.check(
39+
ongoing: element,
40+
),
41+
),
42+
);
43+
}
44+
45+
if (_filter!.upcoming ?? false) {
46+
_filteredUpcoming.addAll(
47+
_upcoming.where(
48+
(element) => _filter!.check(
49+
upcoming: element,
50+
),
51+
),
52+
);
53+
}
54+
}
55+
56+
ContestFilter? _filter;
57+
List<Ongoing> _ongoing = [], _filteredOngoing = [];
58+
List<Upcoming> _upcoming = [], _filteredUpcoming = [];
59+
2560
void init() {
61+
final exists = StorageService.exists(AppStrings.filterKey);
62+
if (!exists) {
63+
StorageService.filter = ContestFilter(
64+
duration: 4,
65+
platform: [true, true, true, true, false],
66+
startDate: DateTime.now(),
67+
ongoing: true,
68+
upcoming: true,
69+
);
70+
}
71+
72+
_filter = StorageService.filter;
2673
add(const FetchContests());
2774
}
2875
}
76+
77+
extension on ContestFilter {
78+
bool checkPlatform(String platformName) {
79+
switch (platformName.toLowerCase()) {
80+
case 'codechef':
81+
return platform?[0] ?? false;
82+
case 'codeforces':
83+
return platform?[1] ?? false;
84+
case 'hackerearth':
85+
return platform?[2] ?? false;
86+
case 'hackerrank':
87+
return platform?[3] ?? false;
88+
default:
89+
return platform?[4] ?? false;
90+
}
91+
}
92+
93+
bool check({
94+
Upcoming? upcoming,
95+
Ongoing? ongoing,
96+
}) {
97+
assert(upcoming != null || ongoing != null, '');
98+
final platfromCheck = checkPlatform(
99+
upcoming != null ? upcoming.platform : ongoing!.platform,
100+
);
101+
102+
if (!platfromCheck) return platfromCheck;
103+
104+
Duration _maxDuration;
105+
switch (duration) {
106+
case 0:
107+
_maxDuration = const Duration(hours: 2);
108+
break;
109+
case 1:
110+
_maxDuration = const Duration(hours: 3);
111+
break;
112+
case 2:
113+
_maxDuration = const Duration(hours: 5);
114+
break;
115+
case 3:
116+
_maxDuration = const Duration(days: 1);
117+
break;
118+
case 4:
119+
_maxDuration = const Duration(days: 10);
120+
break;
121+
case 5:
122+
_maxDuration = const Duration(days: 31);
123+
break;
124+
default:
125+
_maxDuration = Duration(days: 1e5.toInt());
126+
}
127+
128+
final durationCheck = upcoming != null
129+
? upcoming.compareDuration(_maxDuration)
130+
: ongoing!.compareDuration(_maxDuration);
131+
132+
if (!durationCheck) return durationCheck;
133+
134+
bool? startCheck;
135+
if (upcoming != null) startCheck = upcoming.compareStart(startDate!);
136+
137+
startCheck ??= true;
138+
if (!startCheck) return startCheck;
139+
140+
return true;
141+
}
142+
}
143+
144+
extension on Upcoming {
145+
bool compareDuration(Duration _duration) {
146+
return _duration.compareTo(endTime.difference(startTime)) >= 0;
147+
}
148+
149+
bool compareStart(DateTime _startDate) {
150+
return startTime.isAfter(_startDate);
151+
}
152+
}
153+
154+
extension on Ongoing {
155+
bool compareDuration(Duration _duration) {
156+
return _duration.compareTo(endTime.difference(DateTime.now())) >= 0;
157+
}
158+
}

lib/presentation/contests/bloc/contests_state.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ part of 'contests_bloc.dart';
44
class ContestsState with _$ContestsState {
55
const factory ContestsState({
66
@Default(true) bool isLoading,
7-
Contest? contest,
7+
@Default([]) List contests,
88
}) = _ContestsState;
99

1010
const ContestsState._();

lib/presentation/contests/contests_screen.dart

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import 'package:flutter/material.dart';
22
import 'package:flutter_bloc/flutter_bloc.dart';
33

4+
import '../../data/constants/colors.dart';
5+
import '../../domain/models/contest.dart';
46
import 'bloc/contests_bloc.dart';
57
import 'widgets/contest_card.dart';
68
import 'widgets/contest_header.dart';
@@ -17,6 +19,7 @@ class ContestsScreen extends StatelessWidget {
1719
child: BlocBuilder<ContestsBloc, ContestsState>(
1820
builder: (context, state) {
1921
return Scaffold(
22+
backgroundColor: AppColors.white,
2023
appBar: const PreferredSize(
2124
preferredSize: Size.fromHeight(kToolbarHeight),
2225
child: ContestHeader(),
@@ -26,20 +29,21 @@ class ContestsScreen extends StatelessWidget {
2629
if (state.isLoading) {
2730
return const LoadingState();
2831
}
29-
if (state.contest == null) {
32+
if (state.contests.isEmpty) {
3033
return const EmptyState();
3134
}
3235
return ListView.builder(
33-
itemCount: (state.contest!.upcoming?.length ?? 0) + 0,
34-
// (state.contest!.ongoing?.length ?? 0),
36+
itemCount: state.contests.length,
3537
itemBuilder: (context, index) {
36-
final contest = state.contest!.upcoming![index];
37-
// if (index >= (state.contest!.upcoming?.length ?? 0)) {
38-
// index -= state.contest!.upcoming?.length ?? 0;
39-
// contest = state.contest!.ongoing?[index];
40-
// }
38+
if (state.contests[index] is Ongoing) {
39+
return ContestCard(
40+
ongoing: state.contests[index],
41+
);
42+
}
4143

42-
return ContestCard(contest: contest);
44+
return ContestCard(
45+
upcoming: state.contests[index],
46+
);
4347
},
4448
);
4549
},

lib/presentation/contests/widgets/contest_card.dart

Lines changed: 68 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -9,67 +9,86 @@ import '../../../domain/models/contest.dart';
99
import '../../../utils/contest_util.dart';
1010

1111
class ContestCard extends StatelessWidget {
12-
const ContestCard({required this.contest, Key? key}) : super(key: key);
13-
final Upcoming contest;
12+
const ContestCard({
13+
this.upcoming,
14+
this.ongoing,
15+
Key? key,
16+
}) : assert(upcoming != null || ongoing != null, ''),
17+
super(key: key);
18+
final Upcoming? upcoming;
19+
final Ongoing? ongoing;
1420

1521
@override
1622
Widget build(BuildContext context) {
17-
return Padding(
18-
padding: EdgeInsets.symmetric(
19-
vertical: 10.h,
20-
),
21-
child: Row(
22-
mainAxisAlignment: MainAxisAlignment.spaceBetween,
23-
crossAxisAlignment: CrossAxisAlignment.start,
24-
children: [
25-
Padding(
26-
padding: EdgeInsets.all(15.r),
27-
child: Container(
28-
width: 24.r,
29-
decoration: const BoxDecoration(shape: BoxShape.circle),
30-
child: Image.asset(contest.icon),
31-
),
32-
),
33-
Expanded(
34-
child: Column(
35-
crossAxisAlignment: CrossAxisAlignment.start,
36-
children: [
37-
Text(
38-
'${contest.platformName} hosted a contest',
39-
style: AppStyles.h6,
23+
return InkWell(
24+
onTap: () {},
25+
child: Padding(
26+
padding: EdgeInsets.symmetric(
27+
vertical: 10.h,
28+
),
29+
child: Row(
30+
mainAxisAlignment: MainAxisAlignment.spaceBetween,
31+
crossAxisAlignment: CrossAxisAlignment.start,
32+
children: [
33+
Padding(
34+
padding: EdgeInsets.all(15.r),
35+
child: Container(
36+
width: 24.r,
37+
decoration: const BoxDecoration(shape: BoxShape.circle),
38+
child: Image.asset(
39+
upcoming != null ? upcoming!.icon : ongoing!.icon,
4040
),
41-
Padding(
42-
padding: EdgeInsets.symmetric(vertical: 6.h),
43-
child: Text(
44-
contest.name.trim(),
45-
style: AppStyles.h4.copyWith(
46-
fontSize: 16.sp,
41+
),
42+
),
43+
Expanded(
44+
child: Column(
45+
crossAxisAlignment: CrossAxisAlignment.start,
46+
children: [
47+
Text(
48+
'${upcoming != null ? upcoming!.platformName : ongoing!.platformName} hosted a contest',
49+
style: AppStyles.h6,
50+
),
51+
Padding(
52+
padding: EdgeInsets.symmetric(vertical: 6.h),
53+
child: Text(
54+
upcoming != null ? upcoming!.name : ongoing!.name,
55+
style: AppStyles.h4.copyWith(
56+
fontSize: 16.sp,
57+
),
4758
),
4859
),
49-
),
50-
Text(
51-
contest.time,
52-
style: AppStyles.h6,
53-
),
54-
],
60+
Wrap(
61+
children: [
62+
SvgPicture.asset(
63+
AppAssets.clock,
64+
),
65+
SizedBox(width: 4.w),
66+
Text(
67+
upcoming != null ? upcoming!.time : ongoing!.time,
68+
style: AppStyles.h6,
69+
),
70+
],
71+
),
72+
],
73+
),
74+
),
75+
IconButton(
76+
onPressed: () {},
77+
icon: SvgPicture.asset(AppAssets.bell),
5578
),
56-
),
57-
IconButton(
58-
onPressed: () {},
59-
icon: SvgPicture.asset(AppAssets.bell),
60-
),
61-
],
79+
],
80+
),
6281
),
6382
);
6483
}
6584
}
6685

67-
// extension on Ongoing {
68-
// String get platformName => ContestUtil.getPlatformName(platform);
69-
// String get icon => ContestUtil.getPlatformIcon(platform);
70-
// String get time =>
71-
// 'Ends on ${DateFormat('dd, MMMM yyyy hh:mm a').format(endTime)}';
72-
// }
86+
extension on Ongoing {
87+
String get platformName => ContestUtil.getPlatformName(platform);
88+
String get icon => ContestUtil.getPlatformIcon(platform);
89+
String get time =>
90+
'Ends on ${DateFormat('dd, MMMM yyyy hh:mm a').format(endTime)}';
91+
}
7392

7493
extension on Upcoming {
7594
String get platformName => ContestUtil.getPlatformName(platform);

0 commit comments

Comments
 (0)