Skip to content

Commit

Permalink
feat: add_contest_2_calendar (#63)
Browse files Browse the repository at this point in the history
* 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 삭제
  • Loading branch information
w8385 authored Oct 10, 2024
1 parent 1468ce1 commit 03dec90
Show file tree
Hide file tree
Showing 17 changed files with 337 additions and 158 deletions.
2 changes: 1 addition & 1 deletion android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ android {

defaultConfig {
applicationId "com.w8385.my_solved"
minSdkVersion 34
minSdkVersion flutter.minSdkVersion
targetSdkVersion 34
multiDexEnabled true
versionCode flutterVersionCode.toInteger()
Expand Down
9 changes: 9 additions & 0 deletions android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
<uses-permission android:name="android.permission.USE_EXACT_ALARM" />
<uses-permission android:name="android.permission.WRITE_CALENDAR" />
<uses-permission android:name="android.permission.READ_CALENDAR" />

<application
android:name="${applicationName}"
Expand Down Expand Up @@ -52,5 +54,12 @@
android:name="com.dexterous.flutterlocalnotifications.ForegroundService"
android:exported="true"
android:stopWithTask="false" />

</application>
<queries>
<intent>
<action android:name="android.intent.action.INSERT" />
<data android:mimeType="vnd.android.cursor.item/event" />
</intent>
</queries>
</manifest>
18 changes: 9 additions & 9 deletions ios/Runner.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -378,16 +378,16 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = 3PFDRPLY6N;
CURRENT_PROJECT_VERSION = 74;
DEVELOPMENT_TEAM = 2U3M8CAZBZ;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = MY.SOLVED;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0.0;
MARKETING_VERSION = 2.0.5;
PRODUCT_BUNDLE_IDENTIFIER = com.jeehoukjung.mySolved;
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
Expand Down Expand Up @@ -514,16 +514,16 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = 3PFDRPLY6N;
CURRENT_PROJECT_VERSION = 74;
DEVELOPMENT_TEAM = 2U3M8CAZBZ;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = MY.SOLVED;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0.0;
MARKETING_VERSION = 2.0.5;
PRODUCT_BUNDLE_IDENTIFIER = com.jeehoukjung.mySolved;
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
Expand All @@ -544,16 +544,16 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = 3PFDRPLY6N;
CURRENT_PROJECT_VERSION = 74;
DEVELOPMENT_TEAM = 2U3M8CAZBZ;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = MY.SOLVED;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0.0;
MARKETING_VERSION = 2.0.5;
PRODUCT_BUNDLE_IDENTIFIER = com.jeehoukjung.mySolved;
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
Expand Down
3 changes: 3 additions & 0 deletions ios/Runner/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -110,5 +110,8 @@
<true/>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>

<key>NSCalendarsUsageDescription</key>
<string>adds contest to calendar</string>
</dict>
</plist>
77 changes: 76 additions & 1 deletion lib/features/contest/bloc/contest_bloc.dart
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
import 'package:add_2_calendar/add_2_calendar.dart';
import 'package:boj_api/boj_api.dart';
import 'package:contest_notification_repository/contest_notification_repository.dart';
import 'package:contest_repository/contest_repository.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:meta/meta.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:my_solved/features/contest_filter/bloc/contest_filter_bloc.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:shared_preferences_repository/shared_preferences_repository.dart';

import '../../../components/styles/color.dart';

part 'contest_event.dart';
part 'contest_state.dart';

Expand All @@ -31,6 +35,7 @@ class ContestBloc extends Bloc<ContestEvent, ContestState> {
on<ContestInit>(_onInit);
on<ContestSegmentedControlPressed>(_onSegmentedControlPressed);
on<ContestNotificationButtonPressed>(_onNotificationPressed);
on<ContestCalendarButtonPressed>(_onCalendarPressed);
on<ContestFilterTogglePressed>(_onFilterPressed);
}

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

List<bool> isOnContestNotifications = [];
List<bool> isOnContestCalendars = [];
for (Contest contest in upcomingContests ?? []) {
isOnContestNotifications.add(await _sharedPreferencesRepository
.getIsOnContestNotification(title: contest.name));
isOnContestCalendars.add(await _sharedPreferencesRepository
.getIsOnContestCalendar(title: contest.name));
}

emit(state.copyWith(
Expand All @@ -59,6 +67,7 @@ class ContestBloc extends Bloc<ContestEvent, ContestState> {
ongoingContests: ongoingContests,
upcomingContests: upcomingContests,
isOnNotificationUpcomingContests: isOnContestNotifications,
isOnCalendarUpcomingContests: isOnContestCalendars,
));
} catch (e) {
emit(state.copyWith(status: ContestStatus.failure));
Expand Down Expand Up @@ -90,6 +99,15 @@ class ContestBloc extends Bloc<ContestEvent, ContestState> {
final minute =
await _sharedPreferencesRepository.getContestNotificationMinute();

Fluttertoast.showToast(
msg: isOn ? "알람이 취소되었습니다." : "알람이 설정되었습니다.",
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.CENTER,
timeInSecForIosWeb: 1,
backgroundColor: MySolvedColor.main.withOpacity(0.8),
textColor: Colors.white,
fontSize: 16.0);

if (isOn) {
await _contestNotificationRepository.cancelContestNotification(
title: contest.name,
Expand Down Expand Up @@ -121,6 +139,63 @@ class ContestBloc extends Bloc<ContestEvent, ContestState> {
}
}

void _onCalendarPressed(
ContestCalendarButtonPressed event,
Emitter<ContestState> emit,
) async {
emit(state.copyWith(status: ContestStatus.loading));

final contest = state.filteredUpcomingContests[event.index];
List<bool> isOnCalendar = state.isOnCalendarUpcomingContests;
final isOn = await _sharedPreferencesRepository.getIsOnContestCalendar(
title: contest.name,
);

if (isOn) {
await _sharedPreferencesRepository.setIsOnContestCalendar(
title: contest.name,
isOn: false,
);
isOnCalendar[event.index] = false;
emit(state.copyWith(
status: ContestStatus.success,
isOnCalendarUpcomingContests: isOnCalendar,
));
} else {
Fluttertoast.showToast(
msg: "일정을 등록합니다.",
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.CENTER,
timeInSecForIosWeb: 1,
backgroundColor: MySolvedColor.main.withOpacity(0.8),
textColor: Colors.white,
fontSize: 16.0);

final Event calendarEvent = Event(
title: contest.name,
description: contest.url,
startDate: contest.startTime,
endDate: contest.endTime,
iosParams: IOSParams(
url: contest.url,
),
);
Add2Calendar.addEvent2Cal(calendarEvent);

await _sharedPreferencesRepository.setIsOnContestCalendar(
title: contest.name,
isOn: true,
);
isOnCalendar[event.index] = true;
emit(state.copyWith(
status: ContestStatus.success,
isOnCalendarUpcomingContests: isOnCalendar,
));
}

emit(state.copyWith(status: ContestStatus.success));
}

void _onSegmentedControlPressed(
ContestSegmentedControlPressed event,
Emitter<ContestState> emit,
Expand Down
6 changes: 6 additions & 0 deletions lib/features/contest/bloc/contest_event.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ class ContestNotificationButtonPressed extends ContestEvent {
ContestNotificationButtonPressed({required this.index});
}

class ContestCalendarButtonPressed extends ContestEvent {
final int index;

ContestCalendarButtonPressed({required this.index});
}

class ContestFilterTogglePressed extends ContestEvent {
final ContestVenue venue;

Expand Down
14 changes: 13 additions & 1 deletion lib/features/contest/bloc/contest_state.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@ enum ContestStatus { initial, loading, success, failure }

extension ContestStatusX on ContestStatus {
bool get isInitial => this == ContestStatus.initial;

bool get isLoading => this == ContestStatus.loading;

bool get isSuccess => this == ContestStatus.success;

bool get isFailure => this == ContestStatus.failure;
}

Expand All @@ -16,18 +19,22 @@ class ContestState extends Equatable {
final List<Contest> ongoingContests;
final List<Contest> upcomingContests;
final List<bool> isOnNotificationUpcomingContests;
final List<bool> isOnCalendarUpcomingContests;
final Map<ContestVenue, bool> filters;

List<String> get selectedVenues => ContestVenue.allCases
.where((venue) => filters[venue] ?? false)
.map((venue) => venue.value)
.toList();

List<Contest> get filteredEndedContests => endedContests
.where((contest) => selectedVenues.contains(contest.venue))
.toList();

List<Contest> get filteredOngoingContests => ongoingContests
.where((contest) => selectedVenues.contains(contest.venue))
.toList();

List<Contest> get filteredUpcomingContests => upcomingContests
.where((contest) => selectedVenues.contains(contest.venue))
.toList();
Expand All @@ -39,6 +46,7 @@ class ContestState extends Equatable {
this.ongoingContests = const [],
this.upcomingContests = const [],
this.isOnNotificationUpcomingContests = const [],
this.isOnCalendarUpcomingContests = const [],
required this.filters,
});

Expand All @@ -49,6 +57,7 @@ class ContestState extends Equatable {
List<Contest>? ongoingContests,
List<Contest>? upcomingContests,
List<bool>? isOnNotificationUpcomingContests,
List<bool>? isOnCalendarUpcomingContests,
Map<ContestVenue, bool>? filters,
}) {
return ContestState(
Expand All @@ -59,6 +68,8 @@ class ContestState extends Equatable {
upcomingContests: upcomingContests ?? this.upcomingContests,
isOnNotificationUpcomingContests: isOnNotificationUpcomingContests ??
this.isOnNotificationUpcomingContests,
isOnCalendarUpcomingContests:
isOnCalendarUpcomingContests ?? this.isOnCalendarUpcomingContests,
filters: filters ?? this.filters,
);
}
Expand All @@ -71,10 +82,11 @@ class ContestState extends Equatable {
ongoingContests,
upcomingContests,
isOnNotificationUpcomingContests,
isOnCalendarUpcomingContests,
filters,
selectedVenues,
filteredEndedContests,
filteredOngoingContests,
filteredUpcomingContests,
];
}
}
Loading

0 comments on commit 03dec90

Please sign in to comment.