diff --git a/android/app/build.gradle b/android/app/build.gradle
index 3d41f167..15b6b3e4 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -52,7 +52,7 @@ android {
defaultConfig {
applicationId "com.w8385.my_solved"
- minSdkVersion 34
+ minSdkVersion flutter.minSdkVersion
targetSdkVersion 34
multiDexEnabled true
versionCode flutterVersionCode.toInteger()
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index d330d2b7..f275ee81 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -9,6 +9,8 @@
+
+
+
+
+
+
+
+
+
diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj
index 5a7e87d6..b630814b 100644
--- a/ios/Runner.xcodeproj/project.pbxproj
+++ b/ios/Runner.xcodeproj/project.pbxproj
@@ -378,8 +378,8 @@
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;
@@ -387,7 +387,7 @@
"$(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";
@@ -514,8 +514,8 @@
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;
@@ -523,7 +523,7 @@
"$(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";
@@ -544,8 +544,8 @@
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;
@@ -553,7 +553,7 @@
"$(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";
diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist
index 351c9ff4..882a0c13 100644
--- a/ios/Runner/Info.plist
+++ b/ios/Runner/Info.plist
@@ -110,5 +110,8 @@
UIApplicationSupportsIndirectInputEvents
+
+ NSCalendarsUsageDescription
+ adds contest to calendar
diff --git a/lib/features/contest/bloc/contest_bloc.dart b/lib/features/contest/bloc/contest_bloc.dart
index 0f85eae9..5e581c14 100644
--- a/lib/features/contest/bloc/contest_bloc.dart
+++ b/lib/features/contest/bloc/contest_bloc.dart
@@ -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';
@@ -31,6 +35,7 @@ class ContestBloc extends Bloc {
on(_onInit);
on(_onSegmentedControlPressed);
on(_onNotificationPressed);
+ on(_onCalendarPressed);
on(_onFilterPressed);
}
@@ -48,9 +53,12 @@ class ContestBloc extends Bloc {
final upcomingContests = result[ContestType.upcoming];
List isOnContestNotifications = [];
+ List 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(
@@ -59,6 +67,7 @@ class ContestBloc extends Bloc {
ongoingContests: ongoingContests,
upcomingContests: upcomingContests,
isOnNotificationUpcomingContests: isOnContestNotifications,
+ isOnCalendarUpcomingContests: isOnContestCalendars,
));
} catch (e) {
emit(state.copyWith(status: ContestStatus.failure));
@@ -90,6 +99,15 @@ class ContestBloc extends Bloc {
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,
@@ -121,6 +139,63 @@ class ContestBloc extends Bloc {
}
}
+ void _onCalendarPressed(
+ ContestCalendarButtonPressed event,
+ Emitter emit,
+ ) async {
+ emit(state.copyWith(status: ContestStatus.loading));
+
+ final contest = state.filteredUpcomingContests[event.index];
+ List 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 emit,
diff --git a/lib/features/contest/bloc/contest_event.dart b/lib/features/contest/bloc/contest_event.dart
index c78cb89b..3e1f3d34 100644
--- a/lib/features/contest/bloc/contest_event.dart
+++ b/lib/features/contest/bloc/contest_event.dart
@@ -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;
diff --git a/lib/features/contest/bloc/contest_state.dart b/lib/features/contest/bloc/contest_state.dart
index 7f16b454..4eaa1281 100644
--- a/lib/features/contest/bloc/contest_state.dart
+++ b/lib/features/contest/bloc/contest_state.dart
@@ -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;
}
@@ -16,18 +19,22 @@ class ContestState extends Equatable {
final List ongoingContests;
final List upcomingContests;
final List isOnNotificationUpcomingContests;
+ final List isOnCalendarUpcomingContests;
final Map filters;
List get selectedVenues => ContestVenue.allCases
.where((venue) => filters[venue] ?? false)
.map((venue) => venue.value)
.toList();
+
List get filteredEndedContests => endedContests
.where((contest) => selectedVenues.contains(contest.venue))
.toList();
+
List get filteredOngoingContests => ongoingContests
.where((contest) => selectedVenues.contains(contest.venue))
.toList();
+
List get filteredUpcomingContests => upcomingContests
.where((contest) => selectedVenues.contains(contest.venue))
.toList();
@@ -39,6 +46,7 @@ class ContestState extends Equatable {
this.ongoingContests = const [],
this.upcomingContests = const [],
this.isOnNotificationUpcomingContests = const [],
+ this.isOnCalendarUpcomingContests = const [],
required this.filters,
});
@@ -49,6 +57,7 @@ class ContestState extends Equatable {
List? ongoingContests,
List? upcomingContests,
List? isOnNotificationUpcomingContests,
+ List? isOnCalendarUpcomingContests,
Map? filters,
}) {
return ContestState(
@@ -59,6 +68,8 @@ class ContestState extends Equatable {
upcomingContests: upcomingContests ?? this.upcomingContests,
isOnNotificationUpcomingContests: isOnNotificationUpcomingContests ??
this.isOnNotificationUpcomingContests,
+ isOnCalendarUpcomingContests:
+ isOnCalendarUpcomingContests ?? this.isOnCalendarUpcomingContests,
filters: filters ?? this.filters,
);
}
@@ -71,10 +82,11 @@ class ContestState extends Equatable {
ongoingContests,
upcomingContests,
isOnNotificationUpcomingContests,
+ isOnCalendarUpcomingContests,
filters,
selectedVenues,
filteredEndedContests,
filteredOngoingContests,
filteredUpcomingContests,
];
-}
\ No newline at end of file
+}
diff --git a/lib/features/contest/screen/contest_screen.dart b/lib/features/contest/screen/contest_screen.dart
index 6ca1024b..908b5acf 100644
--- a/lib/features/contest/screen/contest_screen.dart
+++ b/lib/features/contest/screen/contest_screen.dart
@@ -2,7 +2,7 @@ import 'package:boj_api/boj_api.dart';
import 'package:extended_image/extended_image.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
-import 'package:my_solved/components/atoms/button/button.dart';
+import 'package:fluttertoast/fluttertoast.dart';
import 'package:my_solved/components/molecules/segmented_control/segmented_control.dart';
import 'package:my_solved/components/styles/color.dart';
import 'package:my_solved/components/styles/font.dart';
@@ -124,12 +124,7 @@ class _ContestViewState extends State {
(index) => Column(
children: [
ElevatedButton(
- onPressed: () async {
- final urlString = contests[index].url;
- if (urlString != null) {
- launchUrlString(urlString);
- }
- },
+ onPressed: () {},
style: ElevatedButton.styleFrom(
minimumSize: Size.fromHeight(128),
padding: EdgeInsets.all(16),
@@ -157,78 +152,129 @@ class _ContestViewState extends State {
color: MySolvedColor.secondaryFont,
),
),
- SizedBox(height: 8),
- Row(
- children: [
- if (state.currentIndex == 1)
- MySolvedFitButton(
- onPressed: () => context
- .read()
- .add(ContestNotificationButtonPressed(
- index: index,
+ Container(
+ margin: EdgeInsets.only(top: 8),
+ height: 50,
+ child: Row(
+ children: [
+ if (state.currentIndex == 1)
+ IconButton(
+ onPressed: () => context
+ .read()
+ .add(
+ ContestNotificationButtonPressed(
+ index: index,
+ )),
+ style: ButtonStyle(
+ backgroundColor: state
+ .isOnNotificationUpcomingContests[
+ index]
+ ? MaterialStateProperty.all(
+ MySolvedColor
+ .secondaryButtonBackground)
+ : MaterialStateProperty.all(
+ MySolvedColor.main),
+ ),
+ icon: Icon(
+ state.isOnNotificationUpcomingContests[
+ index]
+ ? Icons.alarm_off
+ : Icons.alarm,
+ color:
+ state.isOnNotificationUpcomingContests[
+ index]
+ ? MySolvedColor.secondaryFont
+ : MySolvedColor.background,
+ )),
+ if (state.currentIndex == 1)
+ IconButton(
+ onPressed: () {
+ context.read().add(
+ ContestCalendarButtonPressed(
+ index: index));
+ },
+ style: ButtonStyle(
+ backgroundColor: state
+ .isOnCalendarUpcomingContests[
+ index]
+ ? MaterialStateProperty.all(
+ MySolvedColor
+ .secondaryButtonBackground)
+ : MaterialStateProperty.all(
+ MySolvedColor.main),
+ ),
+ icon: Icon(
+ Icons.calendar_month,
+ color:
+ state.isOnCalendarUpcomingContests[
+ index]
+ ? MySolvedColor.secondaryFont
+ : MySolvedColor.background,
+ size: 20,
)),
- buttonStyle:
- state.isOnNotificationUpcomingContests[
- index]
- ? MySolvedButtonStyle.secondary
- : MySolvedButtonStyle.primary,
- text:
- state.isOnNotificationUpcomingContests[
- index]
- ? "알림 취소하기"
- : "알림 설정하기",
- ),
- Spacer(),
- if (contests[index].badge != null)
- ElevatedButton(
- onPressed: () {},
- style: ElevatedButton.styleFrom(
- elevation: 0,
- minimumSize: Size.zero,
- padding: EdgeInsets.symmetric(
- vertical: 8, horizontal: 12),
- foregroundColor: Color(0xFFfab005),
+ Spacer(),
+ if (contests[index].badge != null)
+ IconButton(
+ onPressed: () => Fluttertoast.showToast(
+ msg:
+ "${contests[index].badge!}시 뱃지 획득",
+ toastLength: Toast.LENGTH_SHORT,
+ gravity: ToastGravity.CENTER,
+ timeInSecForIosWeb: 1,
+ backgroundColor: MySolvedColor.main
+ .withOpacity(0.8),
+ textColor: Colors.white,
+ fontSize: 16.0),
+ style: IconButton.styleFrom(
+ foregroundColor: Color(0xFFfab005),
+ backgroundColor: MySolvedColor
+ .secondaryBackground),
+ icon: Icon(Icons.badge),
),
- child: Tooltip(
- triggerMode: TooltipTriggerMode.tap,
- message: contests[index].badge,
- child: Text(
- '🏅',
- style: MySolvedTextStyle.caption1,
+ if (contests[index].background != null)
+ IconButton(
+ onPressed: () => Fluttertoast.showToast(
+ msg:
+ "${contests[index].background!}시 배경 획득",
+ toastLength: Toast.LENGTH_SHORT,
+ gravity: ToastGravity.CENTER,
+ timeInSecForIosWeb: 1,
+ backgroundColor: MySolvedColor.main
+ .withOpacity(0.8),
+ textColor: Colors.white,
+ fontSize: 16.0),
+ style: IconButton.styleFrom(
+ foregroundColor: Color(0xFFb197fc),
+ backgroundColor:
+ MySolvedColor.secondaryBackground,
),
+ icon: Icon(Icons.image),
),
- ),
- if (contests[index].background != null)
- ElevatedButton(
- onPressed: () {},
- style: ElevatedButton.styleFrom(
- elevation: 0,
- minimumSize: Size.zero,
- padding: EdgeInsets.symmetric(
- vertical: 8, horizontal: 12),
- foregroundColor: Color(0xFFb197fc),
+ IconButton(
+ onPressed: () async {
+ final urlString = contests[index].url;
+ if (urlString != null) {
+ launchUrlString(urlString);
+ }
+ },
+ style: IconButton.styleFrom(
+ backgroundColor:
+ MySolvedColor.secondaryBackground,
),
- child: Tooltip(
- triggerMode: TooltipTriggerMode.tap,
- message: contests[index].background,
- child: Text(
- '🖼️',
- style: MySolvedTextStyle.caption1,
- ),
+ icon: ExtendedImage.asset(
+ "assets/images/venues/${contests[index].venue?.toLowerCase() ?? "etc"}.png",
+ width: 24,
),
- ),
- ExtendedImage.asset(
- "assets/images/venues/${contests[index].venue?.toLowerCase() ?? "etc"}.png",
- height: 30,
- width: 30,
- ),
- ],
+ )
+ ],
+ ),
),
- if (state.currentIndex == 0) SizedBox(height: 8),
if (state.currentIndex == 0)
- ProgressIndicator(
- contests[index].startTime.toLocal(),
- contests[index].endTime.toLocal()),
+ Container(
+ margin: EdgeInsets.only(top: 8),
+ child: ProgressIndicator(
+ contests[index].startTime.toLocal(),
+ contests[index].endTime.toLocal())),
],
),
),
diff --git a/packages/repositories/shared_preferences_repository/lib/src/shared_preferences_repository.dart b/packages/repositories/shared_preferences_repository/lib/src/shared_preferences_repository.dart
index acaeab9a..d95e0d39 100644
--- a/packages/repositories/shared_preferences_repository/lib/src/shared_preferences_repository.dart
+++ b/packages/repositories/shared_preferences_repository/lib/src/shared_preferences_repository.dart
@@ -107,4 +107,15 @@ class SharedPreferencesRepository {
}) async {
await _sharedPreferencesApiClient.setBool(key: title, value: isOn);
}
-}
\ No newline at end of file
+
+ Future getIsOnContestCalendar({required String title}) async {
+ return await _sharedPreferencesApiClient.getBool(key: title) ?? false;
+ }
+
+ Future setIsOnContestCalendar({
+ required String title,
+ required bool isOn,
+ }) async {
+ await _sharedPreferencesApiClient.setBool(key: title, value: isOn);
+ }
+}
diff --git a/pubspec.yaml b/pubspec.yaml
index c98f6582..430c8915 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -3,7 +3,7 @@ description: For solving problems in the world of programming; base on solved.ac
publish_to: "none" # Remove this line if you wish to publish to pub.dev
-version: 2.0.5+74
+version: 2.1.0+78
environment:
sdk: ">=2.18.2 <3.0.0"
@@ -16,7 +16,6 @@ dependencies:
equatable: ^2.0.5
extended_image: ^8.1.0
fluttertoast: ^8.2.8
- flutter_app_badger: ^1.5.0
flutter_bloc: ^8.1.3
flutter_local_notifications: ^13.0.0
flutter_native_timezone: ^2.0.0
@@ -34,6 +33,7 @@ dependencies:
timezone: ^0.9.1
url_launcher: ^6.1.10
permission_handler: ^11.3.1
+ add_2_calendar: ^3.0.1
boj_api:
path: packages/apis/boj_api
@@ -61,6 +61,7 @@ dev_dependencies:
sdk: flutter
flutter_lints: ^3.0.1
+ build_web_compilers: ^4.0.4
flutter:
uses-material-design: true
diff --git a/web/favicon.png b/web/favicon.png
index 8aaa46ac..8796d2b1 100644
Binary files a/web/favicon.png and b/web/favicon.png differ
diff --git a/web/icons/Icon-192.png b/web/icons/Icon-192.png
index b749bfef..8ddc28c4 100644
Binary files a/web/icons/Icon-192.png and b/web/icons/Icon-192.png differ
diff --git a/web/icons/Icon-512.png b/web/icons/Icon-512.png
old mode 100644
new mode 100755
index 88cfd48d..9ffba617
Binary files a/web/icons/Icon-512.png and b/web/icons/Icon-512.png differ
diff --git a/web/icons/Icon-maskable-192.png b/web/icons/Icon-maskable-192.png
index eb9b4d76..8ddc28c4 100644
Binary files a/web/icons/Icon-maskable-192.png and b/web/icons/Icon-maskable-192.png differ
diff --git a/web/icons/Icon-maskable-512.png b/web/icons/Icon-maskable-512.png
old mode 100644
new mode 100755
index d69c5669..9ffba617
Binary files a/web/icons/Icon-maskable-512.png and b/web/icons/Icon-maskable-512.png differ
diff --git a/web/index.html b/web/index.html
index 4582a5a3..4aece673 100644
--- a/web/index.html
+++ b/web/index.html
@@ -1,46 +1,62 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- my_solved
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ my_solved
+
+
+
+
+
-
+
diff --git a/web/manifest.json b/web/manifest.json
index 8187dff0..9bdbc720 100644
--- a/web/manifest.json
+++ b/web/manifest.json
@@ -1,35 +1,35 @@
{
- "name": "my_solved",
- "short_name": "my_solved",
- "start_url": ".",
- "display": "standalone",
- "background_color": "#0175C2",
- "theme_color": "#0175C2",
- "description": "A new Flutter project.",
- "orientation": "portrait-primary",
- "prefer_related_applications": false,
- "icons": [
- {
- "src": "icons/Icon-192.png",
- "sizes": "192x192",
- "type": "image/png"
- },
- {
- "src": "icons/Icon-512.png",
- "sizes": "512x512",
- "type": "image/png"
- },
- {
- "src": "icons/Icon-maskable-192.png",
- "sizes": "192x192",
- "type": "image/png",
- "purpose": "maskable"
- },
- {
- "src": "icons/Icon-maskable-512.png",
- "sizes": "512x512",
- "type": "image/png",
- "purpose": "maskable"
- }
- ]
+ "name": "my_solved",
+ "short_name": "my_solved",
+ "start_url": ".",
+ "display": "standalone",
+ "background_color": "#0175C2",
+ "theme_color": "#0175C2",
+ "description": "보다 편한 PS/CP를 위한 애플리케이션",
+ "orientation": "portrait-primary",
+ "prefer_related_applications": false,
+ "icons": [
+ {
+ "src": "icons/Icon-192.png",
+ "sizes": "192x192",
+ "type": "image/png"
+ },
+ {
+ "src": "icons/Icon-512.png",
+ "sizes": "512x512",
+ "type": "image/png"
+ },
+ {
+ "src": "icons/Icon-maskable-192.png",
+ "sizes": "192x192",
+ "type": "image/png",
+ "purpose": "maskable"
+ },
+ {
+ "src": "icons/Icon-maskable-512.png",
+ "sizes": "512x512",
+ "type": "image/png",
+ "purpose": "maskable"
+ }
+ ]
}