├── .editorconfig
├── .github
├── PULL_REQUEST_TEMPLATE
│ ├── basic_template.md
│ └── release_template.md
├── actions
│ ├── get-app-version
│ │ └── action.yml
│ ├── setup-development-environment
│ │ └── action.yml
│ ├── setup-key-environment
│ │ └── action.yml
│ └── update-app-version
│ │ └── action.yml
├── auto_assign_config.yml
└── workflows
│ ├── deploy-new-version-play-store.yml
│ ├── deploy_firebase-distribution_debug.yml
│ ├── deploy_firebase-distribution_release.yml
│ ├── pr_auto_assign.yml
│ ├── pr_template.yml
│ ├── verify_debug_build_test.yml
│ └── verify_release_build_test.yml
├── .gitignore
├── README.md
├── app-version.json
├── app
├── .gitignore
├── build.gradle.kts
├── proguard-rules.pro
└── src
│ ├── debug
│ └── res
│ │ ├── drawable
│ │ └── ic_launcher_foreground.xml
│ │ ├── mipmap-anydpi-v26
│ │ ├── ic_launcher.xml
│ │ └── ic_launcher_round.xml
│ │ ├── mipmap-hdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-mdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xhdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xxhdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xxxhdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ └── values
│ │ ├── ic_launcher_background.xml
│ │ └── strings.xml
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── pomonyang
│ │ └── mohanyang
│ │ ├── MainActivity.kt
│ │ ├── MainElements.kt
│ │ ├── MainViewModel.kt
│ │ ├── MohaNyangApplication.kt
│ │ ├── di
│ │ ├── ManagerModule.kt
│ │ └── ServiceModule.kt
│ │ ├── navigation
│ │ └── MohaNyangNavHost.kt
│ │ ├── notification
│ │ ├── FocusNotificationService.kt
│ │ ├── LocalNotificationReceiver.kt
│ │ ├── MnAlarmManager.kt
│ │ ├── MnFirebaseMessagingService.kt
│ │ └── util
│ │ │ └── NotificationUtils.kt
│ │ ├── ui
│ │ ├── BottomNavItem.kt
│ │ ├── MohaNyangApp.kt
│ │ ├── MohaNyangAppState.kt
│ │ ├── ServiceHelper.kt
│ │ └── component
│ │ │ └── MohaNyangBottomBar.kt
│ │ └── util
│ │ └── DebugTimberTree.kt
│ └── res
│ ├── drawable
│ ├── app_splash.xml
│ └── ic_app_notification.xml
│ ├── mipmap-anydpi-v26
│ └── ic_launcher.xml
│ ├── mipmap-hdpi
│ └── ic_launcher_foreground.webp
│ ├── mipmap-mdpi
│ └── ic_launcher_foreground.webp
│ ├── mipmap-xhdpi
│ └── ic_launcher_foreground.webp
│ ├── mipmap-xxhdpi
│ └── ic_launcher_foreground.webp
│ ├── mipmap-xxxhdpi
│ └── ic_launcher_foreground.webp
│ ├── values
│ ├── colors.xml
│ ├── ic_launcher_background.xml
│ ├── strings.xml
│ └── themes.xml
│ └── xml
│ ├── backup_rules.xml
│ └── data_extraction_rules.xml
├── build-logic
├── .gitignore
├── build.gradle.kts
├── settings.gradle.kts
└── src
│ └── main
│ └── java
│ └── com
│ └── pomonyang
│ └── mohanyang
│ ├── AndroidApplicationComposeConventionPlugin.kt
│ ├── AndroidApplicationConventionPlugin.kt
│ ├── AndroidApplicationFirebaseConventionPlugin.kt
│ ├── AndroidDatadogConventionPlugin.kt
│ ├── AndroidHiltConventionPlugin.kt
│ ├── AndroidLibraryComposeConventionPlugin.kt
│ ├── AndroidLibraryConventionPlugin.kt
│ ├── AppVersionPlugin.kt
│ └── convention
│ ├── AndroidCompose.kt
│ ├── DependencyHandlerExtensions.kt
│ ├── GithubUtils.kt
│ ├── KotlinAndroid.kt
│ ├── ProjectConfigurations.kt
│ ├── ProjectExtensions.kt
│ └── Secrets.kt
├── build.gradle.kts
├── data
├── .gitignore
├── build.gradle.kts
├── consumer-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ └── java
│ └── com
│ └── pomonyang
│ └── mohanyang
│ └── data
│ ├── local
│ ├── datastore
│ │ ├── datasource
│ │ │ ├── deviceid
│ │ │ │ ├── DeviceIdLocalDataSource.kt
│ │ │ │ └── DeviceIdLocalDataSourceImpl.kt
│ │ │ ├── notification
│ │ │ │ ├── NotificationLocalDataSource.kt
│ │ │ │ └── NotificationLocalDataSourceImpl.kt
│ │ │ ├── token
│ │ │ │ ├── TokenLocalDataSource.kt
│ │ │ │ └── TokenLocalDataSourceImpl.kt
│ │ │ └── user
│ │ │ │ ├── UserLocalDataSource.kt
│ │ │ │ └── UserLocalDataSourceImpl.kt
│ │ ├── di
│ │ │ ├── DataStoreModule.kt
│ │ │ ├── DataStoreQualifier.kt
│ │ │ └── LocalDataSourceModule.kt
│ │ ├── model
│ │ │ └── .gitkeep
│ │ └── util
│ │ │ └── .gitkeep
│ ├── device
│ │ ├── receiver
│ │ │ └── LockScreenBroadcastReceiver.kt
│ │ └── util
│ │ │ └── LockScreenUtils.kt
│ └── room
│ │ ├── dao
│ │ ├── PomodoroSettingDao.kt
│ │ └── PomodoroTimerDao.kt
│ │ ├── database
│ │ ├── PomodoroSettingDataBase.kt
│ │ └── PomodoroTimerDataBase.kt
│ │ ├── di
│ │ └── RoomModule.kt
│ │ ├── enitity
│ │ ├── PomodoroSettingEntity.kt
│ │ └── PomodoroTimerEntity.kt
│ │ └── util
│ │ └── TimeUtils.kt
│ ├── remote
│ ├── datasource
│ │ ├── auth
│ │ │ ├── AuthRemoteDataSource.kt
│ │ │ └── AuthRemoteDataSourceImpl.kt
│ │ └── pomodoro
│ │ │ ├── PomodoroSettingRemoteDataSource.kt
│ │ │ └── PomodoroSettingRemoteDataSourceImpl.kt
│ ├── di
│ │ ├── ClientQualifier.kt
│ │ ├── NetworkModule.kt
│ │ └── RemoteDataSourceModule.kt
│ ├── interceptor
│ │ ├── HttpRequestInterceptor.kt
│ │ └── TokenRefreshInterceptor.kt
│ ├── model
│ │ ├── request
│ │ │ ├── AddCategoryRequest.kt
│ │ │ ├── DeleteCategoryRequest.kt
│ │ │ ├── PomodoroTimerRequest.kt
│ │ │ ├── RefreshTokenRequest.kt
│ │ │ ├── RegisterPushTokenRequest.kt
│ │ │ ├── TokenRequest.kt
│ │ │ ├── UpdateCatInfoRequest.kt
│ │ │ ├── UpdateCatTypeRequest.kt
│ │ │ └── UpdateCategoryInfoRequest.kt
│ │ └── response
│ │ │ ├── CatTypeResponse.kt
│ │ │ ├── ErrorResponse.kt
│ │ │ ├── PomodoroSettingResponse.kt
│ │ │ ├── StatisticsResponse.kt
│ │ │ ├── TokenResponse.kt
│ │ │ └── UserInfoResponse.kt
│ ├── service
│ │ ├── AuthService.kt
│ │ └── MohaNyangService.kt
│ └── util
│ │ ├── NetworkException.kt
│ │ ├── NetworkMonitor.kt
│ │ ├── NetworkResultCall.kt
│ │ ├── NetworkResultCallAdapter.kt
│ │ └── NetworkResultCallAdapterFactory.kt
│ └── repository
│ ├── cat
│ ├── CatSettingRepository.kt
│ └── CatSettingRepositoryImpl.kt
│ ├── di
│ └── RepositoryModule.kt
│ ├── pomodoro
│ ├── PomodoroSettingRepository.kt
│ ├── PomodoroSettingRepositoryImpl.kt
│ ├── PomodoroTimerRepository.kt
│ └── PomodoroTimerRepositoryImpl.kt
│ ├── push
│ ├── PushAlarmRepository.kt
│ └── PushAlarmRepositoryImpl.kt
│ ├── statistics
│ ├── StatisticsRepository.kt
│ └── StatisticsRepositoryImpl.kt
│ ├── user
│ ├── UserRepository.kt
│ └── UserRepositoryImpl.kt
│ └── util
│ └── TimerUtil.kt
├── domain
├── .gitignore
├── build.gradle.kts
├── consumer-rules.pro
└── src
│ └── main
│ └── java
│ └── com
│ └── pomonyang
│ └── mohanyang
│ └── domain
│ └── usecase
│ ├── AdjustPomodoroTimeUseCase.kt
│ ├── GetSelectedPomodoroSettingUseCase.kt
│ ├── GetTokenByDeviceIdUseCase.kt
│ └── InsertPomodoroInitialDataUseCase.kt
├── gradle.properties
├── gradle
├── libs.versions.toml
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── presentation
├── .gitignore
├── build.gradle.kts
├── consumer-rules.pro
└── src
│ └── main
│ ├── java
│ └── com
│ │ └── pomonyang
│ │ └── mohanyang
│ │ └── presentation
│ │ ├── base
│ │ ├── BaseViewElements.kt
│ │ └── BaseViewModel.kt
│ │ ├── component
│ │ ├── CatRive.kt
│ │ ├── CategoryBox.kt
│ │ ├── FocusTimerSelecterButtons.kt
│ │ ├── Timer.kt
│ │ └── TimerType.kt
│ │ ├── designsystem
│ │ ├── bottomsheet
│ │ │ ├── MnBottomSheet.kt
│ │ │ └── MnBottomSheetDefaults.kt
│ │ ├── button
│ │ │ ├── box
│ │ │ │ ├── MnBoxButton.kt
│ │ │ │ ├── MnBoxButtonColorType.kt
│ │ │ │ ├── MnBoxButtonColors.kt
│ │ │ │ └── MnBoxButtonStyles.kt
│ │ │ ├── common
│ │ │ │ ├── MnButtonStyleProperties.kt
│ │ │ │ └── MnPressableWrapper.kt
│ │ │ ├── icon
│ │ │ │ └── MnIconButton.kt
│ │ │ ├── round
│ │ │ │ ├── MnRoundButton.kt
│ │ │ │ ├── MnRoundButtonColorType.kt
│ │ │ │ └── MnRoundButtonColors.kt
│ │ │ ├── select
│ │ │ │ ├── MnSelectButton.kt
│ │ │ │ ├── MnSelectButtonSelector.kt
│ │ │ │ ├── MnSelectList.kt
│ │ │ │ └── MnSelectListDefaults.kt
│ │ │ ├── text
│ │ │ │ ├── MnTextButton.kt
│ │ │ │ └── MnTextButtonStyles.kt
│ │ │ └── toggle
│ │ │ │ ├── MnToggleButton.kt
│ │ │ │ └── MnToggleButtonSize.kt
│ │ ├── datepicker
│ │ │ ├── MnDatePickerDefaults.kt
│ │ │ └── MnDatePickerDialog.kt
│ │ ├── dialog
│ │ │ ├── MnDialog.kt
│ │ │ └── MnDialogDefaults.kt
│ │ ├── icon
│ │ │ ├── MnLargeIcon.kt
│ │ │ ├── MnMediumIcon.kt
│ │ │ ├── MnSmallIcon.kt
│ │ │ ├── MnXLargeIcon.kt
│ │ │ └── MnXSmallIcon.kt
│ │ ├── picker
│ │ │ ├── MnWheelMinutePicker.kt
│ │ │ └── MnWheelPickerDefaults.kt
│ │ ├── spinner
│ │ │ └── MnSpinner.kt
│ │ ├── textfield
│ │ │ └── MnTextField.kt
│ │ ├── toast
│ │ │ └── MnToastSnackbarHost.kt
│ │ ├── token
│ │ │ ├── MnColor.kt
│ │ │ ├── MnIconSize.kt
│ │ │ ├── MnInteraction.kt
│ │ │ ├── MnRadius.kt
│ │ │ ├── MnSpacing.kt
│ │ │ ├── MnStroke.kt
│ │ │ └── MnTypography.kt
│ │ ├── tooltip
│ │ │ ├── MnTooltip.kt
│ │ │ └── MnTooltipDefaults.kt
│ │ └── topappbar
│ │ │ ├── MnTopAppBar.kt
│ │ │ └── MnTopAppBarDefaults.kt
│ │ ├── di
│ │ ├── MohanyangLoggerModule.kt
│ │ ├── PomodoroModule.kt
│ │ └── Qualifier.kt
│ │ ├── model
│ │ ├── cat
│ │ │ ├── CatInfoModel.kt
│ │ │ └── CatType.kt
│ │ ├── category
│ │ │ └── PomodoroCategoryModel.kt
│ │ ├── setting
│ │ │ └── PomodoroSettingModel.kt
│ │ └── user
│ │ │ └── UserInfoModel.kt
│ │ ├── noti
│ │ ├── PomodoroNotificationBitmapGenerator.kt
│ │ ├── PomodoroNotificationContentFactory.kt
│ │ └── PomodoroNotificationManager.kt
│ │ ├── screen
│ │ ├── PomodoroConstants.kt
│ │ ├── common
│ │ │ ├── LoadingScreen.kt
│ │ │ ├── NetworkErrorDialog.kt
│ │ │ ├── NetworkErrorScreen.kt
│ │ │ └── ServerErrorScreen.kt
│ │ ├── home
│ │ │ ├── HomeNavigator.kt
│ │ │ ├── category
│ │ │ │ ├── CategoryIconBottomSheet.kt
│ │ │ │ ├── CategoryNameVerifier.kt
│ │ │ │ ├── CategorySettingElements.kt
│ │ │ │ ├── CategorySettingScreen.kt
│ │ │ │ ├── CategorySettingViewModel.kt
│ │ │ │ ├── PomodoroCategoryBottomSheet.kt
│ │ │ │ ├── component
│ │ │ │ │ ├── CategoryActionMoreMenuList.kt
│ │ │ │ │ ├── CategoryBottomSheetContents.kt
│ │ │ │ │ └── CategoryBottomSheetHeaderContents.kt
│ │ │ │ └── model
│ │ │ │ │ ├── CategoryIcon.kt
│ │ │ │ │ ├── CategoryManageState.kt
│ │ │ │ │ └── CategoryModel.kt
│ │ │ ├── setting
│ │ │ │ ├── PomodoroSettingElements.kt
│ │ │ │ ├── PomodoroSettingScreen.kt
│ │ │ │ └── PomodoroSettingViewModel.kt
│ │ │ └── time
│ │ │ │ ├── PomodoroTimeSettingElements.kt
│ │ │ │ ├── PomodoroTimeSettingScreen.kt
│ │ │ │ └── PomodoroTimeSettingViewModel.kt
│ │ ├── mypage
│ │ │ ├── MyPageElements.kt
│ │ │ ├── MyPageNavigator.kt
│ │ │ ├── MyPageScreen.kt
│ │ │ ├── MyPageViewModel.kt
│ │ │ ├── component
│ │ │ │ └── FocusStatisticBox.kt
│ │ │ └── profile
│ │ │ │ ├── CatProfileScreen.kt
│ │ │ │ └── CatProfileViewModel.kt
│ │ ├── onboarding
│ │ │ ├── OnboardingNavigator.kt
│ │ │ ├── guide
│ │ │ │ ├── OnboardingGuideScreen.kt
│ │ │ │ └── OnboardingGuideViewModel.kt
│ │ │ ├── model
│ │ │ │ └── OnboardingGuideContent.kt
│ │ │ ├── naming
│ │ │ │ ├── CatNameVerifier.kt
│ │ │ │ ├── OnboardingNamingCatScreen.kt
│ │ │ │ ├── OnboardingNamingCatViewModel.kt
│ │ │ │ └── OnboardingNamingElements.kt
│ │ │ └── select
│ │ │ │ ├── OnboardingSelectCatElements.kt
│ │ │ │ ├── OnboardingSelectCatScreen.kt
│ │ │ │ └── OnboardingSelectCatViewModel.kt
│ │ ├── pomodoro
│ │ │ ├── PomodoroNavigator.kt
│ │ │ ├── focus
│ │ │ │ ├── PomodoroFocusElements.kt
│ │ │ │ ├── PomodoroFocusScreen.kt
│ │ │ │ └── PomodoroFocusViewModel.kt
│ │ │ ├── rest
│ │ │ │ ├── PomodoroRestElements.kt
│ │ │ │ ├── PomodoroRestScreen.kt
│ │ │ │ └── PomodoroRestViewModel.kt
│ │ │ └── waiting
│ │ │ │ ├── PomodoroBreakReadyElements.kt
│ │ │ │ ├── PomodoroBreakReadyScreen.kt
│ │ │ │ └── PomodoroBreakReadyViewModel.kt
│ │ └── statistics
│ │ │ ├── StaticsNavigator.kt
│ │ │ ├── StatisticsElements.kt
│ │ │ ├── StatisticsScreen.kt
│ │ │ ├── StatisticsViewModel.kt
│ │ │ ├── component
│ │ │ ├── Dot.kt
│ │ │ ├── FocusTimeListItem.kt
│ │ │ ├── StatisticsContentHeader.kt
│ │ │ ├── StatisticsTopBar.kt
│ │ │ ├── TotalFocusTimeContent.kt
│ │ │ ├── datepicker
│ │ │ │ ├── StatisticDatePickerModal.kt
│ │ │ │ └── StatisticDateState.kt
│ │ │ └── graph
│ │ │ │ ├── FocusGraphConfigure.kt
│ │ │ │ └── FocusTimeGrpahBox.kt
│ │ │ ├── model
│ │ │ ├── StatisticsModel.kt
│ │ │ └── mapper
│ │ │ │ └── StatisticsMapper.kt
│ │ │ └── widget
│ │ │ └── StatisticsContent.kt
│ │ ├── service
│ │ ├── BasePomodoroTimer.kt
│ │ ├── PomodoroTimer.kt
│ │ ├── PomodoroTimerEventHandler.kt
│ │ ├── PomodoroTimerServiceExtras.kt
│ │ ├── focus
│ │ │ ├── FocusTimer.kt
│ │ │ └── PomodoroFocusTimerService.kt
│ │ └── rest
│ │ │ ├── PomodoroRestTimerService.kt
│ │ │ └── RestTimer.kt
│ │ ├── theme
│ │ └── MnTheme.kt
│ │ └── util
│ │ ├── DpPxSpConversionUtils.kt
│ │ ├── FlowUtils.kt
│ │ ├── IntentUtils.kt
│ │ ├── MnNotificationManager.kt
│ │ ├── ModifierUtils.kt
│ │ ├── MohanyangEventLogger.kt
│ │ ├── NavigationUtils.kt
│ │ ├── PreviewUtils.kt
│ │ ├── TimeUtils.kt
│ │ └── TimerUtils.kt
│ └── res
│ ├── drawable
│ ├── ic_alert.xml
│ ├── ic_app.xml
│ ├── ic_arrow_down.xml
│ ├── ic_arrow_left.xml
│ ├── ic_arrow_right.xml
│ ├── ic_arrow_up.xml
│ ├── ic_asterisk.xml
│ ├── ic_box_pen.xml
│ ├── ic_brifecase.xml
│ ├── ic_bubble_ellipses.xml
│ ├── ic_category_default.xml
│ ├── ic_chart_bar.xml
│ ├── ic_chart_bar_fill.xml
│ ├── ic_check.xml
│ ├── ic_check_32.xml
│ ├── ic_check_circle.xml
│ ├── ic_chevron_down.xml
│ ├── ic_chevron_left.xml
│ ├── ic_chevron_right.xml
│ ├── ic_chevron_up.xml
│ ├── ic_circle.xml
│ ├── ic_clock.xml
│ ├── ic_close.xml
│ ├── ic_dumbbell.xml
│ ├── ic_ellipsis.xml
│ ├── ic_error.xml
│ ├── ic_feedback.xml
│ ├── ic_fire.xml
│ ├── ic_heart.xml
│ ├── ic_house.xml
│ ├── ic_house_fill.xml
│ ├── ic_internet.xml
│ ├── ic_lightning.xml
│ ├── ic_lock.xml
│ ├── ic_menu.xml
│ ├── ic_minus.xml
│ ├── ic_monitor.xml
│ ├── ic_moon.xml
│ ├── ic_null.xml
│ ├── ic_open_book.xml
│ ├── ic_pen.xml
│ ├── ic_play.xml
│ ├── ic_play_32.xml
│ ├── ic_plus.xml
│ ├── ic_sort_offline.xml
│ ├── ic_static_ready.xml
│ ├── ic_sun.xml
│ ├── ic_trashcan.xml
│ ├── ic_user.xml
│ ├── ic_user_fill.xml
│ ├── img_touch_hair_ball.png
│ ├── onboarding_contents_1.xml
│ ├── onboarding_contents_2.xml
│ └── onboarding_contents_3.xml
│ ├── font
│ ├── pretendard_bold.ttf
│ ├── pretendard_regular.ttf
│ └── pretendard_semibold.ttf
│ ├── layout
│ ├── notification_pomodoro_expand.xml
│ └── notification_pomodoro_standard.xml
│ ├── raw
│ ├── alarm.mp3
│ ├── cat_focus.riv
│ ├── cat_home.riv
│ ├── cat_rename_2.riv
│ ├── cat_rest.riv
│ ├── cat_select_2.riv
│ ├── loti_rest_waiting.json
│ ├── loti_rest_waiting_complete_focus.json
│ └── spinner.json
│ └── values
│ ├── arrays.xml
│ ├── colors.xml
│ └── strings.xml
└── settings.gradle.kts
/.editorconfig:
--------------------------------------------------------------------------------
1 | [*.{kt,kts}]
2 | end_of_line = lf
3 | ij_kotlin_allow_trailing_comma = true
4 | ij_kotlin_allow_trailing_comma_on_call_site = true
5 | ij_kotlin_imports_layout = *
6 | ij_kotlin_line_break_after_multiline_when_entry = true
7 | ij_kotlin_packages_to_use_import_on_demand = java.util.*, kotlinx.android.synthetic.**
8 | indent_size = 4
9 | indent_style = space
10 | insert_final_newline = true
11 | ktlint_code_style = android_studio
12 | ktlint_function_naming_ignore_when_annotated_with = Composable, Test
13 | ktlint_function_signature_body_expression_wrapping = default
14 | ktlint_function_signature_rule_force_multiline_when_parameter_count_greater_or_equal_than = unset
15 | ktlint_ignore_back_ticked_identifier = true
16 | ktlint_standard_value-parameter-comment = disabled
17 | ktlint_standard_filename = disabled
18 | max_line_length = off
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE/basic_template.md:
--------------------------------------------------------------------------------
1 | ## 작업 내용
2 |
3 | ## 체크리스트
4 | - [ ] 빌드 확인
5 |
6 | ## 동작 화면
7 |
8 | ## 살려주세요
9 |
10 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE/release_template.md:
--------------------------------------------------------------------------------
1 | ## RELEASE 모하냥
2 |
3 | > 해당하는 업데이트 사항을 선택해주세요:
4 |
5 | - [ ] Major
6 | - [ ] Minor
7 | - [ ] Patch
8 |
9 | ## 설명
10 |
11 | 이 PR에 포함된 변경 사항을 간략히 설명해주세요.
12 |
13 | ## 체크리스트
14 |
15 | - [ ] 체크 해야 하는 내용
16 |
--------------------------------------------------------------------------------
/.github/actions/get-app-version/action.yml:
--------------------------------------------------------------------------------
1 | name: 'Get App Version'
2 | description: 'Get App Version'
3 |
4 | outputs:
5 | major:
6 | description: "앱 버전 중 major"
7 | value: ${{ steps.get-app-version.outputs.major }}
8 | minor:
9 | description: "앱 버전 중 minor"
10 | value: ${{ steps.get-app-version.outputs.minor }}
11 | patch:
12 | description: "앱 버전 중 patch"
13 | value: ${{ steps.get-app-version.outputs.patch }}
14 | code:
15 | description: "앱 버전 코드"
16 | value: ${{ steps.get-app-version.outputs.code }}
17 | version_name:
18 | description: "앱 버전 이름"
19 | value: ${{ steps.get-app-version.outputs.version_name }}
20 |
21 | runs:
22 | using: "composite"
23 | steps:
24 | - name: Get App Version
25 | id: get-app-version
26 | shell: bash
27 | run: |
28 | major=$(jq .major app-version.json)
29 | minor=$(jq .minor app-version.json)
30 | patch=$(jq .patch app-version.json)
31 | code=$(jq .code app-version.json)
32 |
33 | {
34 | echo "major=$major"
35 | echo "minor=$minor"
36 | echo "patch=$patch"
37 | echo "code=$code"
38 | echo "version_name=$major.$minor.$patch"
39 | } >> $GITHUB_OUTPUT
--------------------------------------------------------------------------------
/.github/actions/setup-development-environment/action.yml:
--------------------------------------------------------------------------------
1 | name: "Setup Development Environment"
2 |
3 | inputs:
4 | google-services:
5 | description: 'Google Services Json'
6 | required: true
7 | test-mode:
8 | description: 'Test debug or release mode'
9 | required: false
10 | debug-properties:
11 | description: 'Secret Debug Properties'
12 | required: false
13 | release-properties:
14 | description: 'Secret Release Properties'
15 | required: false
16 |
17 | runs:
18 | using: "composite"
19 | steps:
20 | - name: Setup JDK
21 | uses: actions/setup-java@v4
22 | with:
23 | distribution: 'zulu'
24 | java-version: '21'
25 |
26 | - name: Setup Gradle
27 | uses: gradle/actions/setup-gradle@v3
28 |
29 | - name: Create google-services.json
30 | shell: bash
31 | run: echo -n "${{ inputs.google-services }}" | base64 --decode > ./app/google-services.json
32 |
33 | - name: Create Debug properties file
34 | shell: bash
35 | run: echo -n "${{ inputs.debug-properties }}" > ./debug.secrets.properties
36 |
37 | - name: Create Release properties file
38 | shell: bash
39 | run: echo -n "${{ inputs.release-properties }}" > ./release.secrets.properties
40 |
--------------------------------------------------------------------------------
/.github/actions/setup-key-environment/action.yml:
--------------------------------------------------------------------------------
1 | name: "Setup Release Environment"
2 |
3 | inputs:
4 | key-properties:
5 | description: 'Secret Key Properties'
6 | required: true
7 | key-file:
8 | description: 'Signed Key'
9 | required: true
10 |
11 | runs:
12 | using: "composite"
13 | steps:
14 | - name: Setup JDK
15 | uses: actions/setup-java@v4
16 | with:
17 | distribution: 'zulu'
18 | java-version: '21'
19 |
20 | - name: Setup Gradle
21 | uses: gradle/actions/setup-gradle@v3
22 |
23 | - name: Create Key Properties
24 | shell: bash
25 | run: echo -n "${{ inputs.key-properties }}" > ./key.secrets.properties
26 |
27 | - name: Create Signed Key
28 | shell: bash
29 | run: echo -n "${{ inputs.key-file }}" | base64 --decode > ./key-release
--------------------------------------------------------------------------------
/.github/actions/update-app-version/action.yml:
--------------------------------------------------------------------------------
1 | name: "Update App Version"
2 |
3 | inputs:
4 | major:
5 | description: "업데이트 할 앱 major"
6 | required: true
7 | minor:
8 | description: "업데이트 할 앱 버전 minor"
9 | required: true
10 | patch:
11 | description: "업데이트 할 앱 버전 patch"
12 | required: true
13 | code:
14 | description: "업데이트 할 앱 버전 code"
15 | required: true
16 | file:
17 | description: "업데이트 할 파일 경로"
18 | required: true
19 |
20 | runs:
21 | using: "composite"
22 | steps:
23 | - name: Update app version
24 | shell: bash
25 | if: success()
26 | run: |
27 | VERSION_FILE="${{ inputs.file }}"
28 | if command -v jq &> /dev/null; then
29 | # jq가 설치되어 있는 경우
30 | jq '.major = $major | .minor = $minor | .patch = $patch | .code = $code' \
31 | --argjson major "${{ inputs.major }}" \
32 | --argjson minor "${{ inputs.minor }}" \
33 | --argjson patch "${{ inputs.patch }}" \
34 | --argjson code "${{ inputs.code }}" \
35 | $VERSION_FILE > tmp.$$.json && mv tmp.$$.json $VERSION_FILE
36 | else
37 | # jq가 설치되어 있지 않은 경우
38 | sed -i "s/\"major\": [0-9]\+/\"major\": ${{ inputs.major }}/" $VERSION_FILE
39 | sed -i "s/\"minor\": [0-9]\+/\"minor\": ${{ inputs.minor }}/" $VERSION_FILE
40 | sed -i "s/\"patch\": [0-9]\+/\"patch\": ${{ inputs.patch }}/" $VERSION_FILE
41 | sed -i "s/\"code\": [0-9]\+/\"code\": ${{ inputs.code }}/" $VERSION_FILE
42 | fi
43 | git config --local user.email "github-actions[bot]@users.noreply.github.com"
44 | git config --local user.name "github-actions[bot]"
45 | git add $VERSION_FILE
46 | git commit -m "Update app version to ${{ inputs.major }}.${{ inputs.minor }}.${{ inputs.patch }} [${{ inputs.code }}]"
47 | git push
--------------------------------------------------------------------------------
/.github/auto_assign_config.yml:
--------------------------------------------------------------------------------
1 | addAssignees: author
2 | addReviewers: true
3 | reviewers:
4 | - HyomK
5 | - lee-ji-hoon
6 |
--------------------------------------------------------------------------------
/.github/workflows/deploy_firebase-distribution_debug.yml:
--------------------------------------------------------------------------------
1 | name: "[CD] Debug to Firebase Distribution"
2 |
3 | on:
4 | push:
5 | branches:
6 | - develop
7 |
8 | jobs:
9 | deploy_debug:
10 | environment: Debug
11 | runs-on: ubuntu-latest
12 | steps:
13 | - name: Checkout
14 | uses: actions/checkout@v4
15 |
16 | - name: Setup Development Environment
17 | uses: ./.github/actions/setup-development-environment
18 | with:
19 | google-services: ${{ secrets.GOOGLE_SERVICES }}
20 | test-mode: debug
21 | debug-properties: ${{ secrets.DEBUG_PROPERTIES }}
22 |
23 | - name: Setup Signed Key Environment
24 | uses: ./.github/actions/setup-key-environment
25 | with:
26 | key-properties: ${{ secrets.KEY_PROPERTIES }}
27 | key-file: ${{ secrets.SIGNED_KEY }}
28 |
29 | - name: Build Debug APK
30 | run: ./gradlew :app:assembleDebug
31 |
32 | - name: Check if APK exists
33 | run: ls -la app/build/outputs/apk/debug/
34 |
35 | - name: Upload to Firebase App Distribution
36 | uses: wzieba/Firebase-Distribution-Github-Action@v1
37 | with:
38 | appId: ${{secrets.FIREBASE_APP_ID}}
39 | serviceCredentialsFileContent: ${{ secrets.FIREBASE_CREDENTIALS }}
40 | groups: 뽀모냥
41 | file: app/build/outputs/apk/debug/app-debug.apk
--------------------------------------------------------------------------------
/.github/workflows/deploy_firebase-distribution_release.yml:
--------------------------------------------------------------------------------
1 | name: "[CD] Release to Firebase Distribution"
2 |
3 | on:
4 | push:
5 | branches:
6 | - develop
7 |
8 | jobs:
9 | deploy_release:
10 | environment: Release
11 | runs-on: ubuntu-latest
12 | steps:
13 | - name: Checkout
14 | uses: actions/checkout@v4
15 |
16 | - name: Setup Development Environment
17 | uses: ./.github/actions/setup-development-environment
18 | with:
19 | google-services: ${{ secrets.GOOGLE_SERVICES }}
20 | test-mode: release
21 | release-properties: ${{ secrets.RELEASE_PROPERTIES }}
22 |
23 | - name: Setup Signed Key Environment
24 | uses: ./.github/actions/setup-key-environment
25 | with:
26 | key-properties: ${{ secrets.KEY_PROPERTIES }}
27 | key-file: ${{ secrets.SIGNED_KEY }}
28 |
29 | - name: Build Release APK
30 | run: ./gradlew :app:assembleRelease
31 |
32 | - name: Check if APK exists
33 | run: ls -la app/build/outputs/apk/release/
34 |
35 | - name: Upload to Firebase App Distribution
36 | uses: wzieba/Firebase-Distribution-Github-Action@v1
37 | with:
38 | appId: ${{secrets.FIREBASE_APP_ID}}
39 | serviceCredentialsFileContent: ${{ secrets.FIREBASE_CREDENTIALS }}
40 | groups: 뽀모냥
41 | file: app/build/outputs/apk/release/app-release.apk
--------------------------------------------------------------------------------
/.github/workflows/pr_auto_assign.yml:
--------------------------------------------------------------------------------
1 | name: 'PR Auto Assign'
2 | on:
3 | pull_request:
4 | types: [opened, ready_for_review]
5 |
6 | permissions:
7 | contents: read
8 | pull-requests: write
9 |
10 | jobs:
11 | add-reviewers-and-assignee:
12 | runs-on: ubuntu-latest
13 | steps:
14 | - uses: kentaro-m/auto-assign-action@v1.2.5
15 | with:
16 | repo-token: '${{ secrets.GITHUB_TOKEN }}'
17 | configuration-path: '.github/auto_assign_config.yml'
18 |
--------------------------------------------------------------------------------
/.github/workflows/pr_template.yml:
--------------------------------------------------------------------------------
1 | name: PR Template Selector
2 |
3 | on:
4 | pull_request:
5 | types: [opened]
6 |
7 | jobs:
8 | apply-template:
9 | runs-on: ubuntu-latest
10 |
11 | steps:
12 | - name: Check out the repository
13 | uses: actions/checkout@v3
14 |
15 | - name: Set up Node.js
16 | uses: actions/setup-node@v3
17 | with:
18 | node-version: '16'
19 |
20 | - name: Check Branch and Apply Template
21 | uses: actions/github-script@v6
22 | with:
23 | script: |
24 | const fs = require('fs');
25 | const prBranch = context.payload.pull_request.head.ref;
26 | const baseBranch = context.payload.pull_request.base.ref;
27 |
28 | let templatePath = '.github/PULL_REQUEST_TEMPLATE/basic_template.md';
29 |
30 | if (prBranch.startsWith('release/') || prBranch.startsWith('hotfix/')) {
31 | templatePath = '.github/PULL_REQUEST_TEMPLATE/release_template.md';
32 | } else if (baseBranch === 'develop') {
33 | templatePath = '.github/PULL_REQUEST_TEMPLATE/basic_template.md';
34 | }
35 |
36 | const template = fs.readFileSync(templatePath, 'utf8');
37 | await github.rest.pulls.update({
38 | ...context.repo,
39 | pull_number: context.payload.pull_request.number,
40 | body: context.payload.pull_request.body ? `${template}\n\n---\n\n${context.payload.pull_request.body}` : template
41 | });
42 |
--------------------------------------------------------------------------------
/.github/workflows/verify_debug_build_test.yml:
--------------------------------------------------------------------------------
1 | name: "[CI] verify debug build test"
2 |
3 | on:
4 | pull_request:
5 | types: [ opened, synchronize ]
6 | push:
7 | branches:
8 | - main
9 | - develop
10 |
11 | jobs:
12 | build_test:
13 | environment: Debug
14 | runs-on: ubuntu-latest
15 | strategy:
16 | matrix:
17 | gradle_command:
18 | - :app:assembleDebug
19 | steps:
20 | - name: Checkout
21 | uses: actions/checkout@v4
22 |
23 | - name: Setup Development Environment
24 | uses: ./.github/actions/setup-development-environment
25 | with:
26 | google-services: ${{ secrets.GOOGLE_SERVICES }}
27 | test-mode: debug
28 | debug-properties: ${{ secrets.DEBUG_PROPERTIES }}
29 |
30 | - name: Setup Signed Key Environment
31 | uses: ./.github/actions/setup-key-environment
32 | with:
33 | key-properties: ${{ secrets.KEY_PROPERTIES }}
34 | key-file: ${{ secrets.SIGNED_KEY }}
35 |
36 | - name: build test
37 | run: ./gradlew ${{ matrix.gradle_command }}
--------------------------------------------------------------------------------
/.github/workflows/verify_release_build_test.yml:
--------------------------------------------------------------------------------
1 | name: "[CI] verify release build test"
2 |
3 | on:
4 | pull_request:
5 | types: [ opened, synchronize ]
6 | branches:
7 | - main
8 | push:
9 | branches:
10 | - main
11 |
12 | jobs:
13 | build_test:
14 | environment: Release
15 | runs-on: ubuntu-latest
16 | strategy:
17 | matrix:
18 | gradle_command:
19 | - :app:assembleRelease
20 | steps:
21 | - name: Checkout
22 | uses: actions/checkout@v4
23 |
24 | - name: Setup Development Environment
25 | uses: ./.github/actions/setup-development-environment
26 | with:
27 | google-services: ${{ secrets.GOOGLE_SERVICES }}
28 | test-mode: release
29 | release-properties: ${{ secrets.RELEASE_PROPERTIES }}
30 |
31 | - name: Setup Signed Key Environment
32 | uses: ./.github/actions/setup-key-environment
33 | with:
34 | key-properties: ${{ secrets.KEY_PROPERTIES }}
35 | key-file: ${{ secrets.SIGNED_KEY }}
36 |
37 | - name: build test
38 | run: ./gradlew ${{ matrix.gradle_command }}
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | .DS_Store
5 | /build
6 | /captures
7 | .externalNativeBuild
8 | .cxx
9 | local.properties
10 | .idea/
11 | /core/ui/build/
12 | /library/network/build/
13 | /.kotlin/
14 | debug.secrets.properties
15 | release.secrets.properties
16 | /app/google-services.json
17 | key-release
18 | key.secrets.properties
19 |
--------------------------------------------------------------------------------
/app-version.json:
--------------------------------------------------------------------------------
1 | {
2 | "major": 1,
3 | "minor": 0,
4 | "patch": 4,
5 | "code": 32
6 | }
7 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/app/src/debug/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/debug/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/debug/res/mipmap-hdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/PomoNyang-Android/b0d194d192747a7fe1abc9a5d9bffea357610a7f/app/src/debug/res/mipmap-hdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/debug/res/mipmap-hdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/PomoNyang-Android/b0d194d192747a7fe1abc9a5d9bffea357610a7f/app/src/debug/res/mipmap-hdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/debug/res/mipmap-mdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/PomoNyang-Android/b0d194d192747a7fe1abc9a5d9bffea357610a7f/app/src/debug/res/mipmap-mdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/debug/res/mipmap-mdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/PomoNyang-Android/b0d194d192747a7fe1abc9a5d9bffea357610a7f/app/src/debug/res/mipmap-mdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/debug/res/mipmap-xhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/PomoNyang-Android/b0d194d192747a7fe1abc9a5d9bffea357610a7f/app/src/debug/res/mipmap-xhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/debug/res/mipmap-xhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/PomoNyang-Android/b0d194d192747a7fe1abc9a5d9bffea357610a7f/app/src/debug/res/mipmap-xhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/debug/res/mipmap-xxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/PomoNyang-Android/b0d194d192747a7fe1abc9a5d9bffea357610a7f/app/src/debug/res/mipmap-xxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/debug/res/mipmap-xxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/PomoNyang-Android/b0d194d192747a7fe1abc9a5d9bffea357610a7f/app/src/debug/res/mipmap-xxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/debug/res/mipmap-xxxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/PomoNyang-Android/b0d194d192747a7fe1abc9a5d9bffea357610a7f/app/src/debug/res/mipmap-xxxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/debug/res/mipmap-xxxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/PomoNyang-Android/b0d194d192747a7fe1abc9a5d9bffea357610a7f/app/src/debug/res/mipmap-xxxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/debug/res/values/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFE9BF
4 |
--------------------------------------------------------------------------------
/app/src/debug/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 모하냥 debug
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/java/com/pomonyang/mohanyang/MainElements.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang
2 |
3 | import com.pomonyang.mohanyang.presentation.base.NetworkViewState
4 | import com.pomonyang.mohanyang.presentation.base.ViewEvent
5 | import com.pomonyang.mohanyang.presentation.base.ViewSideEffect
6 |
7 | data class MainState(
8 | override val isLoading: Boolean = true,
9 | override val isInternalError: Boolean = false,
10 | override val isInvalidError: Boolean = false,
11 | override val lastRequestAction: MainEvent? = null,
12 | ) : NetworkViewState()
13 |
14 | sealed interface MainEvent : ViewEvent {
15 | data object Init : MainEvent
16 | data object ClickRefresh : MainEvent
17 | data object ClickClose : MainEvent
18 | data object ClickRetry : MainEvent
19 | }
20 |
21 | sealed interface MainEffect : ViewSideEffect {
22 | data object ShowDialog : MainEffect
23 | data object DismissDialog : MainEffect
24 | data object GoToOnBoarding : MainEffect
25 | data object GoToTimer : MainEffect
26 | data object ExitApp : MainEffect
27 | }
28 |
--------------------------------------------------------------------------------
/app/src/main/java/com/pomonyang/mohanyang/di/ManagerModule.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.di
2 |
3 | import android.app.AlarmManager
4 | import android.content.Context
5 | import com.pomonyang.mohanyang.notification.MnAlarmManager
6 | import dagger.Module
7 | import dagger.Provides
8 | import dagger.hilt.InstallIn
9 | import dagger.hilt.android.qualifiers.ApplicationContext
10 | import dagger.hilt.components.SingletonComponent
11 |
12 | @Module
13 | @InstallIn(SingletonComponent::class)
14 | internal object ManagerModule {
15 | @Provides
16 | internal fun provideMnAlarmManager(
17 | @ApplicationContext context: Context,
18 | ): MnAlarmManager {
19 | val alarmManager =
20 | context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
21 | return MnAlarmManager(context, alarmManager)
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/app/src/main/java/com/pomonyang/mohanyang/di/ServiceModule.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.di
2 |
3 | import android.app.NotificationManager
4 | import android.content.Context
5 | import androidx.core.app.NotificationCompat
6 | import com.pomonyang.mohanyang.R
7 | import com.pomonyang.mohanyang.presentation.di.PomodoroNotification
8 | import com.pomonyang.mohanyang.presentation.screen.PomodoroConstants.POMODORO_NOTIFICATION_CHANNEL_ID
9 | import com.pomonyang.mohanyang.presentation.screen.PomodoroConstants.POMODORO_NOTIFICATION_ID
10 | import com.pomonyang.mohanyang.ui.ServiceHelper
11 | import dagger.Module
12 | import dagger.Provides
13 | import dagger.hilt.InstallIn
14 | import dagger.hilt.android.components.ServiceComponent
15 | import dagger.hilt.android.qualifiers.ApplicationContext
16 | import dagger.hilt.android.scopes.ServiceScoped
17 |
18 | @Module
19 | @InstallIn(ServiceComponent::class)
20 | internal object ServiceModule {
21 |
22 | @Provides
23 | @PomodoroNotification
24 | @ServiceScoped
25 | fun provideNotificationBuilder(
26 | @ApplicationContext context: Context,
27 | ): NotificationCompat.Builder = NotificationCompat.Builder(context, POMODORO_NOTIFICATION_CHANNEL_ID)
28 | .setContentTitle(context.getString(R.string.app_name))
29 | .setSmallIcon(R.drawable.ic_app_notification)
30 | .setContentIntent(ServiceHelper.clickPendingIntent(context, POMODORO_NOTIFICATION_ID))
31 |
32 | @Provides
33 | @ServiceScoped
34 | fun provideNotificationManager(
35 | @ApplicationContext context: Context,
36 | ): NotificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
37 | }
38 |
--------------------------------------------------------------------------------
/app/src/main/java/com/pomonyang/mohanyang/ui/BottomNavItem.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.ui
2 |
3 | import androidx.annotation.DrawableRes
4 |
5 | data class BottomNavItem(
6 | val route: Any,
7 | @DrawableRes val iconRes: Int,
8 | @DrawableRes val selectedIconRes: Int,
9 | val label: String,
10 | )
11 |
--------------------------------------------------------------------------------
/app/src/main/java/com/pomonyang/mohanyang/ui/ServiceHelper.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.ui
2 |
3 | import android.app.PendingIntent
4 | import android.content.Context
5 | import android.content.Intent
6 |
7 | object ServiceHelper {
8 |
9 | private const val FLAG = PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
10 |
11 | fun clickPendingIntent(
12 | context: Context,
13 | requestCode: Int,
14 | ): PendingIntent {
15 | val notificationIntent = context.packageManager.getLaunchIntentForPackage(context.packageName)?.apply {
16 | flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP
17 | }
18 | return PendingIntent.getActivity(
19 | context,
20 | requestCode,
21 | notificationIntent,
22 | FLAG,
23 | )
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/app/src/main/java/com/pomonyang/mohanyang/util/DebugTimberTree.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.util
2 |
3 | import timber.log.Timber
4 |
5 | internal class DebugTimberTree : Timber.DebugTree() {
6 | override fun createStackElementTag(element: StackTraceElement): String = "${super.createStackElementTag(element)}"
7 |
8 | override fun log(
9 | priority: Int,
10 | tag: String?,
11 | message: String,
12 | t: Throwable?,
13 | ) {
14 | val element =
15 | Throwable().stackTrace.first { stackElement ->
16 | stackElement.className.run {
17 | startsWith("timber.log.").not() && contains("Timber").not()
18 | }
19 | }
20 | val prefix = "[${element.fileName}:${element.lineNumber}]"
21 | super.log(priority, tag, "$prefix$message", t)
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/PomoNyang-Android/b0d194d192747a7fe1abc9a5d9bffea357610a7f/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/PomoNyang-Android/b0d194d192747a7fe1abc9a5d9bffea357610a7f/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/PomoNyang-Android/b0d194d192747a7fe1abc9a5d9bffea357610a7f/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/PomoNyang-Android/b0d194d192747a7fe1abc9a5d9bffea357610a7f/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/PomoNyang-Android/b0d194d192747a7fe1abc9a5d9bffea357610a7f/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFBB86FC
4 | #FF6200EE
5 | #FF3700B3
6 | #FF03DAC5
7 | #FF018786
8 | #FF000000
9 | #FFFFFFFF
10 | #FFFFE9BF
11 | #FFFAF6F3
12 |
--------------------------------------------------------------------------------
/app/src/main/res/values/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFE9BF
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | 모하냥
3 | moha-nyang-channel-v2
4 | moha-nyang
5 | moha-nyang-pomodoro
6 | local_notification_channel_id
7 | local_notification_id
8 | local_notification_title
9 | local_notification_message
10 | 오프라인 모드
11 | 집중을 끝내고 돌아왔어요
12 | 너무 오랜 시간동안 대기화면에 머물러서 홈화면으로 이동되었어요.
13 | moha-nyang-channel
14 | pomodoro_notification_channel_id
15 | interrupt_notification_channel_id
16 | interrupt_notification_channel_name
17 |
18 |
--------------------------------------------------------------------------------
/app/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/backup_rules.xml:
--------------------------------------------------------------------------------
1 |
8 |
9 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/data_extraction_rules.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
12 |
13 |
19 |
--------------------------------------------------------------------------------
/build-logic/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/build-logic/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")
2 |
3 | dependencyResolutionManagement {
4 | repositories {
5 | google()
6 | mavenCentral()
7 | }
8 |
9 | versionCatalogs {
10 | create("libs") {
11 | from(files("../gradle/libs.versions.toml"))
12 | }
13 | }
14 | }
15 |
16 | rootProject.name = "build-logic"
17 |
--------------------------------------------------------------------------------
/build-logic/src/main/java/com/pomonyang/mohanyang/AndroidApplicationComposeConventionPlugin.kt:
--------------------------------------------------------------------------------
1 | import com.android.build.api.dsl.ApplicationExtension
2 | import com.pomonyang.mohanyang.convention.configureAndroidCompose
3 | import com.pomonyang.mohanyang.convention.findPluginId
4 | import com.pomonyang.mohanyang.convention.libs
5 | import org.gradle.api.Plugin
6 | import org.gradle.api.Project
7 | import org.gradle.kotlin.dsl.getByType
8 |
9 | class AndroidApplicationComposeConventionPlugin : Plugin {
10 | override fun apply(target: Project) {
11 | with(target) {
12 | with(pluginManager) {
13 | apply(libs.findPluginId("android.application"))
14 | apply(libs.findPluginId("compose.compiler"))
15 | }
16 |
17 | val extension = extensions.getByType()
18 | configureAndroidCompose(extension)
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/build-logic/src/main/java/com/pomonyang/mohanyang/AndroidApplicationConventionPlugin.kt:
--------------------------------------------------------------------------------
1 | import com.android.build.api.dsl.ApplicationExtension
2 | import com.pomonyang.mohanyang.convention.ProjectConfigurations
3 | import com.pomonyang.mohanyang.convention.configureKotlinAndroid
4 | import com.pomonyang.mohanyang.convention.configureSecret
5 | import com.pomonyang.mohanyang.convention.findPluginId
6 | import com.pomonyang.mohanyang.convention.libs
7 | import org.gradle.api.Plugin
8 | import org.gradle.api.Project
9 | import org.gradle.kotlin.dsl.configure
10 |
11 | class AndroidApplicationConventionPlugin : Plugin {
12 | override fun apply(target: Project) {
13 | with(target) {
14 | with(pluginManager) {
15 | apply(libs.findPluginId("android.application"))
16 | apply(libs.findPluginId("kotlin.android"))
17 | apply(libs.findPluginId("kotlin.serialization"))
18 | apply(libs.findPluginId("gradle.secrets"))
19 | }
20 |
21 | extensions.configure {
22 | configureKotlinAndroid(this)
23 | configureSecret()
24 | defaultConfig.targetSdk = ProjectConfigurations.TARGET_SDK
25 |
26 | packaging {
27 | resources {
28 | excludes.add("/META-INF/{AL2.0,LGPL2.1}")
29 | }
30 | }
31 | }
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/build-logic/src/main/java/com/pomonyang/mohanyang/AndroidApplicationFirebaseConventionPlugin.kt:
--------------------------------------------------------------------------------
1 | import com.android.build.api.dsl.ApplicationExtension
2 | import com.google.firebase.crashlytics.buildtools.gradle.CrashlyticsExtension
3 | import com.pomonyang.mohanyang.convention.findPluginId
4 | import com.pomonyang.mohanyang.convention.libs
5 | import org.gradle.api.Plugin
6 | import org.gradle.api.Project
7 | import org.gradle.kotlin.dsl.configure
8 | import org.gradle.kotlin.dsl.dependencies
9 |
10 | class AndroidApplicationFirebaseConventionPlugin : Plugin {
11 | override fun apply(target: Project) {
12 | with(target) {
13 | with(pluginManager) {
14 | apply(libs.findPluginId("google.services"))
15 | apply(libs.findPluginId("firebase.pref"))
16 | apply(libs.findPluginId("firebase.crashlytics"))
17 | apply(libs.findPluginId("firebase.appdistribution"))
18 | }
19 |
20 | dependencies {
21 | add("implementation", libs.findBundle("firebase").get())
22 | add("implementation", platform(libs.findLibrary("firebase-bom").get()))
23 | }
24 |
25 | extensions.configure {
26 | buildTypes.configureEach {
27 | if (name == "release") {
28 | configure {
29 | mappingFileUploadEnabled = true
30 | }
31 | } else {
32 | configure {
33 | mappingFileUploadEnabled = false
34 | }
35 | }
36 | }
37 | }
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/build-logic/src/main/java/com/pomonyang/mohanyang/AndroidDatadogConventionPlugin.kt:
--------------------------------------------------------------------------------
1 | import com.pomonyang.mohanyang.convention.findPluginId
2 | import com.pomonyang.mohanyang.convention.implementation
3 | import com.pomonyang.mohanyang.convention.libs
4 | import org.gradle.api.Plugin
5 | import org.gradle.api.Project
6 | import org.gradle.kotlin.dsl.dependencies
7 |
8 | class AndroidDatadogConventionPlugin : Plugin {
9 | override fun apply(target: Project) {
10 | with(target) {
11 | with(pluginManager) {
12 | apply(libs.findPluginId("datadog"))
13 | }
14 |
15 | dependencies {
16 | implementation(libs.findLibrary("datadog-rum"))
17 | implementation(libs.findLibrary("datadog-compose"))
18 | implementation(libs.findLibrary("datadog-okhttp"))
19 | }
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/build-logic/src/main/java/com/pomonyang/mohanyang/AndroidHiltConventionPlugin.kt:
--------------------------------------------------------------------------------
1 | import com.pomonyang.mohanyang.convention.findPluginId
2 | import com.pomonyang.mohanyang.convention.implementation
3 | import com.pomonyang.mohanyang.convention.ksp
4 | import com.pomonyang.mohanyang.convention.libs
5 | import org.gradle.api.Plugin
6 | import org.gradle.api.Project
7 | import org.gradle.kotlin.dsl.dependencies
8 |
9 | class AndroidHiltConventionPlugin : Plugin {
10 | override fun apply(target: Project) {
11 | with(target) {
12 | with(pluginManager) {
13 | apply(libs.findPluginId("hilt"))
14 | apply(libs.findPluginId("ksp"))
15 | }
16 |
17 | dependencies {
18 | implementation(libs.findLibrary("dagger-hilt-android"))
19 | ksp(libs.findLibrary("dagger-hilt-android-compiler"))
20 | }
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/build-logic/src/main/java/com/pomonyang/mohanyang/AndroidLibraryComposeConventionPlugin.kt:
--------------------------------------------------------------------------------
1 | import com.android.build.gradle.LibraryExtension
2 | import com.pomonyang.mohanyang.convention.configureAndroidCompose
3 | import com.pomonyang.mohanyang.convention.findPluginId
4 | import com.pomonyang.mohanyang.convention.libs
5 | import org.gradle.api.Plugin
6 | import org.gradle.api.Project
7 | import org.gradle.kotlin.dsl.configure
8 |
9 | class AndroidLibraryComposeConventionPlugin : Plugin {
10 | override fun apply(target: Project) {
11 | with(target) {
12 | with(pluginManager) {
13 | apply(libs.findPluginId("compose.compiler"))
14 | apply(libs.findPluginId("android.library"))
15 | }
16 |
17 | extensions.configure {
18 | configureAndroidCompose(this)
19 | }
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/build-logic/src/main/java/com/pomonyang/mohanyang/AndroidLibraryConventionPlugin.kt:
--------------------------------------------------------------------------------
1 | import com.android.build.gradle.LibraryExtension
2 | import com.pomonyang.mohanyang.convention.configureKotlinAndroid
3 | import com.pomonyang.mohanyang.convention.configureSecret
4 | import com.pomonyang.mohanyang.convention.findPluginId
5 | import com.pomonyang.mohanyang.convention.libs
6 | import org.gradle.api.Plugin
7 | import org.gradle.api.Project
8 | import org.gradle.kotlin.dsl.configure
9 |
10 | class AndroidLibraryConventionPlugin : Plugin {
11 | override fun apply(target: Project) {
12 | with(target) {
13 | with(pluginManager) {
14 | apply(libs.findPluginId("android.library"))
15 | apply(libs.findPluginId("kotlin.android"))
16 | apply(libs.findPluginId("kotlin.serialization"))
17 | apply(libs.findPluginId("gradle.secrets"))
18 | }
19 |
20 | extensions.configure {
21 | configureKotlinAndroid(this)
22 | configureSecret()
23 | }
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/build-logic/src/main/java/com/pomonyang/mohanyang/AppVersionPlugin.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang
2 |
3 | import groovy.json.JsonSlurper
4 | import java.io.File
5 | import org.gradle.api.Plugin
6 | import org.gradle.api.Project
7 |
8 | open class AppVersionExtension(val name: String, val code: Int)
9 |
10 | data class AppVersion(
11 | val major: Int,
12 | val minor: Int,
13 | val patch: Int,
14 | val code: Int,
15 | ) {
16 | fun getName() = "$major.$minor.$patch"
17 | fun getVersionCode() = code
18 | }
19 |
20 | class AppVersionPlugin : Plugin {
21 | override fun apply(project: Project) {
22 | val versionFile = getAppVersionFile(project)
23 | val appVersion = getAppVersion(versionFile)
24 | setupAppVersionExtension(project, appVersion)
25 | }
26 |
27 | private fun getAppVersionFile(project: Project): File {
28 | val file = File(project.rootDir, "app-version.json")
29 | if (!file.exists()) {
30 | throw IllegalStateException("app-version.json file not found")
31 | }
32 | return file
33 | }
34 |
35 | private fun getAppVersion(versionFile: File): AppVersion {
36 | val appVersionJson = JsonSlurper().parseText(versionFile.readText()) as Map<*, *>
37 | return AppVersion(
38 | (appVersionJson["major"] as Number).toInt(),
39 | (appVersionJson["minor"] as Number).toInt(),
40 | (appVersionJson["patch"] as Number).toInt(),
41 | (appVersionJson["code"] as Number).toInt(),
42 | )
43 | }
44 |
45 | private fun setupAppVersionExtension(project: Project, appVersion: AppVersion) {
46 | project.extensions.create(
47 | "appVersion",
48 | AppVersionExtension::class.java,
49 | appVersion.getName(),
50 | appVersion.getVersionCode(),
51 | )
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/build-logic/src/main/java/com/pomonyang/mohanyang/convention/DependencyHandlerExtensions.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.convention
2 |
3 | import java.util.Optional
4 | import org.gradle.api.artifacts.Dependency
5 | import org.gradle.api.artifacts.dsl.DependencyHandler
6 | import org.gradle.api.provider.Provider
7 |
8 | internal fun DependencyHandler.implementation(dependencyNotation: Optional>): Dependency? = add("implementation", dependencyNotation.get())
9 |
10 | internal fun DependencyHandler.debugImplementation(dependencyNotation: Optional>): Dependency? = add("debugImplementation", dependencyNotation.get())
11 |
12 | internal fun DependencyHandler.ksp(dependencyNotation: Optional>): Dependency? = add("ksp", dependencyNotation.get())
13 |
14 | internal fun DependencyHandler.compileOnly(dependencyNotation: Optional>): Dependency? = add("compileOnly", dependencyNotation.get())
15 |
--------------------------------------------------------------------------------
/build-logic/src/main/java/com/pomonyang/mohanyang/convention/GithubUtils.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.convention
2 |
3 | import java.io.File
4 | import java.util.concurrent.TimeUnit
5 |
6 | object GithubUtils {
7 | fun commitHash(): String = runCommand("git rev-parse --short=8 HEAD")
8 |
9 | fun lastCommitMessage(): String = runCommand("git show -s --format=%B")
10 |
11 | private fun runCommand(
12 | command: String,
13 | workingDir: File = File("."),
14 | ): String = try {
15 | val parts = command.split("\\s".toRegex())
16 | val process =
17 | ProcessBuilder(*parts.toTypedArray())
18 | .directory(workingDir)
19 | .redirectOutput(ProcessBuilder.Redirect.PIPE)
20 | .redirectError(ProcessBuilder.Redirect.PIPE)
21 | .start()
22 |
23 | process.waitFor(60, TimeUnit.MINUTES)
24 | process.inputStream
25 | .bufferedReader()
26 | .readText()
27 | .trim()
28 | } catch (e: Exception) {
29 | e.printStackTrace()
30 | "Command failed"
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/build-logic/src/main/java/com/pomonyang/mohanyang/convention/KotlinAndroid.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.convention
2 |
3 | import com.android.build.api.dsl.CommonExtension
4 | import com.pomonyang.mohanyang.convention.ProjectConfigurations.JAVA_VERSION
5 | import org.gradle.api.Project
6 | import org.gradle.kotlin.dsl.dependencies
7 | import org.gradle.kotlin.dsl.provideDelegate
8 |
9 | internal fun Project.configureKotlinAndroid(commonExtension: CommonExtension<*, *, *, *, *, *>) {
10 | commonExtension.apply {
11 | compileSdk = ProjectConfigurations.COMPILE_SDK
12 |
13 | defaultConfig {
14 | minSdk = ProjectConfigurations.MIN_SDK
15 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
16 | vectorDrawables.useSupportLibrary = true
17 | resourceConfigurations.addAll(listOf("en", "ko"))
18 | }
19 |
20 | compileOptions {
21 | sourceCompatibility = JAVA_VERSION
22 | targetCompatibility = JAVA_VERSION
23 | }
24 |
25 | buildFeatures {
26 | buildConfig = true
27 | }
28 |
29 | kotlinOptions {
30 | val warningsAsErrors: String? by project
31 | allWarningsAsErrors = warningsAsErrors.toBoolean()
32 |
33 | freeCompilerArgs = freeCompilerArgs +
34 | listOf(
35 | "-opt-in=kotlin.RequiresOptIn",
36 | "-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi",
37 | "-opt-in=kotlinx.coroutines.FlowPreview",
38 | )
39 |
40 | jvmTarget = JAVA_VERSION.toString()
41 | }
42 |
43 | dependencies {
44 | add("implementation", libs.findLibrary("timber").get())
45 | add("implementation", libs.findLibrary("kotlin.serialization.json").get())
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/build-logic/src/main/java/com/pomonyang/mohanyang/convention/ProjectConfigurations.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.convention
2 |
3 | import org.gradle.api.JavaVersion
4 |
5 | object ProjectConfigurations {
6 | const val COMPILE_SDK = 34
7 | const val MIN_SDK = 26
8 | const val TARGET_SDK = 34
9 | val JAVA_VERSION = JavaVersion.VERSION_21
10 | }
11 |
--------------------------------------------------------------------------------
/build-logic/src/main/java/com/pomonyang/mohanyang/convention/ProjectExtensions.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.convention
2 |
3 | import org.gradle.api.Project
4 | import org.gradle.api.artifacts.VersionCatalog
5 | import org.gradle.api.artifacts.VersionCatalogsExtension
6 | import org.gradle.kotlin.dsl.getByType
7 |
8 | val Project.libs: VersionCatalog
9 | get() = extensions.getByType().named("libs")
10 |
11 | internal fun VersionCatalog.findPluginId(alias: String): String = findPlugin(alias).get().get().pluginId
12 |
--------------------------------------------------------------------------------
/build-logic/src/main/java/com/pomonyang/mohanyang/convention/Secrets.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.convention
2 |
3 | import com.google.android.libraries.mapsplatform.secrets_gradle_plugin.SecretsPluginExtension
4 | import org.gradle.api.Project
5 | import org.gradle.kotlin.dsl.configure
6 |
7 | // https://developers.google.com/maps/documentation/android-sdk/secrets-gradle-plugin?hl=ko
8 | internal fun Project.configureSecret() {
9 | val releasePropertiesName = "release.secrets.properties"
10 | val debugPropertiedName = "debug.secrets.properties"
11 | extensions.configure {
12 | val isDebug = project.gradle.startParameter.taskNames.any { it.contains("Debug", ignoreCase = true) }
13 | propertiesFileName = if (isDebug) debugPropertiedName else releasePropertiesName
14 | ignoreList.add("keyToIgnore")
15 | ignoreList.add("sdk.*")
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.android.application) apply false
3 | alias(libs.plugins.kotlin.android) apply false
4 | alias(libs.plugins.kotlin.jvm) apply false
5 | alias(libs.plugins.hilt) apply false
6 | alias(libs.plugins.android.library) apply false
7 | alias(libs.plugins.ksp) apply false
8 | alias(libs.plugins.compose.compiler) apply false
9 | alias(libs.plugins.kotlin.serialization) apply false
10 | alias(libs.plugins.gradle.secrets) apply false
11 | alias(libs.plugins.google.services) apply false
12 | alias(libs.plugins.firebase.pref) apply false
13 | alias(libs.plugins.firebase.crashlytics) apply false
14 | alias(libs.plugins.firebase.appdistribution) apply false
15 | alias(libs.plugins.datadog) apply false
16 | }
17 |
--------------------------------------------------------------------------------
/data/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/data/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("mohanyang.android.library")
3 | id("mohanyang.android.hilt")
4 | }
5 |
6 | android {
7 | namespace = "com.mohanyang.data"
8 | defaultConfig {
9 | consumerProguardFiles("consumer-rules.pro")
10 | }
11 | }
12 |
13 | dependencies {
14 | implementation(libs.kotlin.serialization.json)
15 | implementation(libs.kotlin.serialization.converter)
16 | implementation(libs.retrofit)
17 | implementation(libs.okhttp)
18 | implementation(libs.okhttp.logging)
19 | implementation(libs.kotlin.coroutine.core)
20 | implementation(libs.androidx.datastore)
21 | implementation(libs.androidx.room.runtime)
22 | implementation(libs.androidx.room.ktx)
23 | implementation(libs.datadog.okhttp)
24 | ksp(libs.androidx.room.compiler)
25 | }
26 |
--------------------------------------------------------------------------------
/data/consumer-rules.pro:
--------------------------------------------------------------------------------
1 | ## coroutine
2 | -keep class kotlinx.coroutines.android.** {*;}
3 | -keepnames class kotlinx.coroutines.internal.MainDispatcherFactory { *; }
4 | -keepnames class kotlinx.coroutines.CoroutineExceptionHandler { *; }
5 | -keepclassmembernames class kotlinx.** {
6 | volatile ;
7 | }
8 | -dontwarn kotlinx.coroutines.**
9 |
10 | ## Kotlin
11 | -keepattributes *Annotation*
12 | -keep class kotlin.** { *; }
13 | -keep class org.jetbrains.** { *; }
--------------------------------------------------------------------------------
/data/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/data/src/main/java/com/pomonyang/mohanyang/data/local/datastore/datasource/deviceid/DeviceIdLocalDataSource.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.data.local.datastore.datasource.deviceid
2 |
3 | interface DeviceIdLocalDataSource {
4 | suspend fun getDeviceId(): String
5 | }
6 |
--------------------------------------------------------------------------------
/data/src/main/java/com/pomonyang/mohanyang/data/local/datastore/datasource/deviceid/DeviceIdLocalDataSourceImpl.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.data.local.datastore.datasource.deviceid
2 |
3 | import android.annotation.SuppressLint
4 | import android.content.Context
5 | import android.provider.Settings
6 | import androidx.datastore.core.DataStore
7 | import androidx.datastore.core.IOException
8 | import androidx.datastore.preferences.core.Preferences
9 | import androidx.datastore.preferences.core.emptyPreferences
10 | import androidx.datastore.preferences.core.stringPreferencesKey
11 | import com.pomonyang.mohanyang.data.local.datastore.di.DeviceIdDataStore
12 | import dagger.hilt.android.qualifiers.ApplicationContext
13 | import java.util.*
14 | import javax.inject.Inject
15 | import kotlinx.coroutines.flow.catch
16 | import kotlinx.coroutines.flow.first
17 |
18 | internal class DeviceIdLocalDataSourceImpl @Inject constructor(
19 | @DeviceIdDataStore private val dataStore: DataStore,
20 | @ApplicationContext private val context: Context,
21 | ) : DeviceIdLocalDataSource {
22 |
23 | override suspend fun getDeviceId(): String = getStoredDeviceId() ?: getSSAID() ?: getUUID()
24 |
25 | private suspend fun getStoredDeviceId(): String? = dataStore.data
26 | .catch { exception ->
27 | if (exception is IOException) {
28 | emit(emptyPreferences())
29 | } else {
30 | throw exception
31 | }
32 | }.first()[DEVICE_ID_KEY]
33 |
34 | @SuppressLint("HardwareIds")
35 | private fun getSSAID(): String? = Settings.Secure.getString(context.contentResolver, Settings.Secure.ANDROID_ID)
36 |
37 | private fun getUUID(): String = UUID.randomUUID().toString()
38 |
39 | companion object {
40 | const val DEVICE_ID_PREFERENCES_NAME = "device_id_preferences"
41 | private val DEVICE_ID_KEY = stringPreferencesKey("device_id")
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/data/src/main/java/com/pomonyang/mohanyang/data/local/datastore/datasource/notification/NotificationLocalDataSource.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.data.local.datastore.datasource.notification
2 |
3 | interface NotificationLocalDataSource {
4 | suspend fun saveInterruptNotification(isEnabled: Boolean)
5 | suspend fun saveTimerNotification(isEnabled: Boolean)
6 | suspend fun saveLockScreenNotification(isEnabled: Boolean)
7 | suspend fun isInterruptNotificationEnabled(): Boolean
8 | suspend fun isTimerNotification(): Boolean
9 | suspend fun isLockScreenNotification(): Boolean
10 | }
11 |
--------------------------------------------------------------------------------
/data/src/main/java/com/pomonyang/mohanyang/data/local/datastore/datasource/token/TokenLocalDataSource.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.data.local.datastore.datasource.token
2 |
3 | interface TokenLocalDataSource {
4 | suspend fun saveAccessToken(accessToken: String)
5 | suspend fun saveRefreshToken(refreshToken: String)
6 | suspend fun saveFcmToken(fcmToken: String)
7 | suspend fun getAccessToken(): String
8 | suspend fun getRefreshToken(): String
9 | suspend fun getFcmToken(): String
10 | suspend fun clear()
11 | }
12 |
--------------------------------------------------------------------------------
/data/src/main/java/com/pomonyang/mohanyang/data/local/datastore/datasource/user/UserLocalDataSource.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.data.local.datastore.datasource.user
2 |
3 | import com.pomonyang.mohanyang.data.remote.model.response.UserInfoResponse
4 |
5 | internal interface UserLocalDataSource {
6 | suspend fun saveUserInfo(userInfo: UserInfoResponse)
7 | suspend fun getUserInfo(): UserInfoResponse
8 | }
9 |
--------------------------------------------------------------------------------
/data/src/main/java/com/pomonyang/mohanyang/data/local/datastore/di/DataStoreQualifier.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.data.local.datastore.di
2 |
3 | import javax.inject.Qualifier
4 |
5 | @Qualifier
6 | @Retention(AnnotationRetention.BINARY)
7 | annotation class TokenDataStore
8 |
9 | @Qualifier
10 | @Retention(AnnotationRetention.BINARY)
11 | annotation class DeviceIdDataStore
12 |
13 | @Qualifier
14 | @Retention(AnnotationRetention.BINARY)
15 | annotation class PomodoroDataStore
16 |
17 | @Qualifier
18 | @Retention(AnnotationRetention.BINARY)
19 | annotation class UserDataStore
20 |
21 | @Qualifier
22 | @Retention(AnnotationRetention.BINARY)
23 | annotation class NotificationDataStore
24 |
--------------------------------------------------------------------------------
/data/src/main/java/com/pomonyang/mohanyang/data/local/datastore/di/LocalDataSourceModule.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.data.local.datastore.di
2 |
3 | import com.pomonyang.mohanyang.data.local.datastore.datasource.deviceid.DeviceIdLocalDataSource
4 | import com.pomonyang.mohanyang.data.local.datastore.datasource.deviceid.DeviceIdLocalDataSourceImpl
5 | import com.pomonyang.mohanyang.data.local.datastore.datasource.notification.NotificationLocalDataSource
6 | import com.pomonyang.mohanyang.data.local.datastore.datasource.notification.NotificationLocalDataSourceImpl
7 | import com.pomonyang.mohanyang.data.local.datastore.datasource.token.TokenLocalDataSource
8 | import com.pomonyang.mohanyang.data.local.datastore.datasource.token.TokenLocalDataSourceImpl
9 | import com.pomonyang.mohanyang.data.local.datastore.datasource.user.UserLocalDataSource
10 | import com.pomonyang.mohanyang.data.local.datastore.datasource.user.UserLocalDataSourceImpl
11 | import dagger.Binds
12 | import dagger.Module
13 | import dagger.hilt.InstallIn
14 | import dagger.hilt.components.SingletonComponent
15 | import javax.inject.Singleton
16 |
17 | @Module
18 | @InstallIn(SingletonComponent::class)
19 | internal abstract class LocalDataSourceModule {
20 | @Binds
21 | @Singleton
22 | abstract fun provideTokenDataSource(tokenDataSourceImpl: TokenLocalDataSourceImpl): TokenLocalDataSource
23 |
24 | @Binds
25 | @Singleton
26 | abstract fun provideDeviceIdDataSource(deviceIdDataSourceImpl: DeviceIdLocalDataSourceImpl): DeviceIdLocalDataSource
27 |
28 | @Binds
29 | @Singleton
30 | abstract fun provideUserDataSource(userLocalDataSourceImpl: UserLocalDataSourceImpl): UserLocalDataSource
31 |
32 | @Binds
33 | @Singleton
34 | abstract fun provideNotificationDataSource(notificationDataSourceImpl: NotificationLocalDataSourceImpl): NotificationLocalDataSource
35 | }
36 |
--------------------------------------------------------------------------------
/data/src/main/java/com/pomonyang/mohanyang/data/local/datastore/model/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/PomoNyang-Android/b0d194d192747a7fe1abc9a5d9bffea357610a7f/data/src/main/java/com/pomonyang/mohanyang/data/local/datastore/model/.gitkeep
--------------------------------------------------------------------------------
/data/src/main/java/com/pomonyang/mohanyang/data/local/datastore/util/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/PomoNyang-Android/b0d194d192747a7fe1abc9a5d9bffea357610a7f/data/src/main/java/com/pomonyang/mohanyang/data/local/datastore/util/.gitkeep
--------------------------------------------------------------------------------
/data/src/main/java/com/pomonyang/mohanyang/data/local/device/receiver/LockScreenBroadcastReceiver.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.data.local.device.receiver
2 |
3 | import android.content.BroadcastReceiver
4 | import android.content.Context
5 | import android.content.Intent
6 |
7 | class LockScreenBroadcastReceiver(
8 | private var lockStateListener: (Boolean) -> Unit,
9 | ) : BroadcastReceiver() {
10 |
11 | override fun onReceive(context: Context?, intent: Intent?) {
12 | if (intent == null) return
13 | when (intent.action) {
14 | Intent.ACTION_SCREEN_ON -> lockStateListener(false)
15 | Intent.ACTION_SCREEN_OFF -> lockStateListener(true)
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/data/src/main/java/com/pomonyang/mohanyang/data/local/device/util/LockScreenUtils.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.data.local.device.util
2 |
3 | import android.content.Context
4 | import android.content.Context.RECEIVER_EXPORTED
5 | import android.content.Context.RECEIVER_NOT_EXPORTED
6 | import android.content.Intent
7 | import android.content.IntentFilter
8 | import android.os.Build
9 | import com.pomonyang.mohanyang.data.local.device.receiver.LockScreenBroadcastReceiver
10 | import kotlinx.coroutines.channels.awaitClose
11 | import kotlinx.coroutines.flow.callbackFlow
12 | import kotlinx.coroutines.flow.distinctUntilChanged
13 |
14 | fun Context.lockScreenState() = callbackFlow {
15 | val lockScreenReceiver = LockScreenBroadcastReceiver { isLocked ->
16 | trySend(isLocked)
17 | }
18 |
19 | val screenEventIntentFilter = IntentFilter().apply {
20 | addAction(Intent.ACTION_SCREEN_ON)
21 | addAction(Intent.ACTION_SCREEN_OFF)
22 | }
23 |
24 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
25 | applicationContext.registerReceiver(
26 | lockScreenReceiver,
27 | screenEventIntentFilter,
28 | RECEIVER_NOT_EXPORTED,
29 | )
30 | } else {
31 | applicationContext.registerReceiver(
32 | lockScreenReceiver,
33 | screenEventIntentFilter,
34 | RECEIVER_EXPORTED,
35 | )
36 | }
37 |
38 | awaitClose {
39 | applicationContext.unregisterReceiver(lockScreenReceiver)
40 | }
41 | }.distinctUntilChanged()
42 |
--------------------------------------------------------------------------------
/data/src/main/java/com/pomonyang/mohanyang/data/local/room/database/PomodoroSettingDataBase.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.data.local.room.database
2 |
3 | import androidx.room.Database
4 | import androidx.room.RoomDatabase
5 | import com.pomonyang.mohanyang.data.local.room.dao.PomodoroSettingDao
6 | import com.pomonyang.mohanyang.data.local.room.enitity.PomodoroSettingEntity
7 |
8 | @Database(entities = [PomodoroSettingEntity::class], version = 2, exportSchema = false)
9 | internal abstract class PomodoroSettingDataBase : RoomDatabase() {
10 | abstract fun dao(): PomodoroSettingDao
11 | }
12 |
--------------------------------------------------------------------------------
/data/src/main/java/com/pomonyang/mohanyang/data/local/room/database/PomodoroTimerDataBase.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.data.local.room.database
2 |
3 | import androidx.room.Database
4 | import androidx.room.RoomDatabase
5 | import com.pomonyang.mohanyang.data.local.room.dao.PomodoroTimerDao
6 | import com.pomonyang.mohanyang.data.local.room.enitity.PomodoroTimerEntity
7 |
8 | @Database(entities = [PomodoroTimerEntity::class], version = 1, exportSchema = false)
9 | internal abstract class PomodoroTimerDataBase : RoomDatabase() {
10 | abstract fun dao(): PomodoroTimerDao
11 | }
12 |
--------------------------------------------------------------------------------
/data/src/main/java/com/pomonyang/mohanyang/data/local/room/di/RoomModule.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.data.local.room.di
2 |
3 | import android.content.Context
4 | import androidx.room.Room
5 | import com.pomonyang.mohanyang.data.local.room.dao.PomodoroSettingDao
6 | import com.pomonyang.mohanyang.data.local.room.dao.PomodoroTimerDao
7 | import com.pomonyang.mohanyang.data.local.room.database.PomodoroSettingDataBase
8 | import com.pomonyang.mohanyang.data.local.room.database.PomodoroTimerDataBase
9 | import dagger.Module
10 | import dagger.Provides
11 | import dagger.hilt.InstallIn
12 | import dagger.hilt.android.qualifiers.ApplicationContext
13 | import dagger.hilt.components.SingletonComponent
14 | import javax.inject.Singleton
15 |
16 | @Module
17 | @InstallIn(SingletonComponent::class)
18 | internal object RoomModule {
19 | @Provides
20 | @Singleton
21 | fun providePomodoroSettingDataBase(
22 | @ApplicationContext context: Context,
23 | ): PomodoroSettingDataBase = Room.databaseBuilder(
24 | context = context,
25 | klass = PomodoroSettingDataBase::class.java,
26 | name = "pomodoro-category-database", // database name string 관리 필요
27 | )
28 | .fallbackToDestructiveMigration()
29 | .build()
30 |
31 | @Provides
32 | @Singleton
33 | fun providePomodoroTimerDataBase(
34 | @ApplicationContext context: Context,
35 | ): PomodoroTimerDataBase = Room
36 | .databaseBuilder(
37 | context = context,
38 | klass = PomodoroTimerDataBase::class.java,
39 | name = "pomodoro-timer-database", // database name string 관리 필요
40 | )
41 | .fallbackToDestructiveMigration()
42 | .build()
43 |
44 | @Provides
45 | @Singleton
46 | fun providePomodoroSettingDao(database: PomodoroSettingDataBase): PomodoroSettingDao = database.dao()
47 |
48 | @Provides
49 | @Singleton
50 | fun providePomodoroTimerDao(database: PomodoroTimerDataBase): PomodoroTimerDao = database.dao()
51 | }
52 |
--------------------------------------------------------------------------------
/data/src/main/java/com/pomonyang/mohanyang/data/local/room/enitity/PomodoroSettingEntity.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.data.local.room.enitity
2 |
3 | import androidx.room.Entity
4 | import androidx.room.PrimaryKey
5 |
6 | @Entity(tableName = "pomodoro_setting")
7 | data class PomodoroSettingEntity(
8 | @PrimaryKey
9 | val categoryNo: Int,
10 | val title: String,
11 | val focusTime: String,
12 | val restTime: String,
13 | val iconType: String = "",
14 | val isSelected: Boolean = false,
15 | )
16 |
--------------------------------------------------------------------------------
/data/src/main/java/com/pomonyang/mohanyang/data/local/room/enitity/PomodoroTimerEntity.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.data.local.room.enitity
2 |
3 | import androidx.room.Entity
4 | import androidx.room.PrimaryKey
5 | import com.pomonyang.mohanyang.data.local.room.util.formatDurationToMinutesString
6 | import com.pomonyang.mohanyang.data.remote.model.request.PomodoroTimerRequest
7 | import com.pomonyang.mohanyang.data.repository.util.getCurrentIsoInstant
8 |
9 | @Entity(tableName = "pomodoro_timer")
10 | data class PomodoroTimerEntity(
11 | @PrimaryKey
12 | val focusTimeId: String,
13 | val focusedTime: Int = 0,
14 | val restedTime: Int = 0,
15 | val doneAt: String = "",
16 | val categoryNo: Int,
17 | )
18 |
19 | fun PomodoroTimerEntity.toRequestModel() = PomodoroTimerRequest(
20 | clientFocusTimeId = focusTimeId,
21 | categoryNo = categoryNo,
22 | focusedTime = focusedTime.formatDurationToMinutesString(),
23 | restedTime = restedTime.formatDurationToMinutesString(),
24 | doneAt = doneAt.ifEmpty { getCurrentIsoInstant() },
25 | )
26 |
--------------------------------------------------------------------------------
/data/src/main/java/com/pomonyang/mohanyang/data/local/room/util/TimeUtils.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.data.local.room.util
2 |
3 | import java.time.Duration
4 |
5 | fun Int.formatDurationToMinutesString(): String = Duration.ofMinutes((this / 60).toLong()).toString()
6 |
--------------------------------------------------------------------------------
/data/src/main/java/com/pomonyang/mohanyang/data/remote/datasource/auth/AuthRemoteDataSource.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.data.remote.datasource.auth
2 |
3 | import com.pomonyang.mohanyang.data.remote.model.response.TokenResponse
4 |
5 | interface AuthRemoteDataSource {
6 | suspend fun login(deviceId: String): Result
7 | suspend fun logout() // TODO
8 | suspend fun refreshAccessToken(refreshToken: String): Result
9 | }
10 |
--------------------------------------------------------------------------------
/data/src/main/java/com/pomonyang/mohanyang/data/remote/datasource/auth/AuthRemoteDataSourceImpl.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.data.remote.datasource.auth
2 |
3 | import com.pomonyang.mohanyang.data.remote.model.request.RefreshTokenRequest
4 | import com.pomonyang.mohanyang.data.remote.model.request.TokenRequest
5 | import com.pomonyang.mohanyang.data.remote.service.AuthService
6 | import javax.inject.Inject
7 |
8 | internal class AuthRemoteDataSourceImpl @Inject constructor(
9 | private val authService: AuthService,
10 | ) : AuthRemoteDataSource {
11 |
12 | override suspend fun login(deviceId: String) = authService.getTokenByDeviceId(TokenRequest(deviceId))
13 |
14 | override suspend fun logout() {
15 | }
16 |
17 | override suspend fun refreshAccessToken(refreshToken: String) = authService.refreshToken(RefreshTokenRequest(refreshToken))
18 | }
19 |
--------------------------------------------------------------------------------
/data/src/main/java/com/pomonyang/mohanyang/data/remote/datasource/pomodoro/PomodoroSettingRemoteDataSource.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.data.remote.datasource.pomodoro
2 |
3 | import com.pomonyang.mohanyang.data.remote.model.response.PomodoroSettingResponse
4 |
5 | interface PomodoroSettingRemoteDataSource {
6 | suspend fun getPomodoroSettingList(): Result>
7 | suspend fun modifyCategorySettingOption(
8 | categoryNo: Int,
9 | focusTime: String? = null,
10 | restTime: String? = null,
11 | iconType: String? = null,
12 | title: String? = null,
13 | ): Result
14 | suspend fun addPomodoroCategory(
15 | title: String,
16 | iconType: String,
17 | ): Result
18 |
19 | suspend fun deleteCategories(categoryNumbers: List): Result
20 |
21 | suspend fun updateSelectPomodoroCategory(categoryNo: Int): Result
22 | }
23 |
--------------------------------------------------------------------------------
/data/src/main/java/com/pomonyang/mohanyang/data/remote/di/ClientQualifier.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.data.remote.di
2 |
3 | import javax.inject.Qualifier
4 |
5 | @Qualifier
6 | @Retention(AnnotationRetention.BINARY)
7 | annotation class TokenClient
8 |
9 | @Qualifier
10 | @Retention(AnnotationRetention.BINARY)
11 | annotation class DefaultClient
12 |
13 | @Qualifier
14 | @Retention(AnnotationRetention.BINARY)
15 | annotation class TokenApi
16 |
17 | @Qualifier
18 | @Retention(AnnotationRetention.BINARY)
19 | annotation class DefaultApi
20 |
--------------------------------------------------------------------------------
/data/src/main/java/com/pomonyang/mohanyang/data/remote/di/RemoteDataSourceModule.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.data.remote.di
2 |
3 | import com.pomonyang.mohanyang.data.remote.datasource.auth.AuthRemoteDataSource
4 | import com.pomonyang.mohanyang.data.remote.datasource.auth.AuthRemoteDataSourceImpl
5 | import com.pomonyang.mohanyang.data.remote.datasource.pomodoro.PomodoroSettingRemoteDataSource
6 | import com.pomonyang.mohanyang.data.remote.datasource.pomodoro.PomodoroSettingRemoteDataSourceImpl
7 | import dagger.Binds
8 | import dagger.Module
9 | import dagger.hilt.InstallIn
10 | import dagger.hilt.components.SingletonComponent
11 | import javax.inject.Singleton
12 |
13 | @Module
14 | @InstallIn(SingletonComponent::class)
15 | internal abstract class RemoteDataSourceModule {
16 | @Binds
17 | @Singleton
18 | abstract fun provideAuthDataSource(authDataSourceImpl: AuthRemoteDataSourceImpl): AuthRemoteDataSource
19 |
20 | @Binds
21 | @Singleton
22 | abstract fun providePomodoroSettingDataSource(
23 | pomodoroSettingRemoteDataSourceImpl: PomodoroSettingRemoteDataSourceImpl,
24 | ): PomodoroSettingRemoteDataSource
25 | }
26 |
--------------------------------------------------------------------------------
/data/src/main/java/com/pomonyang/mohanyang/data/remote/model/request/AddCategoryRequest.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.data.remote.model.request
2 |
3 | import kotlinx.serialization.Serializable
4 |
5 | @Serializable
6 | data class AddCategoryRequest(
7 | val title: String,
8 | val iconType: String,
9 | )
10 |
--------------------------------------------------------------------------------
/data/src/main/java/com/pomonyang/mohanyang/data/remote/model/request/DeleteCategoryRequest.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.data.remote.model.request
2 |
3 | import kotlinx.serialization.Serializable
4 |
5 | @Serializable
6 | data class DeleteCategoryRequest(
7 | val no: List,
8 | )
9 |
--------------------------------------------------------------------------------
/data/src/main/java/com/pomonyang/mohanyang/data/remote/model/request/PomodoroTimerRequest.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.data.remote.model.request
2 |
3 | import kotlinx.serialization.Serializable
4 |
5 | @Serializable
6 | data class PomodoroTimerRequest(
7 | val clientFocusTimeId: String,
8 | val categoryNo: Int,
9 | val focusedTime: String,
10 | val restedTime: String,
11 | val doneAt: String,
12 | )
13 |
--------------------------------------------------------------------------------
/data/src/main/java/com/pomonyang/mohanyang/data/remote/model/request/RefreshTokenRequest.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.data.remote.model.request
2 |
3 | import kotlinx.serialization.Serializable
4 |
5 | @Serializable
6 | data class RefreshTokenRequest(val refreshToken: String)
7 |
--------------------------------------------------------------------------------
/data/src/main/java/com/pomonyang/mohanyang/data/remote/model/request/RegisterPushTokenRequest.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.data.remote.model.request
2 |
3 | import kotlinx.serialization.Serializable
4 |
5 | @Serializable
6 | data class RegisterPushTokenRequest(
7 | val deviceToken: String,
8 | val deviceType: String,
9 | )
10 |
--------------------------------------------------------------------------------
/data/src/main/java/com/pomonyang/mohanyang/data/remote/model/request/TokenRequest.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.data.remote.model.request
2 |
3 | import kotlinx.serialization.Serializable
4 |
5 | @Serializable
6 | data class TokenRequest(
7 | val deviceId: String,
8 | )
9 |
--------------------------------------------------------------------------------
/data/src/main/java/com/pomonyang/mohanyang/data/remote/model/request/UpdateCatInfoRequest.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.data.remote.model.request
2 |
3 | import kotlinx.serialization.Serializable
4 |
5 | @Serializable
6 | data class UpdateCatInfoRequest(
7 | val name: String,
8 | )
9 |
--------------------------------------------------------------------------------
/data/src/main/java/com/pomonyang/mohanyang/data/remote/model/request/UpdateCatTypeRequest.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.data.remote.model.request
2 |
3 | import kotlinx.serialization.Serializable
4 |
5 | @Serializable
6 | data class UpdateCatTypeRequest(
7 | val catNo: Int,
8 | )
9 |
--------------------------------------------------------------------------------
/data/src/main/java/com/pomonyang/mohanyang/data/remote/model/request/UpdateCategoryInfoRequest.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.data.remote.model.request
2 |
3 | import kotlinx.serialization.Serializable
4 |
5 | @Serializable
6 | data class UpdateCategoryInfoRequest(
7 | val title: String? = null,
8 | val iconType: String? = null,
9 | val focusTime: String? = null,
10 | val restTime: String? = null,
11 | )
12 |
--------------------------------------------------------------------------------
/data/src/main/java/com/pomonyang/mohanyang/data/remote/model/response/CatTypeResponse.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.data.remote.model.response
2 |
3 | import kotlinx.serialization.Serializable
4 |
5 | @Serializable
6 | data class CatTypeResponse(
7 | val no: Int = -1,
8 | val name: String = "",
9 | val type: String = "",
10 | )
11 |
--------------------------------------------------------------------------------
/data/src/main/java/com/pomonyang/mohanyang/data/remote/model/response/ErrorResponse.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.data.remote.model.response
2 |
3 | import kotlinx.serialization.SerialName
4 | import kotlinx.serialization.Serializable
5 |
6 | @Serializable
7 | data class ErrorResponse(
8 | @SerialName("type") val type: String,
9 | @SerialName("message") val message: String,
10 | @SerialName("errorTraceId") val errorTraceId: String,
11 | )
12 |
--------------------------------------------------------------------------------
/data/src/main/java/com/pomonyang/mohanyang/data/remote/model/response/PomodoroSettingResponse.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.data.remote.model.response
2 |
3 | import com.pomonyang.mohanyang.data.local.room.enitity.PomodoroSettingEntity
4 | import kotlinx.serialization.Serializable
5 |
6 | @Serializable
7 | data class PomodoroSettingResponse(
8 | val no: Int = -1,
9 | val title: String = "",
10 | val focusTime: String = "",
11 | val restTime: String = "",
12 | val iconType: String = "",
13 | val isSelected: Boolean = false,
14 | )
15 |
16 | internal fun PomodoroSettingResponse.toEntity() = PomodoroSettingEntity(
17 | categoryNo = no,
18 | title = title,
19 | focusTime = focusTime,
20 | restTime = restTime,
21 | iconType = iconType,
22 | isSelected = isSelected,
23 | )
24 |
--------------------------------------------------------------------------------
/data/src/main/java/com/pomonyang/mohanyang/data/remote/model/response/TokenResponse.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.data.remote.model.response
2 |
3 | import kotlinx.serialization.Serializable
4 |
5 | @Serializable
6 | data class TokenResponse(
7 | val accessToken: String = "",
8 | val accessTokenExpiredAt: String = "",
9 | val refreshToken: String = "",
10 | val refreshTokenExpiredAt: String = "",
11 | )
12 |
--------------------------------------------------------------------------------
/data/src/main/java/com/pomonyang/mohanyang/data/remote/model/response/UserInfoResponse.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.data.remote.model.response
2 |
3 | import kotlinx.serialization.Serializable
4 |
5 | @Serializable
6 | data class UserInfoResponse(
7 | val registeredDeviceNo: Int = -1,
8 | val isPushEnabled: Boolean = false,
9 | val cat: CatTypeResponse = CatTypeResponse(),
10 | ) {
11 | fun isNewUser(): Boolean {
12 | /* 서버에서 fetch 한 기본 고양이 id가 -1 인 경우 신규 유저로 판단 */
13 | return this.cat.no == NEW_USER_VALIDATION_ID
14 | }
15 |
16 | companion object {
17 | const val NEW_USER_VALIDATION_ID = -1
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/data/src/main/java/com/pomonyang/mohanyang/data/remote/service/AuthService.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.data.remote.service
2 |
3 | import com.pomonyang.mohanyang.data.remote.model.request.RefreshTokenRequest
4 | import com.pomonyang.mohanyang.data.remote.model.request.TokenRequest
5 | import com.pomonyang.mohanyang.data.remote.model.response.TokenResponse
6 | import retrofit2.http.Body
7 | import retrofit2.http.POST
8 |
9 | interface AuthService {
10 | @POST("/papi/v1/tokens/refresh")
11 | suspend fun refreshToken(
12 | @Body request: RefreshTokenRequest,
13 | ): Result
14 |
15 | @POST("/papi/v1/tokens")
16 | suspend fun getTokenByDeviceId(
17 | @Body request: TokenRequest,
18 | ): Result
19 | }
20 |
--------------------------------------------------------------------------------
/data/src/main/java/com/pomonyang/mohanyang/data/remote/util/NetworkException.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.data.remote.util
2 |
3 | import com.pomonyang.mohanyang.data.remote.model.response.ErrorResponse
4 |
5 | class InternalException(errorResponse: ErrorResponse) : Exception(errorResponse.message)
6 | class BadRequestException(errorResponse: ErrorResponse) : Exception(errorResponse.message)
7 | class ForbiddenException(errorResponse: ErrorResponse) : Exception(errorResponse.message)
8 |
--------------------------------------------------------------------------------
/data/src/main/java/com/pomonyang/mohanyang/data/remote/util/NetworkResultCallAdapter.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.data.remote.util
2 |
3 | import java.lang.reflect.Type
4 | import retrofit2.Call
5 | import retrofit2.CallAdapter
6 |
7 | internal class NetworkResultCallAdapter(
8 | private val responseType: Type,
9 | ) : CallAdapter>> {
10 | override fun responseType(): Type = responseType
11 |
12 | override fun adapt(call: Call): Call> = NetworkResultCall(call)
13 | }
14 |
--------------------------------------------------------------------------------
/data/src/main/java/com/pomonyang/mohanyang/data/remote/util/NetworkResultCallAdapterFactory.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.data.remote.util
2 |
3 | import java.lang.reflect.ParameterizedType
4 | import java.lang.reflect.Type
5 | import retrofit2.Call
6 | import retrofit2.CallAdapter
7 | import retrofit2.Retrofit
8 |
9 | internal class NetworkResultCallAdapterFactory : CallAdapter.Factory() {
10 | override fun get(
11 | returnType: Type,
12 | annotations: Array,
13 | retrofit: Retrofit,
14 | ): CallAdapter<*, *>? {
15 | if (getRawType(returnType) != Call::class.java) {
16 | return null
17 | }
18 |
19 | val wrapperType = getParameterUpperBound(0, returnType as ParameterizedType)
20 | if (getRawType(wrapperType) != Result::class.java) {
21 | return null
22 | }
23 |
24 | val resultType = getParameterUpperBound(0, wrapperType as ParameterizedType)
25 | return NetworkResultCallAdapter(resultType)
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/data/src/main/java/com/pomonyang/mohanyang/data/repository/cat/CatSettingRepository.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.data.repository.cat
2 |
3 | import com.pomonyang.mohanyang.data.remote.model.response.CatTypeResponse
4 |
5 | interface CatSettingRepository {
6 | suspend fun getCatTypes(): Result>
7 | suspend fun updateCatInfo(name: String): Result
8 | suspend fun updateCatType(catNo: Int): Result
9 | }
10 |
--------------------------------------------------------------------------------
/data/src/main/java/com/pomonyang/mohanyang/data/repository/cat/CatSettingRepositoryImpl.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.data.repository.cat
2 |
3 | import com.pomonyang.mohanyang.data.remote.model.request.UpdateCatInfoRequest
4 | import com.pomonyang.mohanyang.data.remote.model.request.UpdateCatTypeRequest
5 | import com.pomonyang.mohanyang.data.remote.service.MohaNyangService
6 | import javax.inject.Inject
7 |
8 | class CatSettingRepositoryImpl @Inject constructor(
9 | private val mohaNyangService: MohaNyangService,
10 | ) : CatSettingRepository {
11 | override suspend fun getCatTypes() = mohaNyangService.getCatTypes()
12 |
13 | override suspend fun updateCatInfo(name: String) = mohaNyangService.updateCatInfo(UpdateCatInfoRequest(name))
14 |
15 | override suspend fun updateCatType(catNo: Int) = mohaNyangService.updateCatType(UpdateCatTypeRequest(catNo))
16 | }
17 |
--------------------------------------------------------------------------------
/data/src/main/java/com/pomonyang/mohanyang/data/repository/pomodoro/PomodoroSettingRepository.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.data.repository.pomodoro
2 |
3 | import com.pomonyang.mohanyang.data.local.room.enitity.PomodoroSettingEntity
4 | import kotlinx.coroutines.flow.Flow
5 |
6 | interface PomodoroSettingRepository {
7 |
8 | fun getPomodoroSettingList(): Flow>
9 |
10 | fun getSelectedPomodoroSetting(): Flow
11 |
12 | suspend fun fetchPomodoroSettingList()
13 |
14 | suspend fun updatePomodoroCategorySetting(
15 | categoryNo: Int,
16 | title: String? = null,
17 | iconType: String? = null,
18 | focusTime: Int? = null,
19 | restTime: Int? = null,
20 | ): Result
21 |
22 | suspend fun addPomodoroCategory(
23 | title: String,
24 | iconType: String,
25 | ): Result
26 |
27 | suspend fun updateRecentUseCategoryNo(categoryNo: Int)
28 |
29 | suspend fun deleteCategories(categoryNumbers: List): Result
30 | }
31 |
--------------------------------------------------------------------------------
/data/src/main/java/com/pomonyang/mohanyang/data/repository/pomodoro/PomodoroTimerRepository.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.data.repository.pomodoro
2 |
3 | import com.pomonyang.mohanyang.data.local.room.enitity.PomodoroTimerEntity
4 | import kotlinx.coroutines.flow.Flow
5 |
6 | interface PomodoroTimerRepository {
7 | suspend fun insertPomodoroTimerInitData(categoryNo: Int, pomodoroTimerId: String)
8 | suspend fun incrementFocusedTime(pomodoroTimerId: String)
9 | suspend fun incrementRestedTime(pomodoroTimerId: String)
10 | suspend fun updatePomodoroDone(pomodoroTimerId: String)
11 | suspend fun updateRecentPomodoroDone()
12 | suspend fun savePomodoroData(pomodoroTimerId: String)
13 | suspend fun savePomodoroCacheData()
14 | fun getPomodoroTimer(timerId: String): Flow
15 | }
16 |
--------------------------------------------------------------------------------
/data/src/main/java/com/pomonyang/mohanyang/data/repository/push/PushAlarmRepository.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.data.repository.push
2 |
3 | interface PushAlarmRepository {
4 | suspend fun saveFcmToken(fcmToken: String)
5 | suspend fun getFcmToken(): String
6 | suspend fun registerPushToken(fcmToken: String): Result
7 | suspend fun unRegisterPushToken(): Result
8 | suspend fun subscribeNotification(): Result
9 | suspend fun unSubscribeNotification(): Result
10 | suspend fun setInterruptNotification(isEnabled: Boolean)
11 | suspend fun isInterruptNotificationEnabled(): Boolean
12 | suspend fun setTimerNotification(isEnabled: Boolean)
13 | suspend fun isTimerNotificationEnabled(): Boolean
14 | suspend fun isLockScreenNotificationEnabled(): Boolean
15 | suspend fun setLockScreenNotification(isEnabled: Boolean)
16 | }
17 |
--------------------------------------------------------------------------------
/data/src/main/java/com/pomonyang/mohanyang/data/repository/statistics/StatisticsRepository.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.data.repository.statistics
2 |
3 | import com.pomonyang.mohanyang.data.remote.model.response.StatisticsResponse
4 | import java.time.LocalDate
5 |
6 | interface StatisticsRepository {
7 | suspend fun getStatistics(date: LocalDate): Result
8 | }
9 |
--------------------------------------------------------------------------------
/data/src/main/java/com/pomonyang/mohanyang/data/repository/statistics/StatisticsRepositoryImpl.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.data.repository.statistics
2 |
3 | import com.pomonyang.mohanyang.data.remote.model.response.StatisticsResponse
4 | import com.pomonyang.mohanyang.data.remote.service.MohaNyangService
5 | import java.time.LocalDate
6 | import java.time.format.DateTimeFormatter
7 | import javax.inject.Inject
8 |
9 | internal class StatisticsRepositoryImpl @Inject constructor(
10 | private val mohaNyangService: MohaNyangService,
11 | ) : StatisticsRepository {
12 |
13 | override suspend fun getStatistics(date: LocalDate): Result = runCatching {
14 | mohaNyangService.getStatistics(date.format(DateTimeFormatter.ISO_DATE))
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/data/src/main/java/com/pomonyang/mohanyang/data/repository/user/UserRepository.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.data.repository.user
2 |
3 | import com.pomonyang.mohanyang.data.remote.model.response.TokenResponse
4 | import com.pomonyang.mohanyang.data.remote.model.response.UserInfoResponse
5 |
6 | interface UserRepository {
7 | suspend fun getDeviceId(): String
8 | fun isNewUser(): Boolean
9 | suspend fun login(deviceId: String): Result
10 | suspend fun saveToken(accessToken: String, refreshToken: String)
11 | suspend fun fetchMyInfo(): Result
12 | suspend fun getMyInfo(): UserInfoResponse
13 | }
14 |
--------------------------------------------------------------------------------
/data/src/main/java/com/pomonyang/mohanyang/data/repository/user/UserRepositoryImpl.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.data.repository.user
2 |
3 | import com.pomonyang.mohanyang.data.local.datastore.datasource.deviceid.DeviceIdLocalDataSource
4 | import com.pomonyang.mohanyang.data.local.datastore.datasource.token.TokenLocalDataSource
5 | import com.pomonyang.mohanyang.data.local.datastore.datasource.user.UserLocalDataSource
6 | import com.pomonyang.mohanyang.data.remote.model.request.TokenRequest
7 | import com.pomonyang.mohanyang.data.remote.model.response.UserInfoResponse
8 | import com.pomonyang.mohanyang.data.remote.service.AuthService
9 | import com.pomonyang.mohanyang.data.remote.service.MohaNyangService
10 | import javax.inject.Inject
11 | import kotlinx.coroutines.runBlocking
12 |
13 | internal class UserRepositoryImpl @Inject constructor(
14 | private val deviceLocalDataStore: DeviceIdLocalDataSource,
15 | private val tokenLocalDataSource: TokenLocalDataSource,
16 | private val userLocalDataSource: UserLocalDataSource,
17 | private val mohaNyangService: MohaNyangService,
18 | private val authService: AuthService,
19 | ) : UserRepository {
20 | override suspend fun getDeviceId() = deviceLocalDataStore.getDeviceId()
21 |
22 | override fun isNewUser(): Boolean = runBlocking { tokenLocalDataSource.getAccessToken().isEmpty() || userLocalDataSource.getUserInfo().isNewUser() }
23 |
24 | override suspend fun login(deviceId: String) = authService.getTokenByDeviceId(TokenRequest(deviceId))
25 |
26 | override suspend fun saveToken(accessToken: String, refreshToken: String) {
27 | tokenLocalDataSource.saveAccessToken(accessToken)
28 | tokenLocalDataSource.saveRefreshToken(refreshToken)
29 | }
30 |
31 | override suspend fun fetchMyInfo(): Result = mohaNyangService.getMyInfo().onSuccess { userLocalDataSource.saveUserInfo(it) }
32 |
33 | override suspend fun getMyInfo() = userLocalDataSource.getUserInfo()
34 | }
35 |
--------------------------------------------------------------------------------
/data/src/main/java/com/pomonyang/mohanyang/data/repository/util/TimerUtil.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.data.repository.util
2 |
3 | import java.time.Instant
4 | import java.time.format.DateTimeFormatter
5 |
6 | internal fun getCurrentIsoInstant(): String = DateTimeFormatter.ISO_INSTANT.format(Instant.now())
7 |
--------------------------------------------------------------------------------
/domain/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/domain/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("mohanyang.android.library")
3 | }
4 |
5 | android {
6 | namespace = "com.mohanyang.domain"
7 | }
8 |
9 | dependencies {
10 | implementation(projects.data)
11 | implementation(libs.kotlin.coroutine.core)
12 | implementation(libs.javax.inject)
13 | implementation(libs.androidx.compose.runtime)
14 | }
15 |
--------------------------------------------------------------------------------
/domain/consumer-rules.pro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/PomoNyang-Android/b0d194d192747a7fe1abc9a5d9bffea357610a7f/domain/consumer-rules.pro
--------------------------------------------------------------------------------
/domain/src/main/java/com/pomonyang/mohanyang/domain/usecase/AdjustPomodoroTimeUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.domain.usecase
2 |
3 | import com.pomonyang.mohanyang.data.repository.pomodoro.PomodoroSettingRepository
4 | import java.time.Duration
5 | import javax.inject.Inject
6 | import kotlinx.coroutines.flow.first
7 |
8 | class AdjustPomodoroTimeUseCase @Inject constructor(
9 | private val pomodoroSettingRepository: PomodoroSettingRepository,
10 | private val getSelectedPomodoroSettingUseCase: GetSelectedPomodoroSettingUseCase,
11 | ) {
12 |
13 | suspend operator fun invoke(isFocusTime: Boolean, isIncrease: Boolean) {
14 | val selectedPomodoroSetting = getSelectedPomodoroSettingUseCase().first()
15 | val adjustment = if (isIncrease) ADJUST_TIME_UNIT else -ADJUST_TIME_UNIT
16 | val focusTime = Duration.parse(selectedPomodoroSetting.focusTime).toMinutes()
17 | val restTime = Duration.parse(selectedPomodoroSetting.restTime).toMinutes()
18 |
19 | val updatedFocusTime = if (isFocusTime) focusTime + adjustment else focusTime
20 | val updatedRestTime = if (!isFocusTime) restTime + adjustment else restTime
21 |
22 | pomodoroSettingRepository.updatePomodoroCategorySetting(
23 | categoryNo = selectedPomodoroSetting.categoryNo,
24 | focusTime = updatedFocusTime.toInt(),
25 | restTime = updatedRestTime.toInt(),
26 | )
27 | }
28 |
29 | companion object {
30 | private const val ADJUST_TIME_UNIT = 5
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/domain/src/main/java/com/pomonyang/mohanyang/domain/usecase/GetSelectedPomodoroSettingUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.domain.usecase
2 |
3 | import com.pomonyang.mohanyang.data.local.room.enitity.PomodoroSettingEntity
4 | import com.pomonyang.mohanyang.data.repository.pomodoro.PomodoroSettingRepository
5 | import javax.inject.Inject
6 | import kotlinx.coroutines.flow.Flow
7 | import kotlinx.coroutines.flow.filterNotNull
8 |
9 | class GetSelectedPomodoroSettingUseCase @Inject constructor(
10 | private val pomodoroSettingRepository: PomodoroSettingRepository,
11 | ) {
12 |
13 | operator fun invoke(): Flow {
14 | val settingListFlow: Flow = pomodoroSettingRepository.getSelectedPomodoroSetting().filterNotNull()
15 | return settingListFlow
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/domain/src/main/java/com/pomonyang/mohanyang/domain/usecase/GetTokenByDeviceIdUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.domain.usecase
2 |
3 | import com.pomonyang.mohanyang.data.remote.model.response.TokenResponse
4 | import com.pomonyang.mohanyang.data.repository.user.UserRepository
5 | import javax.inject.Inject
6 |
7 | class GetTokenByDeviceIdUseCase @Inject constructor(
8 | private val userRepository: UserRepository,
9 | ) {
10 | suspend operator fun invoke(): Result {
11 | val deviceId = userRepository.getDeviceId()
12 | return userRepository.login(deviceId).onSuccess {
13 | userRepository.saveToken(
14 | accessToken = it.accessToken,
15 | refreshToken = it.refreshToken,
16 | )
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/domain/src/main/java/com/pomonyang/mohanyang/domain/usecase/InsertPomodoroInitialDataUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.domain.usecase
2 |
3 | import com.pomonyang.mohanyang.data.repository.pomodoro.PomodoroTimerRepository
4 | import javax.inject.Inject
5 | import kotlinx.coroutines.flow.first
6 |
7 | class InsertPomodoroInitialDataUseCase @Inject constructor(
8 | private val pomodoroTimerRepository: PomodoroTimerRepository,
9 | private val getSelectedPomodoroSettingUseCase: GetSelectedPomodoroSettingUseCase,
10 | ) {
11 |
12 | suspend operator fun invoke(focusTimeId: String) {
13 | val selectedPomodoroSetting = getSelectedPomodoroSettingUseCase().first().categoryNo
14 | pomodoroTimerRepository.insertPomodoroTimerInitData(selectedPomodoroSetting, focusTimeId)
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx4g -Dfile.encoding=UTF-8
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. For more details, visit
12 | # https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app's APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Kotlin code style for this project: "official" or "obsolete":
19 | kotlin.code.style=official
20 | # Enables namespacing of each library's R class so that its R class includes only the
21 | # resources declared in the library itself and none from the library's dependencies,
22 | # thereby reducing the size of the R class for that library
23 | android.nonTransitiveRClass=true
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/PomoNyang-Android/b0d194d192747a7fe1abc9a5d9bffea357610a7f/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Sat Jul 13 21:25:49 KST 2024
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
5 | zipStoreBase=GRADLE_USER_HOME
6 | zipStorePath=wrapper/dists
7 |
--------------------------------------------------------------------------------
/presentation/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/presentation/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("mohanyang.android.library")
3 | id("mohanyang.android.hilt")
4 | id("mohanyang.android.library.compose")
5 | id("mohanyang.appversion")
6 | }
7 |
8 | android {
9 | namespace = "com.mohanyang.presentation"
10 | defaultConfig {
11 | buildConfigField("String", "APP_VERSION", "\"${appVersion.name}\"")
12 | }
13 | }
14 |
15 | dependencies {
16 | implementation(libs.material)
17 | implementation(libs.dagger.hilt.android)
18 | implementation(libs.bundles.androidx.compose.navigation)
19 | implementation(libs.permission)
20 | implementation(libs.rive)
21 | implementation(libs.lottie.compose)
22 | implementation(platform(libs.firebase.bom))
23 | implementation(libs.firebase.analytics)
24 |
25 | // module impl
26 | implementation(projects.domain)
27 | implementation(projects.data)
28 | }
29 |
--------------------------------------------------------------------------------
/presentation/consumer-rules.pro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/PomoNyang-Android/b0d194d192747a7fe1abc9a5d9bffea357610a7f/presentation/consumer-rules.pro
--------------------------------------------------------------------------------
/presentation/src/main/java/com/pomonyang/mohanyang/presentation/base/BaseViewElements.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.presentation.base
2 |
3 | interface ViewState
4 |
5 | interface ViewEvent
6 |
7 | interface ViewSideEffect
8 |
9 | open class NetworkViewState(
10 | open val isLoading: Boolean = false,
11 | open val isInternalError: Boolean = false,
12 | open val isInvalidError: Boolean = false,
13 | open val lastRequestAction: ViewEvent? = null,
14 | ) : ViewState
15 |
--------------------------------------------------------------------------------
/presentation/src/main/java/com/pomonyang/mohanyang/presentation/base/BaseViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.presentation.base
2 |
3 | import androidx.annotation.CallSuper
4 | import androidx.lifecycle.ViewModel
5 | import androidx.lifecycle.viewModelScope
6 | import kotlinx.coroutines.channels.Channel
7 | import kotlinx.coroutines.flow.Flow
8 | import kotlinx.coroutines.flow.MutableStateFlow
9 | import kotlinx.coroutines.flow.asStateFlow
10 | import kotlinx.coroutines.flow.receiveAsFlow
11 | import kotlinx.coroutines.flow.update
12 | import kotlinx.coroutines.launch
13 |
14 | abstract class BaseViewModel : ViewModel() {
15 | // UI의 초기 상태를 설정
16 | abstract fun setInitialState(): STATE
17 |
18 | abstract fun handleEvent(event: EVENT)
19 |
20 | // 초기 상태를 지연 초기화
21 | private val initialState: STATE by lazy { setInitialState() }
22 |
23 | private val _state = MutableStateFlow(initialState)
24 | val state = _state.asStateFlow()
25 |
26 | private val _effects: Channel = Channel(Channel.BUFFERED)
27 | val effects: Flow = _effects.receiveAsFlow()
28 |
29 | protected fun updateState(reducer: STATE.() -> STATE) {
30 | _state.update { it.reducer() }
31 | }
32 |
33 | // Intent -> Model -> View 의 사이클을 벗어난 비동기 작업이 완료된 후 UI 상태 변경 외의 작업을 수행할 때 사용
34 | protected fun setEffect(vararg builder: EFFECT) {
35 | viewModelScope.launch {
36 | for (effectValue in builder) {
37 | _effects.send(effectValue)
38 | }
39 | }
40 | }
41 |
42 | // viewmodel이 destroy 될 때 추가적인 작업이 필요하다면
43 | protected open fun onDestroy() {}
44 |
45 | @CallSuper
46 | override fun onCleared() {
47 | super.onCleared()
48 | onDestroy()
49 | _effects.close()
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/presentation/src/main/java/com/pomonyang/mohanyang/presentation/component/TimerType.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.presentation.component
2 |
3 | import androidx.compose.foundation.layout.Row
4 | import androidx.compose.foundation.layout.padding
5 | import androidx.compose.material3.Text
6 | import androidx.compose.runtime.Composable
7 | import androidx.compose.ui.Alignment
8 | import androidx.compose.ui.Modifier
9 | import androidx.compose.ui.graphics.Color
10 | import androidx.compose.ui.tooling.preview.Preview
11 | import com.mohanyang.presentation.R
12 | import com.pomonyang.mohanyang.presentation.designsystem.icon.MnSmallIcon
13 | import com.pomonyang.mohanyang.presentation.designsystem.token.MnSpacing
14 | import com.pomonyang.mohanyang.presentation.theme.MnTheme
15 |
16 | @Composable
17 | fun TimerType(
18 | modifier: Modifier = Modifier,
19 | type: String,
20 | iconRes: Int = R.drawable.ic_null,
21 | ) {
22 | Row(
23 | modifier = modifier.padding(top = MnSpacing.xLarge),
24 | verticalAlignment = Alignment.CenterVertically,
25 | ) {
26 | MnSmallIcon(resourceId = iconRes, tint = Color.Unspecified)
27 | Text(
28 | modifier = Modifier.padding(MnSpacing.xSmall),
29 | text = type,
30 | style = MnTheme.typography.header5,
31 | color = MnTheme.textColorScheme.secondary,
32 | )
33 | }
34 | }
35 |
36 | @Preview
37 | @Composable
38 | private fun TimerTypePreview() {
39 | MnTheme {
40 | TimerType(type = "집중시간")
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/presentation/src/main/java/com/pomonyang/mohanyang/presentation/designsystem/bottomsheet/MnBottomSheetDefaults.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.presentation.designsystem.bottomsheet
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.Immutable
5 | import androidx.compose.ui.graphics.Color
6 | import androidx.compose.ui.text.TextStyle
7 | import com.pomonyang.mohanyang.presentation.designsystem.token.MnColor
8 | import com.pomonyang.mohanyang.presentation.theme.MnTheme
9 |
10 | object MnBottomSheetDefaults {
11 | @Composable
12 | fun colors(
13 | containerColor: Color = MnColor.White,
14 | titleColor: Color = MnTheme.textColorScheme.primary,
15 | subTitleColor: Color = MnTheme.textColorScheme.secondary,
16 | ) = MnBottomSheetColors(
17 | containerColor = containerColor,
18 | titleColor = titleColor,
19 | subTitleColor = subTitleColor,
20 | )
21 |
22 | @Composable
23 | fun textStyles(
24 | titleTextStyle: TextStyle = MnTheme.typography.header3,
25 | subTitleTextStyle: TextStyle = MnTheme.typography.bodyRegular,
26 | ) = MnBottomSheetTextStyles(
27 | titleTextStyle = titleTextStyle,
28 | subTitleTextStyle = subTitleTextStyle,
29 | )
30 | }
31 |
32 | @Immutable
33 | data class MnBottomSheetColors(
34 | val containerColor: Color,
35 | val titleColor: Color,
36 | val subTitleColor: Color,
37 | )
38 |
39 | @Immutable
40 | data class MnBottomSheetTextStyles(
41 | val titleTextStyle: TextStyle,
42 | val subTitleTextStyle: TextStyle,
43 | )
44 |
--------------------------------------------------------------------------------
/presentation/src/main/java/com/pomonyang/mohanyang/presentation/designsystem/button/box/MnBoxButtonColorType.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.presentation.designsystem.button.box
2 |
3 | import androidx.compose.runtime.Composable
4 | import com.pomonyang.mohanyang.presentation.theme.MnTheme
5 |
6 | object MnBoxButtonColorType {
7 | val primary: MnBoxButtonColors
8 | @Composable
9 | get() = MnBoxButtonColors(
10 | containerColor = MnTheme.backgroundColorScheme.accent1,
11 | contentColor = MnTheme.textColorScheme.inverse,
12 | iconColor = MnTheme.iconColorScheme.inverse,
13 | disabledContainerColor = MnTheme.backgroundColorScheme.secondary,
14 | disabledContentColor = MnTheme.textColorScheme.disabled,
15 | disabledIconColor = MnTheme.iconColorScheme.disabled,
16 | )
17 |
18 | val secondary: MnBoxButtonColors
19 | @Composable
20 | get() = MnBoxButtonColors(
21 | containerColor = MnTheme.backgroundColorScheme.inverse,
22 | contentColor = MnTheme.textColorScheme.inverse,
23 | iconColor = MnTheme.iconColorScheme.inverse,
24 | disabledContainerColor = MnTheme.backgroundColorScheme.inverse,
25 | disabledContentColor = MnTheme.textColorScheme.inverse,
26 | disabledIconColor = MnTheme.iconColorScheme.inverse,
27 | )
28 |
29 | val tertiary: MnBoxButtonColors
30 | @Composable
31 | get() = MnBoxButtonColors(
32 | containerColor = MnTheme.backgroundColorScheme.secondary,
33 | contentColor = MnTheme.textColorScheme.tertiary,
34 | iconColor = MnTheme.iconColorScheme.tertiary,
35 | disabledContainerColor = MnTheme.backgroundColorScheme.secondary,
36 | disabledContentColor = MnTheme.textColorScheme.tertiary,
37 | disabledIconColor = MnTheme.iconColorScheme.tertiary,
38 | )
39 | }
40 |
--------------------------------------------------------------------------------
/presentation/src/main/java/com/pomonyang/mohanyang/presentation/designsystem/button/box/MnBoxButtonColors.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.presentation.designsystem.button.box
2 |
3 | import androidx.compose.runtime.Immutable
4 | import androidx.compose.ui.graphics.Color
5 |
6 | @Immutable
7 | data class MnBoxButtonColors(
8 | val containerColor: Color,
9 | val contentColor: Color,
10 | val iconColor: Color,
11 | val disabledContainerColor: Color,
12 | val disabledContentColor: Color,
13 | val disabledIconColor: Color,
14 | )
15 |
--------------------------------------------------------------------------------
/presentation/src/main/java/com/pomonyang/mohanyang/presentation/designsystem/button/round/MnRoundButtonColorType.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.presentation.designsystem.button.round
2 |
3 | import androidx.compose.runtime.Composable
4 | import com.pomonyang.mohanyang.presentation.theme.MnTheme
5 |
6 | object MnRoundButtonColorType {
7 | val primary: MnRoundButtonColors
8 | @Composable
9 | get() = MnRoundButtonColors(
10 | containerColor = MnTheme.backgroundColorScheme.accent1,
11 | iconColor = MnTheme.iconColorScheme.inverse,
12 | )
13 | val secondary: MnRoundButtonColors
14 | @Composable
15 | get() = MnRoundButtonColors(
16 | containerColor = MnTheme.backgroundColorScheme.inverse,
17 | iconColor = MnTheme.iconColorScheme.tertiary,
18 | )
19 | }
20 |
--------------------------------------------------------------------------------
/presentation/src/main/java/com/pomonyang/mohanyang/presentation/designsystem/button/round/MnRoundButtonColors.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.presentation.designsystem.button.round
2 |
3 | import androidx.compose.runtime.Immutable
4 | import androidx.compose.ui.graphics.Color
5 |
6 | @Immutable
7 | data class MnRoundButtonColors(
8 | val containerColor: Color,
9 | val iconColor: Color,
10 | )
11 |
--------------------------------------------------------------------------------
/presentation/src/main/java/com/pomonyang/mohanyang/presentation/designsystem/button/select/MnSelectListDefaults.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.presentation.designsystem.button.select
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.Immutable
5 | import androidx.compose.ui.graphics.Color
6 | import com.pomonyang.mohanyang.presentation.theme.MnTheme
7 |
8 | @Immutable
9 | data class MnSelectListColors(
10 | val enabledTextColor: Color,
11 | val disabledTextColor: Color,
12 | val enabledIconTint: Color,
13 | val disabledIconTint: Color,
14 | )
15 |
16 | object MnSelectListDefaults {
17 |
18 | @Composable
19 | fun colors(
20 | enabledTextColor: Color = MnTheme.textColorScheme.primary,
21 | disabledTextColor: Color = MnTheme.textColorScheme.disabled,
22 | enabledIconTint: Color = Color.Unspecified,
23 | disabledIconTint: Color = MnTheme.textColorScheme.disabled,
24 | ) = MnSelectListColors(
25 | enabledTextColor = enabledTextColor,
26 | disabledTextColor = disabledTextColor,
27 | enabledIconTint = enabledIconTint,
28 | disabledIconTint = disabledIconTint,
29 | )
30 | }
31 |
--------------------------------------------------------------------------------
/presentation/src/main/java/com/pomonyang/mohanyang/presentation/designsystem/button/toggle/MnToggleButtonSize.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.presentation.designsystem.button.toggle
2 |
3 | import androidx.compose.ui.unit.dp
4 |
5 | object MnToggleButtonSize {
6 | val width = 48.dp
7 | val height = 28.dp
8 | val thumbSize = 22.dp
9 | val padding = (height - thumbSize) / 2
10 | }
11 |
--------------------------------------------------------------------------------
/presentation/src/main/java/com/pomonyang/mohanyang/presentation/designsystem/icon/MnLargeIcon.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.presentation.designsystem.icon
2 |
3 | import androidx.annotation.DrawableRes
4 | import androidx.compose.foundation.layout.size
5 | import androidx.compose.material3.Icon
6 | import androidx.compose.material3.LocalContentColor
7 | import androidx.compose.runtime.Composable
8 | import androidx.compose.ui.Modifier
9 | import androidx.compose.ui.graphics.Color
10 | import androidx.compose.ui.graphics.vector.ImageVector
11 | import androidx.compose.ui.res.painterResource
12 | import com.pomonyang.mohanyang.presentation.designsystem.token.MnIconSize
13 |
14 | @Composable
15 | fun MnLargeIcon(
16 | @DrawableRes resourceId: Int,
17 | modifier: Modifier = Modifier,
18 | contentDescription: String? = null,
19 | tint: Color = LocalContentColor.current,
20 | ) {
21 | Icon(
22 | painter = painterResource(id = resourceId),
23 | contentDescription = contentDescription,
24 | modifier = modifier.size(MnIconSize.large),
25 | tint = tint,
26 | )
27 | }
28 |
29 | @Composable
30 | fun MnLargeIcon(
31 | imageVector: ImageVector,
32 | modifier: Modifier = Modifier,
33 | contentDescription: String? = null,
34 | tint: Color = LocalContentColor.current,
35 | ) {
36 | Icon(
37 | imageVector = imageVector,
38 | contentDescription = contentDescription,
39 | modifier = modifier.size(MnIconSize.large),
40 | tint = tint,
41 | )
42 | }
43 |
--------------------------------------------------------------------------------
/presentation/src/main/java/com/pomonyang/mohanyang/presentation/designsystem/icon/MnMediumIcon.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.presentation.designsystem.icon
2 |
3 | import androidx.annotation.DrawableRes
4 | import androidx.compose.foundation.layout.size
5 | import androidx.compose.material3.Icon
6 | import androidx.compose.material3.LocalContentColor
7 | import androidx.compose.runtime.Composable
8 | import androidx.compose.ui.Modifier
9 | import androidx.compose.ui.graphics.Color
10 | import androidx.compose.ui.graphics.vector.ImageVector
11 | import androidx.compose.ui.res.painterResource
12 | import com.pomonyang.mohanyang.presentation.designsystem.token.MnIconSize
13 |
14 | @Composable
15 | fun MnMediumIcon(
16 | @DrawableRes resourceId: Int,
17 | modifier: Modifier = Modifier,
18 | contentDescription: String? = null,
19 | tint: Color = LocalContentColor.current,
20 | ) {
21 | Icon(
22 | painter = painterResource(id = resourceId),
23 | contentDescription = contentDescription,
24 | modifier = modifier.size(MnIconSize.medium),
25 | tint = tint,
26 | )
27 | }
28 |
29 | @Composable
30 | fun MnMediumIcon(
31 | imageVector: ImageVector,
32 | modifier: Modifier = Modifier,
33 | contentDescription: String? = null,
34 | tint: Color = LocalContentColor.current,
35 | ) {
36 | Icon(
37 | imageVector = imageVector,
38 | contentDescription = contentDescription,
39 | modifier = modifier.size(MnIconSize.medium),
40 | tint = tint,
41 | )
42 | }
43 |
--------------------------------------------------------------------------------
/presentation/src/main/java/com/pomonyang/mohanyang/presentation/designsystem/icon/MnSmallIcon.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.presentation.designsystem.icon
2 |
3 | import androidx.annotation.DrawableRes
4 | import androidx.compose.foundation.layout.size
5 | import androidx.compose.material3.Icon
6 | import androidx.compose.material3.LocalContentColor
7 | import androidx.compose.runtime.Composable
8 | import androidx.compose.ui.Modifier
9 | import androidx.compose.ui.graphics.Color
10 | import androidx.compose.ui.graphics.vector.ImageVector
11 | import androidx.compose.ui.res.painterResource
12 | import com.pomonyang.mohanyang.presentation.designsystem.token.MnIconSize
13 |
14 | @Composable
15 | fun MnSmallIcon(
16 | @DrawableRes resourceId: Int,
17 | modifier: Modifier = Modifier,
18 | contentDescription: String? = null,
19 | tint: Color = LocalContentColor.current,
20 | ) {
21 | Icon(
22 | painter = painterResource(id = resourceId),
23 | contentDescription = contentDescription,
24 | modifier = modifier.size(MnIconSize.small),
25 | tint = tint,
26 | )
27 | }
28 |
29 | @Composable
30 | fun MnSmallIcon(
31 | imageVector: ImageVector,
32 | modifier: Modifier = Modifier,
33 | contentDescription: String? = null,
34 | tint: Color = LocalContentColor.current,
35 | ) {
36 | Icon(
37 | imageVector = imageVector,
38 | contentDescription = contentDescription,
39 | modifier = modifier.size(MnIconSize.small),
40 | tint = tint,
41 | )
42 | }
43 |
--------------------------------------------------------------------------------
/presentation/src/main/java/com/pomonyang/mohanyang/presentation/designsystem/icon/MnXLargeIcon.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.presentation.designsystem.icon
2 |
3 | import androidx.annotation.DrawableRes
4 | import androidx.compose.foundation.layout.size
5 | import androidx.compose.material3.Icon
6 | import androidx.compose.material3.LocalContentColor
7 | import androidx.compose.runtime.Composable
8 | import androidx.compose.ui.Modifier
9 | import androidx.compose.ui.graphics.Color
10 | import androidx.compose.ui.graphics.vector.ImageVector
11 | import androidx.compose.ui.res.painterResource
12 | import com.pomonyang.mohanyang.presentation.designsystem.token.MnIconSize
13 |
14 | @Composable
15 | fun MnXLargeIcon(
16 | @DrawableRes resourceId: Int,
17 | modifier: Modifier = Modifier,
18 | contentDescription: String? = null,
19 | tint: Color = LocalContentColor.current,
20 | ) {
21 | Icon(
22 | painter = painterResource(id = resourceId),
23 | contentDescription = contentDescription,
24 | modifier = modifier.size(MnIconSize.xLarge),
25 | tint = tint,
26 | )
27 | }
28 |
29 | @Composable
30 | fun MnXLargeIcon(
31 | imageVector: ImageVector,
32 | modifier: Modifier = Modifier,
33 | contentDescription: String? = null,
34 | tint: Color = LocalContentColor.current,
35 | ) {
36 | Icon(
37 | imageVector = imageVector,
38 | contentDescription = contentDescription,
39 | modifier = modifier.size(MnIconSize.xLarge),
40 | tint = tint,
41 | )
42 | }
43 |
--------------------------------------------------------------------------------
/presentation/src/main/java/com/pomonyang/mohanyang/presentation/designsystem/icon/MnXSmallIcon.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.presentation.designsystem.icon
2 |
3 | import androidx.annotation.DrawableRes
4 | import androidx.compose.foundation.layout.size
5 | import androidx.compose.material3.Icon
6 | import androidx.compose.material3.LocalContentColor
7 | import androidx.compose.runtime.Composable
8 | import androidx.compose.ui.Modifier
9 | import androidx.compose.ui.graphics.Color
10 | import androidx.compose.ui.graphics.vector.ImageVector
11 | import androidx.compose.ui.res.painterResource
12 | import com.pomonyang.mohanyang.presentation.designsystem.token.MnIconSize
13 |
14 | @Composable
15 | fun MnXSmallIcon(
16 | @DrawableRes resourceId: Int,
17 | modifier: Modifier = Modifier,
18 | contentDescription: String? = null,
19 | tint: Color = LocalContentColor.current,
20 | ) {
21 | Icon(
22 | painter = painterResource(id = resourceId),
23 | contentDescription = contentDescription,
24 | modifier = modifier.size(MnIconSize.xSmall),
25 | tint = tint,
26 | )
27 | }
28 |
29 | @Composable
30 | fun MnXSmallIcon(
31 | imageVector: ImageVector,
32 | modifier: Modifier = Modifier,
33 | contentDescription: String? = null,
34 | tint: Color = LocalContentColor.current,
35 | ) {
36 | Icon(
37 | imageVector = imageVector,
38 | contentDescription = contentDescription,
39 | modifier = modifier.size(MnIconSize.xSmall),
40 | tint = tint,
41 | )
42 | }
43 |
--------------------------------------------------------------------------------
/presentation/src/main/java/com/pomonyang/mohanyang/presentation/designsystem/picker/MnWheelPickerDefaults.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.presentation.designsystem.picker
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.Stable
5 | import androidx.compose.ui.graphics.Color
6 | import androidx.compose.ui.text.TextStyle
7 | import androidx.compose.ui.unit.Dp
8 | import androidx.compose.ui.unit.dp
9 | import com.pomonyang.mohanyang.presentation.theme.MnTheme
10 |
11 | object MnWheelPickerDefaults {
12 | val itemHeight: Dp = 98.dp
13 |
14 | @Composable
15 | fun colors(
16 | fadeColor: Color = MnTheme.backgroundColorScheme.inverse,
17 | selectedTextColor: Color = MnTheme.textColorScheme.primary,
18 | unSelectedTextColor: Color = MnTheme.textColorScheme.disabled,
19 | ) = MnWheelPickerColor(
20 | fadeColor = fadeColor,
21 | selectedTextColor = selectedTextColor,
22 | unSelectedTextColor = unSelectedTextColor,
23 | )
24 |
25 | @Composable
26 | fun styles() = MnWheelPickerStyles(
27 | selectedTextStyle = MnTheme.typography.header1,
28 | unSelectedTextStyle = MnTheme.typography.header2,
29 | )
30 | }
31 |
32 | @Stable
33 | data class MnWheelPickerColor(
34 | val fadeColor: Color,
35 | val selectedTextColor: Color,
36 | val unSelectedTextColor: Color,
37 | )
38 |
39 | @Stable
40 | data class MnWheelPickerStyles(
41 | val selectedTextStyle: TextStyle,
42 | val unSelectedTextStyle: TextStyle,
43 | )
44 |
--------------------------------------------------------------------------------
/presentation/src/main/java/com/pomonyang/mohanyang/presentation/designsystem/spinner/MnSpinner.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.presentation.designsystem.spinner
2 |
3 | import androidx.compose.foundation.background
4 | import androidx.compose.foundation.layout.Box
5 | import androidx.compose.foundation.layout.size
6 | import androidx.compose.foundation.shape.RoundedCornerShape
7 | import androidx.compose.runtime.Composable
8 | import androidx.compose.runtime.getValue
9 | import androidx.compose.ui.Alignment
10 | import androidx.compose.ui.Modifier
11 | import androidx.compose.ui.draw.alpha
12 | import androidx.compose.ui.unit.dp
13 | import com.airbnb.lottie.compose.LottieAnimation
14 | import com.airbnb.lottie.compose.LottieCompositionSpec
15 | import com.airbnb.lottie.compose.LottieConstants
16 | import com.airbnb.lottie.compose.animateLottieCompositionAsState
17 | import com.airbnb.lottie.compose.rememberLottieComposition
18 | import com.mohanyang.presentation.R
19 | import com.pomonyang.mohanyang.presentation.designsystem.token.MnRadius
20 | import com.pomonyang.mohanyang.presentation.theme.MnTheme
21 |
22 | @Composable
23 | fun MnSpinner(
24 | modifier: Modifier = Modifier,
25 | ) {
26 | Box(
27 | modifier = modifier
28 | .background(MnTheme.backgroundColorScheme.inverse, RoundedCornerShape(MnRadius.small))
29 | .size(82.dp)
30 | .alpha(0.9f),
31 | contentAlignment = Alignment.Center,
32 | ) {
33 | val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(R.raw.spinner))
34 | val progress by animateLottieCompositionAsState(
35 | composition,
36 | iterations = LottieConstants.IterateForever,
37 | )
38 | LottieAnimation(
39 | composition = composition,
40 | progress = { progress },
41 | )
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/presentation/src/main/java/com/pomonyang/mohanyang/presentation/designsystem/token/MnIconSize.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.presentation.designsystem.token
2 |
3 | import androidx.compose.foundation.layout.Arrangement
4 | import androidx.compose.foundation.layout.Column
5 | import androidx.compose.runtime.Composable
6 | import androidx.compose.ui.Alignment
7 | import androidx.compose.ui.tooling.preview.Preview
8 | import androidx.compose.ui.unit.dp
9 | import com.mohanyang.presentation.R
10 | import com.pomonyang.mohanyang.presentation.designsystem.icon.MnLargeIcon
11 | import com.pomonyang.mohanyang.presentation.designsystem.icon.MnMediumIcon
12 | import com.pomonyang.mohanyang.presentation.designsystem.icon.MnSmallIcon
13 | import com.pomonyang.mohanyang.presentation.designsystem.icon.MnXLargeIcon
14 | import com.pomonyang.mohanyang.presentation.designsystem.icon.MnXSmallIcon
15 | import com.pomonyang.mohanyang.presentation.theme.MnTheme
16 | import com.pomonyang.mohanyang.presentation.util.ThemePreviews
17 |
18 | object MnIconSize {
19 | val xSmall = 16.dp
20 | val small = 20.dp
21 | val medium = 24.dp
22 | val large = 32.dp
23 | val xLarge = 48.dp
24 | }
25 |
26 | @ThemePreviews
27 | @Composable
28 | @Preview
29 | private fun MohaNyangIconPreview() {
30 | MnTheme {
31 | Column(
32 | verticalArrangement = Arrangement.spacedBy(5.dp, Alignment.Top),
33 | ) {
34 | MnXSmallIcon(resourceId = R.drawable.ic_null)
35 | MnSmallIcon(resourceId = R.drawable.ic_null)
36 | MnMediumIcon(resourceId = R.drawable.ic_null)
37 | MnLargeIcon(resourceId = R.drawable.ic_null)
38 | MnXLargeIcon(resourceId = R.drawable.ic_null)
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/presentation/src/main/java/com/pomonyang/mohanyang/presentation/designsystem/token/MnInteraction.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.presentation.designsystem.token
2 |
3 | object MnInteraction {
4 | val default = MnColor.White.copy(alpha = 0f)
5 | val hover = MnColor.White.copy(alpha = 0.1f)
6 | val pressed = MnColor.Black.copy(alpha = 0.05f)
7 | }
8 |
--------------------------------------------------------------------------------
/presentation/src/main/java/com/pomonyang/mohanyang/presentation/designsystem/token/MnRadius.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.presentation.designsystem.token
2 |
3 | import androidx.compose.ui.unit.dp
4 |
5 | object MnRadius {
6 | val twoXSmall = 4.dp
7 | val threeXSmall = 8.dp
8 | val xSmall = 12.dp
9 | val small = 16.dp
10 | val medium = 20.dp
11 | val large = 24.dp
12 | val max = 500.dp
13 | }
14 |
--------------------------------------------------------------------------------
/presentation/src/main/java/com/pomonyang/mohanyang/presentation/designsystem/token/MnSpacing.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.presentation.designsystem.token
2 |
3 | import androidx.compose.ui.unit.dp
4 |
5 | object MnSpacing {
6 | val twoXSmall = 2.dp
7 | val xSmall = 4.dp
8 | val small = 8.dp
9 | val medium = 12.dp
10 | val large = 16.dp
11 | val xLarge = 20.dp
12 | val twoXLarge = 24.dp
13 | val threeXLarge = 32.dp
14 | }
15 |
--------------------------------------------------------------------------------
/presentation/src/main/java/com/pomonyang/mohanyang/presentation/designsystem/token/MnStroke.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.presentation.designsystem.token
2 |
3 | import androidx.compose.ui.unit.dp
4 |
5 | object MnStroke {
6 | val small = 0.5.dp
7 | val medium = 1.dp
8 | val large = 2.dp
9 | }
10 |
--------------------------------------------------------------------------------
/presentation/src/main/java/com/pomonyang/mohanyang/presentation/designsystem/tooltip/MnTooltipDefaults.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.presentation.designsystem.tooltip
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.Immutable
5 | import androidx.compose.ui.graphics.Color
6 | import androidx.compose.ui.unit.dp
7 | import com.pomonyang.mohanyang.presentation.designsystem.token.MnColor
8 | import com.pomonyang.mohanyang.presentation.theme.MnTheme
9 |
10 | object MnTooltipDefaults {
11 | val anchorWidth = 14.dp
12 | val anchorHeight = 9.dp
13 | val overlayBackgroundColor = MnColor.Black.copy(alpha = 0.5f)
14 |
15 | @Composable
16 | fun lightTooltipColors(
17 | containerColor: Color = MnColor.White,
18 | contentColor: Color = MnTheme.textColorScheme.secondary,
19 | ) = MnTooltipColors(
20 | containerColor = containerColor,
21 | contentColor = contentColor,
22 | )
23 |
24 | @Composable
25 | fun darkTooltipColors(
26 | containerColor: Color = MnTheme.backgroundColorScheme.inverse,
27 | contentColor: Color = MnTheme.textColorScheme.inverse,
28 | ) = MnTooltipColors(
29 | containerColor = containerColor,
30 | contentColor = contentColor,
31 | )
32 | }
33 |
34 | @Immutable
35 | data class MnTooltipColors(
36 | val containerColor: Color,
37 | val contentColor: Color,
38 | )
39 |
--------------------------------------------------------------------------------
/presentation/src/main/java/com/pomonyang/mohanyang/presentation/designsystem/topappbar/MnTopAppBarDefaults.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.presentation.designsystem.topappbar
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.Immutable
5 | import androidx.compose.ui.graphics.Color
6 | import androidx.compose.ui.unit.dp
7 | import com.pomonyang.mohanyang.presentation.designsystem.token.MnColor
8 | import com.pomonyang.mohanyang.presentation.theme.MnTheme
9 |
10 | object MnTopAppBarDefaults {
11 | val height = 56.dp
12 | val iconHorizontalPadding = 8.dp
13 |
14 | @Composable
15 | fun topAppBarColors(
16 | containerColor: Color = MnColor.Gray50,
17 | navigationIconContentColor: Color = MnTheme.iconColorScheme.primary,
18 | titleContentColor: Color = MnTheme.textColorScheme.primary,
19 | actionIconContentColor: Color = MnTheme.iconColorScheme.primary,
20 | ) = MnAppBarColors(
21 | containerColor = containerColor,
22 | navigationIconContentColor = navigationIconContentColor,
23 | titleContentColor = titleContentColor,
24 | actionIconContentColor = actionIconContentColor,
25 | )
26 | }
27 |
28 | @Immutable
29 | data class MnAppBarColors(
30 | val containerColor: Color,
31 | val navigationIconContentColor: Color,
32 | val titleContentColor: Color,
33 | val actionIconContentColor: Color,
34 | )
35 |
--------------------------------------------------------------------------------
/presentation/src/main/java/com/pomonyang/mohanyang/presentation/di/MohanyangLoggerModule.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.presentation.di
2 |
3 | import com.pomonyang.mohanyang.presentation.util.MohanyangEventLogger
4 | import com.pomonyang.mohanyang.presentation.util.MohanyangEventLoggerImpl
5 | import dagger.Binds
6 | import dagger.Module
7 | import dagger.hilt.InstallIn
8 | import dagger.hilt.components.SingletonComponent
9 | import javax.inject.Singleton
10 |
11 | @Module
12 | @InstallIn(SingletonComponent::class)
13 | internal abstract class MohanyangLoggerModule {
14 |
15 | @Binds
16 | @Singleton
17 | abstract fun provideMohanyangEventLogger(
18 | mohanyangEventLoggerImpl: MohanyangEventLoggerImpl,
19 | ): MohanyangEventLogger
20 | }
21 |
--------------------------------------------------------------------------------
/presentation/src/main/java/com/pomonyang/mohanyang/presentation/di/PomodoroModule.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.presentation.di
2 |
3 | import com.pomonyang.mohanyang.presentation.service.PomodoroTimer
4 | import com.pomonyang.mohanyang.presentation.service.focus.FocusTimer
5 | import com.pomonyang.mohanyang.presentation.service.rest.RestTimer
6 | import dagger.Module
7 | import dagger.Provides
8 | import dagger.hilt.InstallIn
9 | import dagger.hilt.android.components.ServiceComponent
10 |
11 | @Module
12 | @InstallIn(ServiceComponent::class)
13 | internal object PomodoroModule {
14 |
15 | @Provides
16 | @FocusTimerType
17 | fun provideFocusTimer(): PomodoroTimer = FocusTimer()
18 |
19 | @Provides
20 | @RestTimerType
21 | fun provideRestTimer(): PomodoroTimer = RestTimer()
22 | }
23 |
--------------------------------------------------------------------------------
/presentation/src/main/java/com/pomonyang/mohanyang/presentation/di/Qualifier.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.presentation.di
2 |
3 | import javax.inject.Qualifier
4 |
5 | @Qualifier
6 | @Retention(AnnotationRetention.BINARY)
7 | annotation class PomodoroNotification
8 |
9 | @Qualifier
10 | @Retention(AnnotationRetention.BINARY)
11 | annotation class FocusTimerType
12 |
13 | @Qualifier
14 | @Retention(AnnotationRetention.BINARY)
15 | annotation class RestTimerType
16 |
--------------------------------------------------------------------------------
/presentation/src/main/java/com/pomonyang/mohanyang/presentation/model/cat/CatInfoModel.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.presentation.model.cat
2 |
3 | import androidx.compose.runtime.Stable
4 | import com.pomonyang.mohanyang.data.remote.model.response.CatTypeResponse
5 |
6 | @Stable
7 | data class CatInfoModel(
8 | val no: Int,
9 | val name: String,
10 | val type: CatType,
11 | )
12 |
13 | fun CatTypeResponse.toModel(): CatInfoModel = CatInfoModel(
14 | no = no,
15 | name = name,
16 | type = CatType.safeValueOf(type),
17 | )
18 |
--------------------------------------------------------------------------------
/presentation/src/main/java/com/pomonyang/mohanyang/presentation/model/category/PomodoroCategoryModel.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.presentation.model.category
2 |
3 | import androidx.compose.runtime.Immutable
4 | import com.pomonyang.mohanyang.data.local.room.enitity.PomodoroSettingEntity
5 | import com.pomonyang.mohanyang.presentation.screen.home.category.model.CategoryIcon
6 |
7 | @Immutable
8 | data class PomodoroCategoryModel(
9 | val categoryNo: Int,
10 | val title: String,
11 | val categoryIcon: CategoryIcon,
12 | )
13 |
14 | fun PomodoroSettingEntity.toCategoryModel() = PomodoroCategoryModel(
15 | categoryNo = categoryNo,
16 | title = title,
17 | categoryIcon = CategoryIcon.safeValueOf(iconType),
18 | )
19 |
--------------------------------------------------------------------------------
/presentation/src/main/java/com/pomonyang/mohanyang/presentation/model/setting/PomodoroSettingModel.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.presentation.model.setting
2 |
3 | import androidx.compose.runtime.Immutable
4 | import com.pomonyang.mohanyang.data.local.room.enitity.PomodoroSettingEntity
5 | import com.pomonyang.mohanyang.presentation.screen.home.category.model.CategoryIcon
6 | import java.time.Duration
7 |
8 | @Immutable
9 | data class PomodoroSettingModel(
10 | val categoryNo: Int,
11 | val title: String,
12 | val categoryIcon: CategoryIcon,
13 | val focusTime: Int,
14 | val restTime: Int,
15 | val isSelected: Boolean,
16 | )
17 |
18 | fun PomodoroSettingEntity.toModel() = PomodoroSettingModel(
19 | categoryNo = categoryNo,
20 | title = title,
21 | categoryIcon = CategoryIcon.safeValueOf(iconType),
22 | focusTime = Duration.parse(focusTime).toMinutes().toInt(),
23 | restTime = Duration.parse(restTime).toMinutes().toInt(),
24 | isSelected = isSelected,
25 | )
26 |
--------------------------------------------------------------------------------
/presentation/src/main/java/com/pomonyang/mohanyang/presentation/model/user/UserInfoModel.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.presentation.model.user
2 |
3 | import com.pomonyang.mohanyang.data.remote.model.response.UserInfoResponse
4 | import com.pomonyang.mohanyang.presentation.model.cat.CatInfoModel
5 | import com.pomonyang.mohanyang.presentation.model.cat.toModel
6 |
7 | data class UserInfoModel(
8 | val registeredDeviceNo: Int,
9 | val isPushEnabled: Boolean,
10 | val cat: CatInfoModel,
11 | )
12 |
13 | fun UserInfoResponse.toModel() = UserInfoModel(
14 | registeredDeviceNo = this.registeredDeviceNo,
15 | isPushEnabled = this.isPushEnabled,
16 | cat = this.cat.toModel(),
17 | )
18 |
--------------------------------------------------------------------------------
/presentation/src/main/java/com/pomonyang/mohanyang/presentation/screen/PomodoroConstants.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.presentation.screen
2 |
3 | import com.mohanyang.presentation.BuildConfig
4 |
5 | object PomodoroConstants {
6 | val TIMER_DELAY = if (BuildConfig.DEBUG) 100L else 1_000L
7 | val MAX_EXCEEDED_TIME = if (BuildConfig.DEBUG) 60 else 3600
8 | const val MAX_FOCUS_MINUTES = 60
9 | const val MAX_REST_MINUTES = 30
10 | const val MIN_FOCUS_MINUTES = 10
11 | const val MIN_REST_MINUTES = 5
12 | const val ONE_SECOND = 1
13 | const val DEFAULT_TIME = "00:00"
14 | const val POMODORO_NOTIFICATION_CHANNEL_ID = "pomodoro_notification_channel_v2"
15 | const val POMODORO_NOTIFICATION_CHANNEL_NAME = "pomodoro_notification_channel_name"
16 | const val POMODORO_NOTIFICATION_ID = 1
17 | }
18 |
--------------------------------------------------------------------------------
/presentation/src/main/java/com/pomonyang/mohanyang/presentation/screen/common/LoadingScreen.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.presentation.screen.common
2 |
3 | import androidx.compose.foundation.background
4 | import androidx.compose.foundation.layout.Box
5 | import androidx.compose.foundation.layout.fillMaxSize
6 | import androidx.compose.runtime.Composable
7 | import androidx.compose.ui.Alignment
8 | import androidx.compose.ui.Modifier
9 | import androidx.compose.ui.graphics.Color
10 | import androidx.compose.ui.input.pointer.pointerInput
11 | import com.pomonyang.mohanyang.presentation.designsystem.spinner.MnSpinner
12 |
13 | @Composable
14 | fun LoadingScreen(
15 | modifier: Modifier = Modifier,
16 | ) {
17 | Box(
18 | modifier = modifier
19 | .fillMaxSize()
20 | .background(Color.Transparent)
21 | .pointerInput(Unit) {},
22 | contentAlignment = Alignment.Center,
23 | ) {
24 | MnSpinner()
25 | }
26 | }
27 |
28 | @Composable
29 | fun LoadingContentContainer(
30 | isLoading: Boolean,
31 | content: @Composable () -> Unit,
32 | ) {
33 | Box {
34 | content()
35 | if (isLoading) {
36 | LoadingScreen()
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/presentation/src/main/java/com/pomonyang/mohanyang/presentation/screen/common/NetworkErrorDialog.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.presentation.screen.common
2 |
3 | import androidx.compose.foundation.layout.fillMaxWidth
4 | import androidx.compose.runtime.Composable
5 | import androidx.compose.ui.Modifier
6 | import androidx.compose.ui.platform.LocalContext
7 | import androidx.compose.ui.window.DialogProperties
8 | import com.mohanyang.presentation.R
9 | import com.pomonyang.mohanyang.presentation.designsystem.button.box.MnBoxButton
10 | import com.pomonyang.mohanyang.presentation.designsystem.button.box.MnBoxButtonColorType
11 | import com.pomonyang.mohanyang.presentation.designsystem.button.box.MnBoxButtonStyles
12 | import com.pomonyang.mohanyang.presentation.designsystem.dialog.MnDialog
13 |
14 | @Composable
15 | fun NetworkErrorDialog(
16 | onClickRefresh: () -> Unit,
17 | onDismissRequest: () -> Unit,
18 | modifier: Modifier = Modifier,
19 | ) = with(LocalContext.current) {
20 | MnDialog(
21 | modifier = modifier,
22 | properties = DialogProperties(
23 | dismissOnClickOutside = false,
24 | dismissOnBackPress = true,
25 | ),
26 | title = getString(R.string.network_error_title),
27 | subTitle = getString(R.string.network_error_content),
28 | positiveButton = {
29 | MnBoxButton(
30 | modifier = Modifier.fillMaxWidth(),
31 | text = getString(R.string.network_refresh),
32 | onClick = onClickRefresh,
33 | colors = MnBoxButtonColorType.primary,
34 | styles = MnBoxButtonStyles.medium,
35 |
36 | )
37 | },
38 | onDismissRequest = onDismissRequest,
39 | )
40 | }
41 |
--------------------------------------------------------------------------------
/presentation/src/main/java/com/pomonyang/mohanyang/presentation/screen/home/category/CategoryNameVerifier.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.presentation.screen.home.category
2 |
3 | import com.pomonyang.mohanyang.presentation.model.category.PomodoroCategoryModel
4 | import com.pomonyang.mohanyang.presentation.screen.onboarding.naming.ValidationResult
5 | import kotlinx.collections.immutable.ImmutableList
6 |
7 | object CategoryNameVerifier {
8 |
9 | private const val NAME_MAX_LENGTH = 10
10 | private const val NAME_MIN_LENGTH = 1
11 |
12 | fun validateCategoryName(name: String, categoryList: ImmutableList): ValidationResult = when {
13 | categoryList.any { it.title == name } -> ValidationResult(false, "이미 존재하는 카테고리예요.")
14 | else -> ValidationResult(
15 | isValid = name.length in NAME_MIN_LENGTH..NAME_MAX_LENGTH,
16 | message = if (name.length <= NAME_MAX_LENGTH) "" else "최대 ${NAME_MAX_LENGTH}자리까지 입력할 수 있어요.",
17 | )
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/presentation/src/main/java/com/pomonyang/mohanyang/presentation/screen/home/category/component/CategoryBottomSheetHeaderContents.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.presentation.screen.home.category.component
2 |
3 | import androidx.compose.foundation.layout.Arrangement
4 | import androidx.compose.foundation.layout.Row
5 | import androidx.compose.runtime.Composable
6 | import androidx.compose.ui.Modifier
7 | import androidx.compose.ui.tooling.preview.Preview
8 | import com.mohanyang.presentation.R
9 | import com.pomonyang.mohanyang.presentation.designsystem.button.icon.MnIconButton
10 | import com.pomonyang.mohanyang.presentation.theme.MnTheme
11 |
12 | @Composable
13 | fun CategoryBottomSheetHeaderContents(
14 | isVisibleAddButton: Boolean,
15 | onEditClick: () -> Unit,
16 | onMoreMenuClick: () -> Unit,
17 | modifier: Modifier = Modifier,
18 | ) {
19 | Row(
20 | modifier = modifier,
21 | horizontalArrangement = Arrangement.SpaceBetween,
22 | ) {
23 | if (isVisibleAddButton) {
24 | MnIconButton(
25 | onClick = onEditClick,
26 | iconResourceId = R.drawable.ic_plus,
27 | )
28 | }
29 | MnIconButton(
30 | onClick = onMoreMenuClick,
31 | iconResourceId = R.drawable.ic_ellipsis,
32 | )
33 | }
34 | }
35 |
36 | @Preview(showBackground = true)
37 | @Composable
38 | private fun CategoryBottomSheetHeaderPreview() {
39 | MnTheme {
40 | CategoryBottomSheetHeaderContents(
41 | isVisibleAddButton = true,
42 | onEditClick = {},
43 | onMoreMenuClick = {},
44 | )
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/presentation/src/main/java/com/pomonyang/mohanyang/presentation/screen/home/category/model/CategoryManageState.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.presentation.screen.home.category.model
2 |
3 | import androidx.annotation.StringRes
4 | import com.mohanyang.presentation.R
5 |
6 | enum class CategoryManageState(
7 | @StringRes val title: Int,
8 | ) {
9 | DEFAULT(R.string.change_category_title),
10 | EDIT(R.string.change_category_edit_title),
11 | DELETE(R.string.change_category_delete_title),
12 | ;
13 |
14 | fun isEdit() = this == EDIT
15 |
16 | fun isDefault() = this == DEFAULT
17 |
18 | fun isDelete() = this == DELETE
19 | }
20 |
--------------------------------------------------------------------------------
/presentation/src/main/java/com/pomonyang/mohanyang/presentation/screen/home/category/model/CategoryModel.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.presentation.screen.home.category.model
2 |
3 | import kotlinx.serialization.Serializable
4 |
5 | @Serializable
6 | data class CategoryModel(
7 | val name: String,
8 | val icon: CategoryIcon = CategoryIcon.CAT,
9 | ) : java.io.Serializable
10 |
--------------------------------------------------------------------------------
/presentation/src/main/java/com/pomonyang/mohanyang/presentation/screen/home/time/PomodoroTimeSettingElements.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.presentation.screen.home.time
2 |
3 | import com.pomonyang.mohanyang.presentation.base.NetworkViewState
4 | import com.pomonyang.mohanyang.presentation.base.ViewEvent
5 | import com.pomonyang.mohanyang.presentation.base.ViewSideEffect
6 | import com.pomonyang.mohanyang.presentation.screen.home.category.model.CategoryIcon
7 |
8 | data class PomodoroTimeSettingState(
9 | val categoryNo: Int = 0,
10 | val titleName: String = "",
11 | val initialFocusTime: Int = 10,
12 | val initialRestTime: Int = 10,
13 | val pickFocusTime: Int = 10,
14 | val pickRestTime: Int = 10,
15 | val isFocus: Boolean = false,
16 | val categoryIcon: CategoryIcon = CategoryIcon.CAT,
17 | override val isLoading: Boolean = false,
18 | override val isInternalError: Boolean = false,
19 | override val isInvalidError: Boolean = false,
20 | override val lastRequestAction: PomodoroTimeSettingEvent? = null,
21 | ) : NetworkViewState()
22 |
23 | sealed interface PomodoroTimeSettingEvent : ViewEvent {
24 | data class Init(val isFocusTime: Boolean) : PomodoroTimeSettingEvent
25 |
26 | data object Submit : PomodoroTimeSettingEvent
27 |
28 | data class ChangePickTime(
29 | val time: Int,
30 | ) : PomodoroTimeSettingEvent
31 |
32 | data object ClickClose : PomodoroTimeSettingEvent
33 |
34 | data object ClickRetry : PomodoroTimeSettingEvent
35 | }
36 |
37 | sealed interface PomodoroTimeSettingEffect : ViewSideEffect {
38 | data object GoToPomodoroSettingScreen : PomodoroTimeSettingEffect
39 |
40 | data object ClosePomodoroTimerSettingScreen : PomodoroTimeSettingEffect
41 | }
42 |
--------------------------------------------------------------------------------
/presentation/src/main/java/com/pomonyang/mohanyang/presentation/screen/mypage/MyPageElements.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.presentation.screen.mypage
2 |
3 | import com.pomonyang.mohanyang.presentation.base.ViewEvent
4 | import com.pomonyang.mohanyang.presentation.base.ViewSideEffect
5 | import com.pomonyang.mohanyang.presentation.base.ViewState
6 |
7 | data class MyPageState(
8 | val catName: String = "",
9 | val isInterruptNotificationEnabled: Boolean = false,
10 | val isTimerNotificationEnabled: Boolean = false,
11 | val isLockScreenNotificationEnabled: Boolean = false,
12 | ) : ViewState
13 |
14 | sealed interface MyPageEvent : ViewEvent {
15 | data class Init(val appNotificationGranted: Boolean) : MyPageEvent
16 | data class ClickCatProfile(val isOffline: Boolean) : MyPageEvent
17 | data class ChangeInterruptNotification(val isEnabled: Boolean) : MyPageEvent
18 | data class ChangeTimerNotification(val isEnabled: Boolean) : MyPageEvent
19 | data class ChangeLockScreenNotification(val isEnabled: Boolean) : MyPageEvent
20 | data object CloseDialog : MyPageEvent
21 | data object OpenSetting : MyPageEvent
22 | data object ClickSuggestion : MyPageEvent
23 | }
24 |
25 | sealed interface MyPageSideEffect : ViewSideEffect {
26 | data object GoToCatProfilePage : MyPageSideEffect
27 | data class CheckNotificationPermission(val request: NotificationRequest, val onGranted: () -> Unit) : MyPageSideEffect
28 | data class OpenExternalWebPage(val url: String) : MyPageSideEffect
29 | data object CloseDialog : MyPageSideEffect
30 | data object OpenDialog : MyPageSideEffect
31 | data class ShowSnackBar(val message: String) : MyPageSideEffect
32 | }
33 |
--------------------------------------------------------------------------------
/presentation/src/main/java/com/pomonyang/mohanyang/presentation/screen/onboarding/model/OnboardingGuideContent.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.presentation.screen.onboarding.model
2 |
3 | import android.content.Context
4 | import androidx.annotation.DrawableRes
5 | import androidx.compose.runtime.Stable
6 | import com.mohanyang.presentation.R
7 | import kotlin.math.max
8 |
9 | @Stable
10 | data class OnboardingGuideContent(
11 | val title: String,
12 | val subtitle: String,
13 | @DrawableRes val image: Int,
14 | )
15 |
16 | fun Context.getOnBoardingContents(): List {
17 | val guideTitles = this.resources.getStringArray(R.array.onboarding_guide_title)
18 | val guideSubTitles = this.resources.getStringArray(R.array.onboarding_guide_subtitle)
19 | val guideImages = this.resources.obtainTypedArray(R.array.onboarding_guide_image)
20 |
21 | val maxSize = max(guideTitles.size, guideSubTitles.size)
22 |
23 | val guides = List(maxSize) { index ->
24 | val title = guideTitles.getOrElse(index) { "" }
25 | val subtitle = guideSubTitles.getOrElse(index) { "" }
26 | val guideImage = guideImages.getResourceId(index, R.drawable.onboarding_contents_1)
27 | OnboardingGuideContent(title, subtitle, guideImage)
28 | }
29 | guideImages.recycle()
30 |
31 | return guides
32 | }
33 |
--------------------------------------------------------------------------------
/presentation/src/main/java/com/pomonyang/mohanyang/presentation/screen/onboarding/naming/CatNameVerifier.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.presentation.screen.onboarding.naming
2 |
3 | import javax.inject.Inject
4 |
5 | data class ValidationResult(val isValid: Boolean, val message: String = "")
6 |
7 | class CatNameVerifier @Inject constructor() {
8 |
9 | fun verifyName(name: String): ValidationResult {
10 | val validators = listOf(
11 | ::validateLength,
12 | ::validatePattern,
13 | ::validateFirstSpace,
14 | )
15 |
16 | return validators.asSequence()
17 | .map { it(name) }
18 | .firstOrNull { it.isValid.not() }
19 | ?: ValidationResult(true)
20 | }
21 |
22 | private fun validateLength(name: String) = ValidationResult(
23 | isValid = name.length <= NAME_MAX_LENGTH,
24 | message = if (name.length <= NAME_MAX_LENGTH) "" else "최대 ${NAME_MAX_LENGTH}자리까지 허용됩니다.",
25 | )
26 |
27 | private fun validatePattern(name: String) = ValidationResult(
28 | isValid = NAME_PATTERN.toRegex().matches(name),
29 | message = if (NAME_PATTERN.toRegex().matches(name)) "" else "특수 문자는 사용할 수 없습니다.",
30 | )
31 |
32 | private fun validateFirstSpace(name: String) = ValidationResult(
33 | isValid = !name.first().isWhitespace(),
34 | message = if (name.first().isWhitespace()) "고양이 이름은 빈 칸이 될 수 없어요" else "",
35 | )
36 |
37 | companion object {
38 | private const val NAME_MAX_LENGTH = 10
39 | private const val NAME_MIN_LENGTH = 1
40 | private const val NAME_PATTERN = "^[\\w\\s\\n]{${NAME_MIN_LENGTH},${NAME_MAX_LENGTH}}$"
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/presentation/src/main/java/com/pomonyang/mohanyang/presentation/screen/onboarding/naming/OnboardingNamingElements.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.presentation.screen.onboarding.naming
2 |
3 | import com.pomonyang.mohanyang.presentation.base.NetworkViewState
4 | import com.pomonyang.mohanyang.presentation.base.ViewEvent
5 | import com.pomonyang.mohanyang.presentation.base.ViewSideEffect
6 |
7 | data class NamingState(
8 | override val isLoading: Boolean = false,
9 | override val isInvalidError: Boolean = false,
10 | override val isInternalError: Boolean = false,
11 | override val lastRequestAction: NamingEvent? = null,
12 | ) : NetworkViewState()
13 |
14 | sealed interface NamingEvent : ViewEvent {
15 | data class OnComplete(val name: String) : NamingEvent
16 | data object OnClickRetry : NamingEvent
17 | }
18 |
19 | sealed interface NamingSideEffect : ViewSideEffect {
20 | data object NavToNext : NamingSideEffect
21 | }
22 |
--------------------------------------------------------------------------------
/presentation/src/main/java/com/pomonyang/mohanyang/presentation/screen/onboarding/select/OnboardingSelectCatElements.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.presentation.screen.onboarding.select
2 |
3 | import com.pomonyang.mohanyang.presentation.base.NetworkViewState
4 | import com.pomonyang.mohanyang.presentation.base.ViewEvent
5 | import com.pomonyang.mohanyang.presentation.base.ViewSideEffect
6 | import com.pomonyang.mohanyang.presentation.model.cat.CatInfoModel
7 | import com.pomonyang.mohanyang.presentation.model.cat.CatType
8 |
9 | data class SelectCatState(
10 | override val isLoading: Boolean = true,
11 | override val isInternalError: Boolean = false,
12 | override val isInvalidError: Boolean = false,
13 | override val lastRequestAction: SelectCatEvent? = null,
14 | val cats: List = emptyList(),
15 | val selectedType: CatType? = null,
16 | ) : NetworkViewState()
17 |
18 | sealed interface SelectCatEvent : ViewEvent {
19 | data class Init(val catNo: Int? = null) : SelectCatEvent
20 | data class OnSelectType(val type: CatType) : SelectCatEvent
21 | data object OnStartClick : SelectCatEvent
22 | data object OnGrantedAlarmPermission : SelectCatEvent
23 | data object OnClickRetry : SelectCatEvent
24 | }
25 |
26 | sealed interface SelectCatSideEffect : ViewSideEffect {
27 | data class OnNavToNaming(val no: Int, val catName: String, val catTypeName: String) : SelectCatSideEffect
28 | data object GoToBack : SelectCatSideEffect
29 | data class ShowSnackBar(val message: String) : SelectCatSideEffect
30 | }
31 |
--------------------------------------------------------------------------------
/presentation/src/main/java/com/pomonyang/mohanyang/presentation/screen/pomodoro/focus/PomodoroFocusElements.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.presentation.screen.pomodoro.focus
2 |
3 | import com.pomonyang.mohanyang.presentation.base.ViewEvent
4 | import com.pomonyang.mohanyang.presentation.base.ViewSideEffect
5 | import com.pomonyang.mohanyang.presentation.base.ViewState
6 | import com.pomonyang.mohanyang.presentation.model.cat.CatType
7 | import com.pomonyang.mohanyang.presentation.screen.home.category.model.CategoryIcon
8 | import com.pomonyang.mohanyang.presentation.util.formatTime
9 | import java.util.*
10 |
11 | data class PomodoroFocusState(
12 | val pomodoroId: String = UUID.randomUUID().toString(),
13 | val remainingFocusTime: Int = 0,
14 | val focusExceededTime: Int = 0,
15 | val maxFocusTime: Int = 0,
16 | val title: String = "",
17 | val categoryIcon: CategoryIcon = CategoryIcon.CAT,
18 | val cat: CatType = CatType.CHEESE,
19 | val categoryNo: Int = -1,
20 | val forceGoRest: Boolean = false,
21 | ) : ViewState {
22 |
23 | fun displayFocusTime(): String = remainingFocusTime.formatTime()
24 | fun displayFocusExceedTime(): String = focusExceededTime.formatTime()
25 |
26 | val currentFocusTime: Int
27 | get() = maxFocusTime - remainingFocusTime
28 | }
29 |
30 | sealed interface PomodoroFocusEvent : ViewEvent {
31 | data object Init : PomodoroFocusEvent
32 | data object ClickRest : PomodoroFocusEvent
33 | data object ClickHome : PomodoroFocusEvent
34 | }
35 |
36 | sealed interface PomodoroFocusEffect : ViewSideEffect {
37 | data object GoToPomodoroRest : PomodoroFocusEffect
38 | data object GoToPomodoroSetting : PomodoroFocusEffect
39 | data object StartFocusAlarm : PomodoroFocusEffect
40 | data object StopFocusAlarm : PomodoroFocusEffect
41 | }
42 |
--------------------------------------------------------------------------------
/presentation/src/main/java/com/pomonyang/mohanyang/presentation/screen/statistics/StaticsNavigator.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.presentation.screen.statistics
2 |
3 | import androidx.navigation.NavGraphBuilder
4 | import androidx.navigation.NavHostController
5 | import androidx.navigation.navigation
6 | import com.pomonyang.mohanyang.presentation.util.composableWithDefaultTransition
7 | import kotlinx.serialization.Serializable
8 |
9 | @Serializable
10 | data object StatisticsGraph
11 |
12 | @Serializable
13 | data object Statistics
14 |
15 | fun NavGraphBuilder.statisticsScreen(
16 | onShowSnackbar: (String, Int?) -> Unit,
17 | navHostController: NavHostController,
18 | ) {
19 | navigation(
20 | startDestination = Statistics,
21 | ) {
22 | composableWithDefaultTransition {
23 | StatisticsRoute(
24 | onShowSnackbar = onShowSnackbar,
25 | )
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/presentation/src/main/java/com/pomonyang/mohanyang/presentation/screen/statistics/StatisticsElements.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.presentation.screen.statistics
2 |
3 | import androidx.compose.runtime.Immutable
4 | import androidx.compose.runtime.Stable
5 | import com.pomonyang.mohanyang.presentation.base.ViewEvent
6 | import com.pomonyang.mohanyang.presentation.base.ViewSideEffect
7 | import com.pomonyang.mohanyang.presentation.base.ViewState
8 | import com.pomonyang.mohanyang.presentation.screen.statistics.model.StatisticsModel
9 |
10 | @Stable
11 | data class StatisticsState(
12 | val statisticsModel: StatisticsModel,
13 | ) : ViewState
14 |
15 | @Immutable
16 | sealed interface StatisticsEvent : ViewEvent
17 |
18 | @Immutable
19 | sealed interface StatisticsSideEffect : ViewSideEffect
20 |
--------------------------------------------------------------------------------
/presentation/src/main/java/com/pomonyang/mohanyang/presentation/screen/statistics/StatisticsScreen.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.presentation.screen.statistics
2 |
3 | import androidx.compose.foundation.layout.fillMaxSize
4 | import androidx.compose.material3.Surface
5 | import androidx.compose.material3.Text
6 | import androidx.compose.runtime.Composable
7 | import androidx.compose.ui.Modifier
8 | import androidx.hilt.navigation.compose.hiltViewModel
9 | import com.pomonyang.mohanyang.presentation.theme.MnTheme
10 | import com.pomonyang.mohanyang.presentation.util.ThemePreviews
11 |
12 | @Composable
13 | fun StatisticsRoute(
14 | onShowSnackbar: (String, Int?) -> Unit,
15 | modifier: Modifier = Modifier,
16 | viewModel: StatisticsViewModel = hiltViewModel(),
17 | ) {
18 | StatisticsScreen(modifier)
19 | }
20 |
21 | @Composable
22 | private fun StatisticsScreen(
23 | modifier: Modifier = Modifier,
24 | ) {
25 | Surface(
26 | modifier = modifier.fillMaxSize(),
27 | color = MnTheme.backgroundColorScheme.primary,
28 | ) {
29 | Text("StatisticsScreen")
30 | }
31 | }
32 |
33 | @ThemePreviews
34 | @Composable
35 | private fun StatisticsScreenPreview() {
36 | MnTheme {
37 | StatisticsScreen()
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/presentation/src/main/java/com/pomonyang/mohanyang/presentation/screen/statistics/StatisticsViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.presentation.screen.statistics
2 |
3 | import androidx.lifecycle.viewModelScope
4 | import com.pomonyang.mohanyang.data.repository.statistics.StatisticsRepository
5 | import com.pomonyang.mohanyang.presentation.base.BaseViewModel
6 | import com.pomonyang.mohanyang.presentation.screen.statistics.model.StatisticsModel
7 | import com.pomonyang.mohanyang.presentation.screen.statistics.model.mapper.toModel
8 | import dagger.hilt.android.lifecycle.HiltViewModel
9 | import java.time.LocalDate
10 | import javax.inject.Inject
11 | import kotlinx.coroutines.launch
12 | import timber.log.Timber
13 |
14 | @HiltViewModel
15 | class StatisticsViewModel @Inject constructor(
16 | private val statisticsRepository: StatisticsRepository,
17 | ) : BaseViewModel() {
18 |
19 | init {
20 | viewModelScope.launch {
21 | statisticsRepository.getStatistics(
22 | LocalDate.now(),
23 | ).onSuccess { statisticsResponse ->
24 | updateState {
25 | copy(
26 | statisticsModel = statisticsResponse.toModel(),
27 | )
28 | }
29 | }.onFailure { error ->
30 | Timber.e("getStatistics fail $error")
31 | }
32 | }
33 | }
34 |
35 | override fun setInitialState(): StatisticsState = StatisticsState(
36 | statisticsModel = StatisticsModel.placeHolder,
37 | )
38 |
39 | override fun handleEvent(event: StatisticsEvent) {
40 | when (event) {
41 | else -> {}
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/presentation/src/main/java/com/pomonyang/mohanyang/presentation/screen/statistics/component/Dot.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.presentation.screen.statistics.component
2 |
3 | import androidx.compose.foundation.background
4 | import androidx.compose.foundation.layout.Arrangement
5 | import androidx.compose.foundation.layout.Box
6 | import androidx.compose.foundation.layout.Column
7 | import androidx.compose.foundation.layout.fillMaxSize
8 | import androidx.compose.foundation.layout.size
9 | import androidx.compose.foundation.shape.RoundedCornerShape
10 | import androidx.compose.runtime.Composable
11 | import androidx.compose.ui.Alignment
12 | import androidx.compose.ui.Modifier
13 | import androidx.compose.ui.graphics.Color
14 | import androidx.compose.ui.tooling.preview.Preview
15 | import androidx.compose.ui.unit.dp
16 | import com.pomonyang.mohanyang.presentation.theme.MnTheme
17 |
18 | @Composable
19 | fun Dot(
20 | modifier: Modifier = Modifier,
21 | ) {
22 | Box(
23 | modifier = modifier
24 | .size(
25 | width = 1.dp,
26 | height = 5.dp,
27 | )
28 | .background(
29 | color = MnTheme.iconColorScheme.disabled,
30 | shape = RoundedCornerShape(50.dp),
31 | ),
32 | )
33 | }
34 |
35 | @Preview
36 | @Composable
37 | private fun DotPreview() {
38 | val repeatCount = 100
39 | MnTheme {
40 | Column(
41 | modifier = Modifier
42 | .fillMaxSize()
43 | .background(Color.Black),
44 | verticalArrangement = Arrangement.spacedBy(3.dp, Alignment.CenterVertically),
45 | horizontalAlignment = Alignment.CenterHorizontally,
46 | ) {
47 | repeat(repeatCount) {
48 | Dot()
49 | }
50 | }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/presentation/src/main/java/com/pomonyang/mohanyang/presentation/screen/statistics/component/StatisticsContentHeader.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.presentation.screen.statistics.component
2 |
3 | import androidx.compose.foundation.layout.Arrangement
4 | import androidx.compose.foundation.layout.Row
5 | import androidx.compose.material3.Text
6 | import androidx.compose.runtime.Composable
7 | import androidx.compose.ui.Alignment
8 | import androidx.compose.ui.Modifier
9 | import androidx.compose.ui.tooling.preview.Preview
10 | import com.mohanyang.presentation.R
11 | import com.pomonyang.mohanyang.presentation.designsystem.icon.MnSmallIcon
12 | import com.pomonyang.mohanyang.presentation.designsystem.token.MnSpacing
13 | import com.pomonyang.mohanyang.presentation.theme.MnTheme
14 |
15 | @Composable
16 | fun StatisticsContentHeader(
17 | time: String, // 데이터 형식 어떻게 뽑을지 고민 중
18 | modifier: Modifier = Modifier,
19 | ) {
20 | Row(
21 | verticalAlignment = Alignment.CenterVertically,
22 | horizontalArrangement = Arrangement.spacedBy(MnSpacing.xSmall),
23 | modifier = modifier,
24 | ) {
25 | MnSmallIcon(
26 | resourceId = R.drawable.ic_circle,
27 | modifier = modifier,
28 | tint = MnTheme.iconColorScheme.disabled,
29 | )
30 |
31 | Text(
32 | text = time,
33 | style = MnTheme.typography.subBodyRegular,
34 | color = MnTheme.textColorScheme.tertiary,
35 | )
36 | }
37 | }
38 |
39 | @Preview
40 | @Composable
41 | private fun StatisticsContentHeaderPreview() {
42 | MnTheme {
43 | StatisticsContentHeader("11:58-13:32")
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/presentation/src/main/java/com/pomonyang/mohanyang/presentation/service/PomodoroTimer.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.presentation.service
2 |
3 | import com.pomonyang.mohanyang.presentation.screen.home.category.model.CategoryModel
4 |
5 | internal interface PomodoroTimer {
6 |
7 | fun startTimer(
8 | timerId: String,
9 | maxTime: Int,
10 | eventHandler: PomodoroTimerEventHandler,
11 | category: CategoryModel? = null,
12 | )
13 |
14 | fun stopTimer()
15 | }
16 |
--------------------------------------------------------------------------------
/presentation/src/main/java/com/pomonyang/mohanyang/presentation/service/PomodoroTimerEventHandler.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.presentation.service
2 |
3 | import com.pomonyang.mohanyang.presentation.screen.home.category.model.CategoryModel
4 |
5 | internal interface PomodoroTimerEventHandler {
6 | fun onTimeEnd()
7 | fun onTimeExceeded()
8 | fun updateTimer(
9 | timerId: String,
10 | time: String,
11 | overtime: String,
12 | category: CategoryModel?,
13 | )
14 | }
15 |
--------------------------------------------------------------------------------
/presentation/src/main/java/com/pomonyang/mohanyang/presentation/service/PomodoroTimerServiceExtras.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.presentation.service
2 |
3 | internal object PomodoroTimerServiceExtras {
4 | const val INTENT_TIMER_MAX_TIME = "mohanyang.intent.MAX_TIME"
5 | const val INTENT_TIMER_ID = "mohanyang.intent.TIMER_ID"
6 | const val INTENT_CATEGORY = "mohanyang.intent.CATEGORY"
7 | const val ACTION_TIMER_START = "mohanyang.action.TIMER_START"
8 | const val ACTION_TIMER_STOP = "mohanyang.action.TIMER_STOP"
9 | }
10 |
--------------------------------------------------------------------------------
/presentation/src/main/java/com/pomonyang/mohanyang/presentation/service/focus/FocusTimer.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.presentation.service.focus
2 |
3 | import com.pomonyang.mohanyang.presentation.service.BasePomodoroTimer
4 | import javax.inject.Inject
5 |
6 | internal class FocusTimer @Inject constructor() : BasePomodoroTimer() {
7 |
8 | override fun getTagName(): String = "TIMER_FOCUS"
9 | }
10 |
--------------------------------------------------------------------------------
/presentation/src/main/java/com/pomonyang/mohanyang/presentation/service/rest/RestTimer.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.presentation.service.rest
2 |
3 | import com.pomonyang.mohanyang.presentation.service.BasePomodoroTimer
4 | import javax.inject.Inject
5 |
6 | internal class RestTimer @Inject constructor() : BasePomodoroTimer() {
7 |
8 | override fun getTagName(): String = "TIMER_REST"
9 | }
10 |
--------------------------------------------------------------------------------
/presentation/src/main/java/com/pomonyang/mohanyang/presentation/util/DpPxSpConversionUtils.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.presentation.util
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.ui.platform.LocalDensity
5 | import androidx.compose.ui.unit.Dp
6 | import androidx.compose.ui.unit.TextUnit
7 | import androidx.compose.ui.unit.dp
8 | import androidx.compose.ui.unit.sp
9 |
10 | // dp(Dp) → px(Float)
11 | @Composable
12 | internal fun Dp.dpToPx(): Float = this.value * LocalDensity.current.density
13 |
14 | // dp(Dp) → sp(TextUnit)
15 | @Composable
16 | internal fun Dp.dpToSp(): TextUnit = (this.value * LocalDensity.current.density / LocalDensity.current.fontScale).sp
17 |
18 | // px(Float) → dp(Dp)
19 | @Composable
20 | internal fun Float.pxToDp(): Dp = (this / LocalDensity.current.density).dp
21 |
22 | // px(Float) → sp(TextUnit)
23 | @Composable
24 | internal fun Float.pxToSp(): TextUnit = (this / LocalDensity.current.fontScale).sp
25 |
26 | // sp(TextUnit) → dp(Dp)
27 | @Composable
28 | internal fun TextUnit.spToDp(): Dp = (this.value * LocalDensity.current.fontScale / LocalDensity.current.density).dp
29 |
30 | // sp(TextUnit) → px(Float)
31 | @Composable
32 | internal fun TextUnit.spToPx(): Float = this.value * LocalDensity.current.fontScale
33 |
--------------------------------------------------------------------------------
/presentation/src/main/java/com/pomonyang/mohanyang/presentation/util/FlowUtils.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.presentation.util
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.LaunchedEffect
5 | import androidx.lifecycle.Lifecycle
6 | import androidx.lifecycle.compose.LocalLifecycleOwner
7 | import androidx.lifecycle.repeatOnLifecycle
8 | import kotlinx.coroutines.flow.Flow
9 |
10 | @Composable
11 | inline fun Flow.collectWithLifecycle(
12 | minActiveState: Lifecycle.State = Lifecycle.State.STARTED,
13 | noinline action: suspend (T) -> Unit,
14 | ) {
15 | val lifecycleOwner = LocalLifecycleOwner.current
16 |
17 | LaunchedEffect(this, lifecycleOwner) {
18 | lifecycleOwner.lifecycle.repeatOnLifecycle(minActiveState) {
19 | this@collectWithLifecycle.collect { action(it) }
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/presentation/src/main/java/com/pomonyang/mohanyang/presentation/util/IntentUtils.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.presentation.util
2 |
3 | import android.content.Intent
4 | import android.os.Build
5 | import java.io.Serializable
6 |
7 | inline fun Intent.getSerializableExtraCompat(key: String): T? = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
8 | getSerializableExtra(key, T::class.java)
9 | } else {
10 | @Suppress("DEPRECATION")
11 | getSerializableExtra(key) as? T
12 | }
13 |
--------------------------------------------------------------------------------
/presentation/src/main/java/com/pomonyang/mohanyang/presentation/util/NavigationUtils.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.presentation.util
2 |
3 | import androidx.compose.animation.AnimatedContentScope
4 | import androidx.compose.animation.core.tween
5 | import androidx.compose.animation.fadeIn
6 | import androidx.compose.animation.fadeOut
7 | import androidx.compose.runtime.Composable
8 | import androidx.navigation.NavBackStackEntry
9 | import androidx.navigation.NavGraphBuilder
10 | import androidx.navigation.compose.composable
11 |
12 | inline fun NavGraphBuilder.composableWithDefaultTransition(
13 | noinline content: @Composable AnimatedContentScope.(NavBackStackEntry) -> Unit,
14 | ) {
15 | composable(
16 | enterTransition = { fadeIn(animationSpec = tween(300)) },
17 | popEnterTransition = { fadeIn(animationSpec = tween(300)) },
18 | popExitTransition = { fadeOut(animationSpec = tween(300)) },
19 | content = content,
20 | )
21 | }
22 |
--------------------------------------------------------------------------------
/presentation/src/main/java/com/pomonyang/mohanyang/presentation/util/PreviewUtils.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.presentation.util
2 |
3 | import android.content.res.Configuration
4 | import androidx.compose.ui.tooling.preview.Devices
5 | import androidx.compose.ui.tooling.preview.Preview
6 |
7 | @Preview(
8 | name = "LightTheme",
9 | group = "Theme",
10 | showBackground = true,
11 | backgroundColor = 0xFFFAF6F3,
12 | uiMode = Configuration.UI_MODE_NIGHT_NO,
13 | )
14 | @Preview(
15 | name = "DarkTheme",
16 | group = "Theme",
17 | showBackground = true,
18 | backgroundColor = 0xFFFAF6F3,
19 | uiMode = Configuration.UI_MODE_NIGHT_YES,
20 | )
21 | annotation class ThemePreviews
22 |
23 | @Preview(
24 | name = "Normal",
25 | device = "spec:shape=Normal,width=1440,height=2800,unit=px,dpi=515",
26 | showBackground = true,
27 | backgroundColor = 0xFFFAF6F3,
28 | )
29 | @Preview(
30 | name = "Short",
31 | device = "spec:shape=Normal,width=1440,height=2000,unit=px,dpi=515",
32 | showBackground = true,
33 | backgroundColor = 0xFFFAF6F3,
34 | )
35 | @Preview(
36 | name = "Foldable",
37 | device = Devices.FOLDABLE,
38 | showBackground = true,
39 | backgroundColor = 0xFFFAF6F3,
40 | )
41 | annotation class DevicePreviews
42 |
--------------------------------------------------------------------------------
/presentation/src/main/java/com/pomonyang/mohanyang/presentation/util/TimeUtils.kt:
--------------------------------------------------------------------------------
1 | package com.pomonyang.mohanyang.presentation.util
2 |
3 | import java.time.LocalTime
4 | import java.util.*
5 |
6 | fun LocalTime.displayAlarm(): String = "${this.hour}".padStart(2, '0') + ":" + "${this.minute}".padStart(2, '0') + " " + if (this.hour < 12) "AM" else "PM"
7 |
8 | fun Int.formatTime(): String {
9 | val minutesPart = this / 60
10 | val secondsPart = this % 60
11 | return String.format(Locale.KOREAN, "%02d:%02d", minutesPart, secondsPart)
12 | }
13 |
14 | fun Int.formatToMinutesAndSeconds(): String {
15 | val minutes = this
16 | val seconds = 0
17 | return String.format("%02d:%02d", minutes, seconds)
18 | }
19 |
--------------------------------------------------------------------------------
/presentation/src/main/res/drawable/ic_alert.xml:
--------------------------------------------------------------------------------
1 |
6 |
11 |
17 |
18 |
--------------------------------------------------------------------------------
/presentation/src/main/res/drawable/ic_arrow_down.xml:
--------------------------------------------------------------------------------
1 |
6 |
13 |
14 |
--------------------------------------------------------------------------------
/presentation/src/main/res/drawable/ic_arrow_left.xml:
--------------------------------------------------------------------------------
1 |
6 |
13 |
14 |
--------------------------------------------------------------------------------
/presentation/src/main/res/drawable/ic_arrow_right.xml:
--------------------------------------------------------------------------------
1 |
6 |
13 |
14 |
--------------------------------------------------------------------------------
/presentation/src/main/res/drawable/ic_arrow_up.xml:
--------------------------------------------------------------------------------
1 |
6 |
13 |
14 |
--------------------------------------------------------------------------------
/presentation/src/main/res/drawable/ic_asterisk.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/presentation/src/main/res/drawable/ic_box_pen.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
15 |
16 |
--------------------------------------------------------------------------------
/presentation/src/main/res/drawable/ic_brifecase.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
12 |
16 |
17 |
--------------------------------------------------------------------------------
/presentation/src/main/res/drawable/ic_bubble_ellipses.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/presentation/src/main/res/drawable/ic_category_default.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/presentation/src/main/res/drawable/ic_chart_bar.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/presentation/src/main/res/drawable/ic_chart_bar_fill.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/presentation/src/main/res/drawable/ic_check.xml:
--------------------------------------------------------------------------------
1 |
6 |
13 |
14 |
--------------------------------------------------------------------------------
/presentation/src/main/res/drawable/ic_check_circle.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
13 |
14 |
--------------------------------------------------------------------------------
/presentation/src/main/res/drawable/ic_chevron_down.xml:
--------------------------------------------------------------------------------
1 |
6 |
13 |
14 |
--------------------------------------------------------------------------------
/presentation/src/main/res/drawable/ic_chevron_left.xml:
--------------------------------------------------------------------------------
1 |
6 |
13 |
14 |
--------------------------------------------------------------------------------
/presentation/src/main/res/drawable/ic_chevron_right.xml:
--------------------------------------------------------------------------------
1 |
6 |
13 |
14 |
--------------------------------------------------------------------------------
/presentation/src/main/res/drawable/ic_chevron_up.xml:
--------------------------------------------------------------------------------
1 |
6 |
13 |
14 |
--------------------------------------------------------------------------------
/presentation/src/main/res/drawable/ic_circle.xml:
--------------------------------------------------------------------------------
1 |
6 |
10 |
11 |
--------------------------------------------------------------------------------
/presentation/src/main/res/drawable/ic_clock.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
12 |
15 |
22 |
23 |
--------------------------------------------------------------------------------
/presentation/src/main/res/drawable/ic_close.xml:
--------------------------------------------------------------------------------
1 |
6 |
13 |
14 |
--------------------------------------------------------------------------------
/presentation/src/main/res/drawable/ic_dumbbell.xml:
--------------------------------------------------------------------------------
1 |
6 |
10 |
14 |
15 |
--------------------------------------------------------------------------------
/presentation/src/main/res/drawable/ic_ellipsis.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
12 |
15 |
16 |
--------------------------------------------------------------------------------
/presentation/src/main/res/drawable/ic_error.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
12 |
16 |
19 |
20 |
--------------------------------------------------------------------------------
/presentation/src/main/res/drawable/ic_feedback.xml:
--------------------------------------------------------------------------------
1 |
6 |
13 |
16 |
19 |
22 |
23 |
--------------------------------------------------------------------------------
/presentation/src/main/res/drawable/ic_fire.xml:
--------------------------------------------------------------------------------
1 |
6 |
13 |
18 |
19 |
--------------------------------------------------------------------------------
/presentation/src/main/res/drawable/ic_house.xml:
--------------------------------------------------------------------------------
1 |
6 |
10 |
11 |
--------------------------------------------------------------------------------
/presentation/src/main/res/drawable/ic_house_fill.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/presentation/src/main/res/drawable/ic_lightning.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/presentation/src/main/res/drawable/ic_lock.xml:
--------------------------------------------------------------------------------
1 |
6 |
10 |
11 |
--------------------------------------------------------------------------------
/presentation/src/main/res/drawable/ic_menu.xml:
--------------------------------------------------------------------------------
1 |
6 |
13 |
14 |
--------------------------------------------------------------------------------
/presentation/src/main/res/drawable/ic_minus.xml:
--------------------------------------------------------------------------------
1 |
6 |
13 |
14 |
--------------------------------------------------------------------------------
/presentation/src/main/res/drawable/ic_monitor.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
14 |
17 |
18 |
--------------------------------------------------------------------------------
/presentation/src/main/res/drawable/ic_moon.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/presentation/src/main/res/drawable/ic_null.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
9 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/presentation/src/main/res/drawable/ic_open_book.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
9 |
12 |
15 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/presentation/src/main/res/drawable/ic_pen.xml:
--------------------------------------------------------------------------------
1 |
6 |
10 |
11 |
--------------------------------------------------------------------------------
/presentation/src/main/res/drawable/ic_play.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/presentation/src/main/res/drawable/ic_plus.xml:
--------------------------------------------------------------------------------
1 |
6 |
13 |
14 |
--------------------------------------------------------------------------------
/presentation/src/main/res/drawable/ic_static_ready.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
9 |
12 |
15 |
18 |
21 |
24 |
27 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/presentation/src/main/res/drawable/ic_trashcan.xml:
--------------------------------------------------------------------------------
1 |
6 |
10 |
13 |
14 |
--------------------------------------------------------------------------------
/presentation/src/main/res/drawable/ic_user.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/presentation/src/main/res/drawable/ic_user_fill.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/presentation/src/main/res/drawable/img_touch_hair_ball.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/PomoNyang-Android/b0d194d192747a7fe1abc9a5d9bffea357610a7f/presentation/src/main/res/drawable/img_touch_hair_ball.png
--------------------------------------------------------------------------------
/presentation/src/main/res/font/pretendard_bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/PomoNyang-Android/b0d194d192747a7fe1abc9a5d9bffea357610a7f/presentation/src/main/res/font/pretendard_bold.ttf
--------------------------------------------------------------------------------
/presentation/src/main/res/font/pretendard_regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/PomoNyang-Android/b0d194d192747a7fe1abc9a5d9bffea357610a7f/presentation/src/main/res/font/pretendard_regular.ttf
--------------------------------------------------------------------------------
/presentation/src/main/res/font/pretendard_semibold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/PomoNyang-Android/b0d194d192747a7fe1abc9a5d9bffea357610a7f/presentation/src/main/res/font/pretendard_semibold.ttf
--------------------------------------------------------------------------------
/presentation/src/main/res/layout/notification_pomodoro_standard.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
13 |
14 |
18 |
19 |
24 |
25 |
26 |
27 |
31 |
--------------------------------------------------------------------------------
/presentation/src/main/res/raw/alarm.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/PomoNyang-Android/b0d194d192747a7fe1abc9a5d9bffea357610a7f/presentation/src/main/res/raw/alarm.mp3
--------------------------------------------------------------------------------
/presentation/src/main/res/raw/cat_focus.riv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/PomoNyang-Android/b0d194d192747a7fe1abc9a5d9bffea357610a7f/presentation/src/main/res/raw/cat_focus.riv
--------------------------------------------------------------------------------
/presentation/src/main/res/raw/cat_home.riv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/PomoNyang-Android/b0d194d192747a7fe1abc9a5d9bffea357610a7f/presentation/src/main/res/raw/cat_home.riv
--------------------------------------------------------------------------------
/presentation/src/main/res/raw/cat_rename_2.riv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/PomoNyang-Android/b0d194d192747a7fe1abc9a5d9bffea357610a7f/presentation/src/main/res/raw/cat_rename_2.riv
--------------------------------------------------------------------------------
/presentation/src/main/res/raw/cat_rest.riv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/PomoNyang-Android/b0d194d192747a7fe1abc9a5d9bffea357610a7f/presentation/src/main/res/raw/cat_rest.riv
--------------------------------------------------------------------------------
/presentation/src/main/res/raw/cat_select_2.riv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/PomoNyang-Android/b0d194d192747a7fe1abc9a5d9bffea357610a7f/presentation/src/main/res/raw/cat_select_2.riv
--------------------------------------------------------------------------------
/presentation/src/main/res/values/arrays.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | - @drawable/onboarding_contents_1
5 | - @drawable/onboarding_contents_2
6 | - @drawable/onboarding_contents_3
7 |
8 |
9 |
--------------------------------------------------------------------------------
/presentation/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #E46900
4 | #000000
5 | #8F887E
6 | #3D3732
7 | #FF7E65
8 | #1D1B1B
9 | #3F4946
10 |
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | includeBuild("build-logic")
3 | repositories {
4 | google {
5 | content {
6 | includeGroupByRegex("com\\.android.*")
7 | includeGroupByRegex("com\\.google.*")
8 | includeGroupByRegex("androidx.*")
9 | }
10 | }
11 | mavenCentral()
12 | gradlePluginPortal()
13 | }
14 | }
15 | dependencyResolutionManagement {
16 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
17 | repositories {
18 | google()
19 | mavenCentral()
20 | }
21 | }
22 |
23 | rootProject.name = "moha-nyang"
24 | include(":app")
25 | include(":presentation")
26 | include(":data")
27 | include(":domain")
28 |
--------------------------------------------------------------------------------