Skip to content

Commit 03dec90

Browse files
authored
feat: add_contest_2_calendar (#63)
* feat: add_contest_2_calendar * docs: web description * docs: adds og tags * docs: sdkVersion flutter 해줘 * v2.1.0+76 * v2.1.0+77 * dev: flutter_app_badger 삭제
1 parent 1468ce1 commit 03dec90

File tree

17 files changed

+337
-158
lines changed

17 files changed

+337
-158
lines changed

android/app/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ android {
5252

5353
defaultConfig {
5454
applicationId "com.w8385.my_solved"
55-
minSdkVersion 34
55+
minSdkVersion flutter.minSdkVersion
5656
targetSdkVersion 34
5757
multiDexEnabled true
5858
versionCode flutterVersionCode.toInteger()

android/app/src/main/AndroidManifest.xml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
1010
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
1111
<uses-permission android:name="android.permission.USE_EXACT_ALARM" />
12+
<uses-permission android:name="android.permission.WRITE_CALENDAR" />
13+
<uses-permission android:name="android.permission.READ_CALENDAR" />
1214

1315
<application
1416
android:name="${applicationName}"
@@ -52,5 +54,12 @@
5254
android:name="com.dexterous.flutterlocalnotifications.ForegroundService"
5355
android:exported="true"
5456
android:stopWithTask="false" />
57+
5558
</application>
59+
<queries>
60+
<intent>
61+
<action android:name="android.intent.action.INSERT" />
62+
<data android:mimeType="vnd.android.cursor.item/event" />
63+
</intent>
64+
</queries>
5665
</manifest>

ios/Runner.xcodeproj/project.pbxproj

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -378,16 +378,16 @@
378378
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
379379
CLANG_ENABLE_MODULES = YES;
380380
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
381-
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
382-
DEVELOPMENT_TEAM = 3PFDRPLY6N;
381+
CURRENT_PROJECT_VERSION = 74;
382+
DEVELOPMENT_TEAM = 2U3M8CAZBZ;
383383
ENABLE_BITCODE = NO;
384384
INFOPLIST_FILE = Runner/Info.plist;
385385
INFOPLIST_KEY_CFBundleDisplayName = MY.SOLVED;
386386
LD_RUNPATH_SEARCH_PATHS = (
387387
"$(inherited)",
388388
"@executable_path/Frameworks",
389389
);
390-
MARKETING_VERSION = 1.0.0;
390+
MARKETING_VERSION = 2.0.5;
391391
PRODUCT_BUNDLE_IDENTIFIER = com.jeehoukjung.mySolved;
392392
PRODUCT_NAME = "$(TARGET_NAME)";
393393
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
@@ -514,16 +514,16 @@
514514
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
515515
CLANG_ENABLE_MODULES = YES;
516516
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
517-
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
518-
DEVELOPMENT_TEAM = 3PFDRPLY6N;
517+
CURRENT_PROJECT_VERSION = 74;
518+
DEVELOPMENT_TEAM = 2U3M8CAZBZ;
519519
ENABLE_BITCODE = NO;
520520
INFOPLIST_FILE = Runner/Info.plist;
521521
INFOPLIST_KEY_CFBundleDisplayName = MY.SOLVED;
522522
LD_RUNPATH_SEARCH_PATHS = (
523523
"$(inherited)",
524524
"@executable_path/Frameworks",
525525
);
526-
MARKETING_VERSION = 1.0.0;
526+
MARKETING_VERSION = 2.0.5;
527527
PRODUCT_BUNDLE_IDENTIFIER = com.jeehoukjung.mySolved;
528528
PRODUCT_NAME = "$(TARGET_NAME)";
529529
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
@@ -544,16 +544,16 @@
544544
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
545545
CLANG_ENABLE_MODULES = YES;
546546
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
547-
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
548-
DEVELOPMENT_TEAM = 3PFDRPLY6N;
547+
CURRENT_PROJECT_VERSION = 74;
548+
DEVELOPMENT_TEAM = 2U3M8CAZBZ;
549549
ENABLE_BITCODE = NO;
550550
INFOPLIST_FILE = Runner/Info.plist;
551551
INFOPLIST_KEY_CFBundleDisplayName = MY.SOLVED;
552552
LD_RUNPATH_SEARCH_PATHS = (
553553
"$(inherited)",
554554
"@executable_path/Frameworks",
555555
);
556-
MARKETING_VERSION = 1.0.0;
556+
MARKETING_VERSION = 2.0.5;
557557
PRODUCT_BUNDLE_IDENTIFIER = com.jeehoukjung.mySolved;
558558
PRODUCT_NAME = "$(TARGET_NAME)";
559559
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";

ios/Runner/Info.plist

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,5 +110,8 @@
110110
<true/>
111111
<key>UIApplicationSupportsIndirectInputEvents</key>
112112
<true/>
113+
114+
<key>NSCalendarsUsageDescription</key>
115+
<string>adds contest to calendar</string>
113116
</dict>
114117
</plist>

lib/features/contest/bloc/contest_bloc.dart

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
1+
import 'package:add_2_calendar/add_2_calendar.dart';
12
import 'package:boj_api/boj_api.dart';
23
import 'package:contest_notification_repository/contest_notification_repository.dart';
34
import 'package:contest_repository/contest_repository.dart';
45
import 'package:equatable/equatable.dart';
6+
import 'package:flutter/material.dart';
57
import 'package:flutter_bloc/flutter_bloc.dart';
6-
import 'package:meta/meta.dart';
8+
import 'package:fluttertoast/fluttertoast.dart';
79
import 'package:my_solved/features/contest_filter/bloc/contest_filter_bloc.dart';
810
import 'package:permission_handler/permission_handler.dart';
911
import 'package:shared_preferences_repository/shared_preferences_repository.dart';
1012

13+
import '../../../components/styles/color.dart';
14+
1115
part 'contest_event.dart';
1216
part 'contest_state.dart';
1317

@@ -31,6 +35,7 @@ class ContestBloc extends Bloc<ContestEvent, ContestState> {
3135
on<ContestInit>(_onInit);
3236
on<ContestSegmentedControlPressed>(_onSegmentedControlPressed);
3337
on<ContestNotificationButtonPressed>(_onNotificationPressed);
38+
on<ContestCalendarButtonPressed>(_onCalendarPressed);
3439
on<ContestFilterTogglePressed>(_onFilterPressed);
3540
}
3641

@@ -48,9 +53,12 @@ class ContestBloc extends Bloc<ContestEvent, ContestState> {
4853
final upcomingContests = result[ContestType.upcoming];
4954

5055
List<bool> isOnContestNotifications = [];
56+
List<bool> isOnContestCalendars = [];
5157
for (Contest contest in upcomingContests ?? []) {
5258
isOnContestNotifications.add(await _sharedPreferencesRepository
5359
.getIsOnContestNotification(title: contest.name));
60+
isOnContestCalendars.add(await _sharedPreferencesRepository
61+
.getIsOnContestCalendar(title: contest.name));
5462
}
5563

5664
emit(state.copyWith(
@@ -59,6 +67,7 @@ class ContestBloc extends Bloc<ContestEvent, ContestState> {
5967
ongoingContests: ongoingContests,
6068
upcomingContests: upcomingContests,
6169
isOnNotificationUpcomingContests: isOnContestNotifications,
70+
isOnCalendarUpcomingContests: isOnContestCalendars,
6271
));
6372
} catch (e) {
6473
emit(state.copyWith(status: ContestStatus.failure));
@@ -90,6 +99,15 @@ class ContestBloc extends Bloc<ContestEvent, ContestState> {
9099
final minute =
91100
await _sharedPreferencesRepository.getContestNotificationMinute();
92101

102+
Fluttertoast.showToast(
103+
msg: isOn ? "알람이 취소되었습니다." : "알람이 설정되었습니다.",
104+
toastLength: Toast.LENGTH_SHORT,
105+
gravity: ToastGravity.CENTER,
106+
timeInSecForIosWeb: 1,
107+
backgroundColor: MySolvedColor.main.withOpacity(0.8),
108+
textColor: Colors.white,
109+
fontSize: 16.0);
110+
93111
if (isOn) {
94112
await _contestNotificationRepository.cancelContestNotification(
95113
title: contest.name,
@@ -121,6 +139,63 @@ class ContestBloc extends Bloc<ContestEvent, ContestState> {
121139
}
122140
}
123141

142+
void _onCalendarPressed(
143+
ContestCalendarButtonPressed event,
144+
Emitter<ContestState> emit,
145+
) async {
146+
emit(state.copyWith(status: ContestStatus.loading));
147+
148+
final contest = state.filteredUpcomingContests[event.index];
149+
List<bool> isOnCalendar = state.isOnCalendarUpcomingContests;
150+
final isOn = await _sharedPreferencesRepository.getIsOnContestCalendar(
151+
title: contest.name,
152+
);
153+
154+
if (isOn) {
155+
await _sharedPreferencesRepository.setIsOnContestCalendar(
156+
title: contest.name,
157+
isOn: false,
158+
);
159+
isOnCalendar[event.index] = false;
160+
emit(state.copyWith(
161+
status: ContestStatus.success,
162+
isOnCalendarUpcomingContests: isOnCalendar,
163+
));
164+
} else {
165+
Fluttertoast.showToast(
166+
msg: "일정을 등록합니다.",
167+
toastLength: Toast.LENGTH_SHORT,
168+
gravity: ToastGravity.CENTER,
169+
timeInSecForIosWeb: 1,
170+
backgroundColor: MySolvedColor.main.withOpacity(0.8),
171+
textColor: Colors.white,
172+
fontSize: 16.0);
173+
174+
final Event calendarEvent = Event(
175+
title: contest.name,
176+
description: contest.url,
177+
startDate: contest.startTime,
178+
endDate: contest.endTime,
179+
iosParams: IOSParams(
180+
url: contest.url,
181+
),
182+
);
183+
Add2Calendar.addEvent2Cal(calendarEvent);
184+
185+
await _sharedPreferencesRepository.setIsOnContestCalendar(
186+
title: contest.name,
187+
isOn: true,
188+
);
189+
isOnCalendar[event.index] = true;
190+
emit(state.copyWith(
191+
status: ContestStatus.success,
192+
isOnCalendarUpcomingContests: isOnCalendar,
193+
));
194+
}
195+
196+
emit(state.copyWith(status: ContestStatus.success));
197+
}
198+
124199
void _onSegmentedControlPressed(
125200
ContestSegmentedControlPressed event,
126201
Emitter<ContestState> emit,

lib/features/contest/bloc/contest_event.dart

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,12 @@ class ContestNotificationButtonPressed extends ContestEvent {
1717
ContestNotificationButtonPressed({required this.index});
1818
}
1919

20+
class ContestCalendarButtonPressed extends ContestEvent {
21+
final int index;
22+
23+
ContestCalendarButtonPressed({required this.index});
24+
}
25+
2026
class ContestFilterTogglePressed extends ContestEvent {
2127
final ContestVenue venue;
2228

lib/features/contest/bloc/contest_state.dart

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,11 @@ enum ContestStatus { initial, loading, success, failure }
44

55
extension ContestStatusX on ContestStatus {
66
bool get isInitial => this == ContestStatus.initial;
7+
78
bool get isLoading => this == ContestStatus.loading;
9+
810
bool get isSuccess => this == ContestStatus.success;
11+
912
bool get isFailure => this == ContestStatus.failure;
1013
}
1114

@@ -16,18 +19,22 @@ class ContestState extends Equatable {
1619
final List<Contest> ongoingContests;
1720
final List<Contest> upcomingContests;
1821
final List<bool> isOnNotificationUpcomingContests;
22+
final List<bool> isOnCalendarUpcomingContests;
1923
final Map<ContestVenue, bool> filters;
2024

2125
List<String> get selectedVenues => ContestVenue.allCases
2226
.where((venue) => filters[venue] ?? false)
2327
.map((venue) => venue.value)
2428
.toList();
29+
2530
List<Contest> get filteredEndedContests => endedContests
2631
.where((contest) => selectedVenues.contains(contest.venue))
2732
.toList();
33+
2834
List<Contest> get filteredOngoingContests => ongoingContests
2935
.where((contest) => selectedVenues.contains(contest.venue))
3036
.toList();
37+
3138
List<Contest> get filteredUpcomingContests => upcomingContests
3239
.where((contest) => selectedVenues.contains(contest.venue))
3340
.toList();
@@ -39,6 +46,7 @@ class ContestState extends Equatable {
3946
this.ongoingContests = const [],
4047
this.upcomingContests = const [],
4148
this.isOnNotificationUpcomingContests = const [],
49+
this.isOnCalendarUpcomingContests = const [],
4250
required this.filters,
4351
});
4452

@@ -49,6 +57,7 @@ class ContestState extends Equatable {
4957
List<Contest>? ongoingContests,
5058
List<Contest>? upcomingContests,
5159
List<bool>? isOnNotificationUpcomingContests,
60+
List<bool>? isOnCalendarUpcomingContests,
5261
Map<ContestVenue, bool>? filters,
5362
}) {
5463
return ContestState(
@@ -59,6 +68,8 @@ class ContestState extends Equatable {
5968
upcomingContests: upcomingContests ?? this.upcomingContests,
6069
isOnNotificationUpcomingContests: isOnNotificationUpcomingContests ??
6170
this.isOnNotificationUpcomingContests,
71+
isOnCalendarUpcomingContests:
72+
isOnCalendarUpcomingContests ?? this.isOnCalendarUpcomingContests,
6273
filters: filters ?? this.filters,
6374
);
6475
}
@@ -71,10 +82,11 @@ class ContestState extends Equatable {
7182
ongoingContests,
7283
upcomingContests,
7384
isOnNotificationUpcomingContests,
85+
isOnCalendarUpcomingContests,
7486
filters,
7587
selectedVenues,
7688
filteredEndedContests,
7789
filteredOngoingContests,
7890
filteredUpcomingContests,
7991
];
80-
}
92+
}

0 commit comments

Comments
 (0)