├── .editorconfig ├── .github ├── ISSUE_TEMPLATE │ └── clody-issue-template.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── android_cd.yml │ └── android_ci.yml ├── .gitignore ├── README.md ├── app ├── .gitignore ├── build.gradle.kts ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── sopt │ │ └── clody │ │ └── ExampleInstrumentedTest.kt │ ├── debug │ └── res │ │ ├── values-ko │ │ └── strings.xml │ │ └── values │ │ └── strings.xml │ ├── main │ ├── AndroidManifest.xml │ ├── ic_launcher-playstore.png │ ├── ic_launcher_dev-playstore.png │ ├── java │ │ └── com │ │ │ └── sopt │ │ │ └── clody │ │ │ ├── ClodyApplication.kt │ │ │ ├── ClodyDeeplinkActivity.kt │ │ │ ├── ClodyFirebaseMessagingService.kt │ │ │ ├── core │ │ │ ├── ad │ │ │ │ └── RewardAdShower.kt │ │ │ ├── fcm │ │ │ │ └── FcmTokenProvider.kt │ │ │ ├── login │ │ │ │ ├── KakaoAccessToken.kt │ │ │ │ ├── KakaoLoginSdk.kt │ │ │ │ ├── LoginAccessToken.kt │ │ │ │ ├── LoginException.kt │ │ │ │ ├── LoginModule.kt │ │ │ │ └── LoginSdk.kt │ │ │ ├── network │ │ │ │ ├── NetworkConnectivityModule.kt │ │ │ │ ├── NetworkConnectivityObserver.kt │ │ │ │ └── NetworkStatus.kt │ │ │ ├── review │ │ │ │ └── InAppReviewManager.kt │ │ │ └── security │ │ │ │ ├── DefaultLoginSecurityChecker.kt │ │ │ │ ├── LoginSecurityChecker.kt │ │ │ │ └── SecurityModule.kt │ │ │ ├── data │ │ │ ├── ad │ │ │ │ └── RewardAdShowerImpl.kt │ │ │ ├── datastore │ │ │ │ ├── DataStoreModule.kt │ │ │ │ ├── OAuthDataStore.kt │ │ │ │ ├── OAuthDataStoreKeys.kt │ │ │ │ ├── OAuthProvider.kt │ │ │ │ ├── TokenDataStore.kt │ │ │ │ └── TokenDataStoreImpl.kt │ │ │ ├── local │ │ │ │ ├── datasource │ │ │ │ │ ├── AppReviewLocalDataSource.kt │ │ │ │ │ ├── FirstDraftLocalDataSource.kt │ │ │ │ │ └── LocalDataSource.kt │ │ │ │ └── datasourceimpl │ │ │ │ │ ├── AppReviewLocalDataSourceImpl.kt │ │ │ │ │ ├── FirstDraftLocalDataSourceImpl.kt │ │ │ │ │ └── LocalDataSourceImpl.kt │ │ │ ├── remote │ │ │ │ ├── api │ │ │ │ │ ├── AccountManagementService.kt │ │ │ │ │ ├── AdService.kt │ │ │ │ │ ├── AuthService.kt │ │ │ │ │ ├── DiaryService.kt │ │ │ │ │ ├── NotificationService.kt │ │ │ │ │ └── TokenReissueService.kt │ │ │ │ ├── appupdate │ │ │ │ │ └── AppUpdateCheckerImpl.kt │ │ │ │ ├── datasource │ │ │ │ │ ├── AccountManagementDataSource.kt │ │ │ │ │ ├── AdRemoteDataSource.kt │ │ │ │ │ ├── AuthDataSource.kt │ │ │ │ │ ├── DiaryRemoteDataSource.kt │ │ │ │ │ ├── NotificationDataSource.kt │ │ │ │ │ ├── RemoteConfigDataSource.kt │ │ │ │ │ └── TokenReissueDataSource.kt │ │ │ │ ├── datasourceimpl │ │ │ │ │ ├── AccountManagementDataSourceImpl.kt │ │ │ │ │ ├── AuthDataSourceImpl.kt │ │ │ │ │ ├── DiaryRemoteDataSourceImpl.kt │ │ │ │ │ ├── NotificationDataSourceImpl.kt │ │ │ │ │ └── TokenReissueDataSourceImpl.kt │ │ │ │ ├── dto │ │ │ │ │ ├── base │ │ │ │ │ │ ├── ApiResponse.kt │ │ │ │ │ │ └── NullableApiResponse.kt │ │ │ │ │ ├── request │ │ │ │ │ │ ├── AdRequestDto.kt │ │ │ │ │ │ ├── GoogleSignUpRequestDto.kt │ │ │ │ │ │ ├── LoginRequestDto.kt │ │ │ │ │ │ ├── ModifyNicknameRequestDto.kt │ │ │ │ │ │ ├── SaveDraftDiaryRequestDto.kt │ │ │ │ │ │ ├── SendNotificationRequestDto.kt │ │ │ │ │ │ ├── SignUpRequestDto.kt │ │ │ │ │ │ └── WriteDiaryRequestDto.kt │ │ │ │ │ └── response │ │ │ │ │ │ ├── DailyDiariesResponseDto.kt │ │ │ │ │ │ ├── DiaryTimeResponseDto.kt │ │ │ │ │ │ ├── DraftDiariesResponseDto.kt │ │ │ │ │ │ ├── LoginResponseDto.kt │ │ │ │ │ │ ├── ModifyNicknameResponseDto.kt │ │ │ │ │ │ ├── MonthlyCalendarResponseDto.kt │ │ │ │ │ │ ├── MonthlyDiaryResponseDto.kt │ │ │ │ │ │ ├── NotificationInfoResponseDto.kt │ │ │ │ │ │ ├── ReplyDiaryResponseDto.kt │ │ │ │ │ │ ├── SendNotificationResponseDto.kt │ │ │ │ │ │ ├── SignUpResponseDto.kt │ │ │ │ │ │ ├── TokenReissueResponseDto.kt │ │ │ │ │ │ ├── UserInfoResponseDto.kt │ │ │ │ │ │ └── WriteDiaryResponseDto.kt │ │ │ │ └── util │ │ │ │ │ ├── ApiError.kt │ │ │ │ │ ├── AuthInterceptor.kt │ │ │ │ │ ├── HandleApiResponse.kt │ │ │ │ │ ├── SafeApiCall.kt │ │ │ │ │ └── TimeZoneInterceptor.kt │ │ │ └── repositoryimpl │ │ │ │ ├── AccountManagementRepositoryImpl.kt │ │ │ │ ├── AdRepositoryImpl.kt │ │ │ │ ├── AuthRepositoryImpl.kt │ │ │ │ ├── DiaryRepositoryImpl.kt │ │ │ │ ├── DraftRepositoryImpl.kt │ │ │ │ ├── NotificationRepositoryImpl.kt │ │ │ │ ├── ReviewRepositoryImpl.kt │ │ │ │ ├── TokenReissueRepositoryImpl.kt │ │ │ │ └── TokenRepositoryImpl.kt │ │ │ ├── di │ │ │ ├── AdModule.kt │ │ │ ├── ApiModule.kt │ │ │ ├── AppUpdateModule.kt │ │ │ ├── LocalDataSourceModule.kt │ │ │ ├── NetworkModule.kt │ │ │ ├── RemoteDataSourceModule.kt │ │ │ ├── RepositoryModule.kt │ │ │ ├── SharedPreferencesModule.kt │ │ │ └── qualifier │ │ │ │ └── Qualifier.kt │ │ │ ├── domain │ │ │ ├── appupdate │ │ │ │ └── AppUpdateChecker.kt │ │ │ ├── model │ │ │ │ ├── AppUpdateState.kt │ │ │ │ ├── CalendarMonthlyInfo.kt │ │ │ │ ├── DailyDiaryInfo.kt │ │ │ │ └── DraftDiaryContents.kt │ │ │ ├── repository │ │ │ │ ├── AccountManagementRepository.kt │ │ │ │ ├── AdRepository.kt │ │ │ │ ├── AuthRepository.kt │ │ │ │ ├── DiaryRepository.kt │ │ │ │ ├── DraftRepository.kt │ │ │ │ ├── NotificationRepository.kt │ │ │ │ ├── ReviewRepository.kt │ │ │ │ ├── TokenReissueRepository.kt │ │ │ │ └── TokenRepository.kt │ │ │ ├── type │ │ │ │ ├── Notification.kt │ │ │ │ └── ReplyStatus.kt │ │ │ ├── usecase │ │ │ │ ├── FetchDraftDiaryUseCase.kt │ │ │ │ ├── SaveDraftDiaryUseCase.kt │ │ │ │ └── WriteDiaryUseCase.kt │ │ │ └── util │ │ │ │ └── VersionComparator.kt │ │ │ ├── presentation │ │ │ ├── di │ │ │ │ ├── LanguageModule.kt │ │ │ │ └── ViewModelsModule.kt │ │ │ ├── ui │ │ │ │ ├── auth │ │ │ │ │ ├── component │ │ │ │ │ │ ├── button │ │ │ │ │ │ │ ├── GoogleButton.kt │ │ │ │ │ │ │ └── KaKaoButton.kt │ │ │ │ │ │ ├── checkbox │ │ │ │ │ │ │ └── CustomCheckBox.kt │ │ │ │ │ │ ├── container │ │ │ │ │ │ │ └── PickerBox.kt │ │ │ │ │ │ ├── textfield │ │ │ │ │ │ │ └── NickNameTextField.kt │ │ │ │ │ │ └── timepicker │ │ │ │ │ │ │ └── BottomSheetTimePicker.kt │ │ │ │ │ ├── guide │ │ │ │ │ │ ├── GuideScreen.kt │ │ │ │ │ │ └── navigation │ │ │ │ │ │ │ └── GuideNavigation.kt │ │ │ │ │ ├── signup │ │ │ │ │ │ ├── NicknameMessage.kt │ │ │ │ │ │ ├── SignUpContract.kt │ │ │ │ │ │ ├── SignUpScreen.kt │ │ │ │ │ │ ├── SignUpViewModel.kt │ │ │ │ │ │ ├── navigation │ │ │ │ │ │ │ └── SignUpNavigation.kt │ │ │ │ │ │ └── page │ │ │ │ │ │ │ ├── NicknamePage.kt │ │ │ │ │ │ │ └── TermsOfServicePage.kt │ │ │ │ │ └── timereminder │ │ │ │ │ │ ├── TimeReminderNavigation.kt │ │ │ │ │ │ ├── TimeReminderScreen.kt │ │ │ │ │ │ ├── TimeReminderState.kt │ │ │ │ │ │ └── TimeReminderViewModel.kt │ │ │ │ ├── component │ │ │ │ │ ├── FailureScreen.kt │ │ │ │ │ ├── LoadingScreen.kt │ │ │ │ │ ├── bottomsheet │ │ │ │ │ │ ├── ClodyBottomSheet.kt │ │ │ │ │ │ └── DiaryDeleteSheet.kt │ │ │ │ │ ├── button │ │ │ │ │ │ ├── ClodyButton.kt │ │ │ │ │ │ └── ClodyReplyButton.kt │ │ │ │ │ ├── dialog │ │ │ │ │ │ ├── ClodyDialog.kt │ │ │ │ │ │ ├── FailureDialog.kt │ │ │ │ │ │ └── InspectionDialog.kt │ │ │ │ │ ├── popup │ │ │ │ │ │ └── ClodyPopupBottomSheet.kt │ │ │ │ │ ├── timepicker │ │ │ │ │ │ ├── ClodyPicker.kt │ │ │ │ │ │ ├── PickerState.kt │ │ │ │ │ │ └── YearMonthPicker.kt │ │ │ │ │ └── toast │ │ │ │ │ │ └── ClodyToastMessage.kt │ │ │ │ ├── diarylist │ │ │ │ │ ├── component │ │ │ │ │ │ ├── DailyDiaryCard.kt │ │ │ │ │ │ ├── DailyDiaryCardContent.kt │ │ │ │ │ │ ├── DiaryListTopAppBar.kt │ │ │ │ │ │ ├── EmptyDiaryList.kt │ │ │ │ │ │ └── MonthlyDiaryList.kt │ │ │ │ │ ├── navigation │ │ │ │ │ │ └── DiaryListNavigation.kt │ │ │ │ │ └── screen │ │ │ │ │ │ ├── DiaryDeleteState.kt │ │ │ │ │ │ ├── DiaryListScreen.kt │ │ │ │ │ │ ├── DiaryListState.kt │ │ │ │ │ │ └── DiaryListViewModel.kt │ │ │ │ ├── home │ │ │ │ │ ├── component │ │ │ │ │ │ ├── DailyDiary.kt │ │ │ │ │ │ ├── DailyStateButton.kt │ │ │ │ │ │ ├── HomeTopAppBar.kt │ │ │ │ │ │ ├── MonthlyCalendar.kt │ │ │ │ │ │ └── MonthlyCalendarAndDailyDiary.kt │ │ │ │ │ ├── navigation │ │ │ │ │ │ └── HomeNavigation.kt │ │ │ │ │ └── screen │ │ │ │ │ │ ├── HomeContract.kt │ │ │ │ │ │ ├── HomeScreen.kt │ │ │ │ │ │ └── HomeViewModel.kt │ │ │ │ ├── login │ │ │ │ │ ├── GoogleSignInHelper.kt │ │ │ │ │ ├── LoginContract.kt │ │ │ │ │ ├── LoginScreen.kt │ │ │ │ │ ├── LoginType.kt │ │ │ │ │ ├── LoginViewModel.kt │ │ │ │ │ └── navigation │ │ │ │ │ │ └── LoginNavigation.kt │ │ │ │ ├── main │ │ │ │ │ ├── ClodyApp.kt │ │ │ │ │ ├── ClodyAppState.kt │ │ │ │ │ ├── ClodyNavHost.kt │ │ │ │ │ └── MainActivity.kt │ │ │ │ ├── replydiary │ │ │ │ │ ├── CloverDialog.kt │ │ │ │ │ ├── ReplyDiaryScreen.kt │ │ │ │ │ ├── ReplyDiaryState.kt │ │ │ │ │ ├── ReplyDiaryViewModel.kt │ │ │ │ │ └── navigation │ │ │ │ │ │ └── ReplyDiaryNavigation.kt │ │ │ │ ├── replyloading │ │ │ │ │ ├── component │ │ │ │ │ │ ├── LottieAnimation.kt │ │ │ │ │ │ └── QuickReplyAdButton.kt │ │ │ │ │ ├── navigation │ │ │ │ │ │ └── ReplyLoadingNavigation.kt │ │ │ │ │ └── screen │ │ │ │ │ │ ├── ReplyLoadingScreen.kt │ │ │ │ │ │ ├── ReplyLoadingState.kt │ │ │ │ │ │ └── ReplyLoadingViewModel.kt │ │ │ │ ├── setting │ │ │ │ │ ├── component │ │ │ │ │ │ ├── AccountManagementLogoutOption.kt │ │ │ │ │ │ ├── AccountManagementNicknameOption.kt │ │ │ │ │ │ ├── AccountManagementRevokeOption.kt │ │ │ │ │ │ ├── LogoutDialog.kt │ │ │ │ │ │ ├── NicknameChangeBottomSheet.kt │ │ │ │ │ │ ├── NicknameChangeTextField.kt │ │ │ │ │ │ ├── SettingOption.kt │ │ │ │ │ │ ├── SettingSeparateLine.kt │ │ │ │ │ │ ├── SettingTopAppBar.kt │ │ │ │ │ │ └── SettingVersionInfo.kt │ │ │ │ │ ├── navigation │ │ │ │ │ │ └── SettingNavigation.kt │ │ │ │ │ ├── notificationsetting │ │ │ │ │ │ ├── component │ │ │ │ │ │ │ ├── NotificationSettingTimePicker.kt │ │ │ │ │ │ │ ├── NotificationSwitch.kt │ │ │ │ │ │ │ └── NotificationTimeSelector.kt │ │ │ │ │ │ └── screen │ │ │ │ │ │ │ ├── NotificationChangeState.kt │ │ │ │ │ │ │ ├── NotificationInfoState.kt │ │ │ │ │ │ │ ├── NotificationSettingScreen.kt │ │ │ │ │ │ │ ├── NotificationSettingViewModel.kt │ │ │ │ │ │ │ └── NotificationTimeChangeState.kt │ │ │ │ │ └── screen │ │ │ │ │ │ ├── AccountManagementScreen.kt │ │ │ │ │ │ ├── AccountManagementViewModel.kt │ │ │ │ │ │ ├── LogOutState.kt │ │ │ │ │ │ ├── RevokeAccountState.kt │ │ │ │ │ │ ├── SettingOptionUrls.kt │ │ │ │ │ │ ├── SettingScreen.kt │ │ │ │ │ │ ├── SettingViewModel.kt │ │ │ │ │ │ ├── UserInfoState.kt │ │ │ │ │ │ └── UserNicknameState.kt │ │ │ │ ├── splash │ │ │ │ │ ├── SplashContract.kt │ │ │ │ │ ├── SplashScreen.kt │ │ │ │ │ ├── SplashViewModel.kt │ │ │ │ │ └── navigation │ │ │ │ │ │ └── SplashNavigation.kt │ │ │ │ ├── type │ │ │ │ │ ├── DailyCloverType.kt │ │ │ │ │ └── DailyStateButtonType.kt │ │ │ │ └── writediary │ │ │ │ │ ├── component │ │ │ │ │ ├── bottomsheet │ │ │ │ │ │ └── DeleteWriteDiaryBottomSheet.kt │ │ │ │ │ ├── button │ │ │ │ │ │ ├── AddDiaryEntryFAB.kt │ │ │ │ │ │ └── SendButton.kt │ │ │ │ │ ├── textfield │ │ │ │ │ │ └── WriteDiaryTextField.kt │ │ │ │ │ ├── tooltip │ │ │ │ │ │ ├── BubbleLayout.kt │ │ │ │ │ │ ├── CalculateTooltipPopupPosition.kt │ │ │ │ │ │ ├── DisplayTooltipPopup.kt │ │ │ │ │ │ ├── NoRippleClickable.kt │ │ │ │ │ │ ├── TooltipAlignment.kt │ │ │ │ │ │ ├── TooltipIcon.kt │ │ │ │ │ │ ├── TooltipPopupPosition.kt │ │ │ │ │ │ ├── TooltipPositionProvider.kt │ │ │ │ │ │ └── TooltopPopUp.kt │ │ │ │ │ └── topbar │ │ │ │ │ │ └── WriteDiaryTopBar.kt │ │ │ │ │ ├── navigation │ │ │ │ │ └── WriteDiaryNavigation.kt │ │ │ │ │ └── screen │ │ │ │ │ ├── WriteDiaryScreen.kt │ │ │ │ │ ├── WriteDiaryState.kt │ │ │ │ │ └── WriteDiaryViewModel.kt │ │ │ └── utils │ │ │ │ ├── OpenExternalBrowser.kt │ │ │ │ ├── amplitude │ │ │ │ ├── AmplitudeConstraints.kt │ │ │ │ └── AmplitudeUtils.kt │ │ │ │ ├── appupdate │ │ │ │ └── AppUpdateUtils.kt │ │ │ │ ├── base │ │ │ │ ├── ClodyPreview.kt │ │ │ │ ├── UiLoadState.kt │ │ │ │ └── UiState.kt │ │ │ │ ├── extension │ │ │ │ ├── DateLabelExtension.kt │ │ │ │ ├── GetDayOfWeek.kt │ │ │ │ ├── LifeCycle.kt │ │ │ │ ├── ModifierExt.kt │ │ │ │ ├── ThrottleExtensions.kt │ │ │ │ ├── TimePeriod.kt │ │ │ │ ├── TimeZoneExt.kt │ │ │ │ └── YearMonthLabelUtil.kt │ │ │ │ ├── language │ │ │ │ ├── LanguageProvider.kt │ │ │ │ └── LanguageProviderImpl.kt │ │ │ │ ├── navigation │ │ │ │ ├── NavControllerExtensions.kt │ │ │ │ └── Route.kt │ │ │ │ └── network │ │ │ │ ├── ErrorMessageModule.kt │ │ │ │ ├── ErrorMessageProvider.kt │ │ │ │ └── ErrorMessageProviderImpl.kt │ │ │ └── ui │ │ │ └── theme │ │ │ ├── Color.kt │ │ │ ├── Theme.kt │ │ │ └── Type.kt │ └── res │ │ ├── drawable-ko │ │ └── img_splash_logo.png │ │ ├── drawable-xhdpi │ │ ├── img_account_management_kakao.png │ │ ├── img_failure_screen_clody.png │ │ ├── img_guide_first.png │ │ ├── img_guide_fourth.png │ │ ├── img_guide_second.png │ │ ├── img_guide_third.png │ │ ├── img_loading_complete.png │ │ ├── img_loading_wait.png │ │ └── img_reply_logo.png │ │ ├── drawable-xxhdpi │ │ ├── img_account_management_kakao.png │ │ ├── img_failure_screen_clody.png │ │ ├── img_guide_first.png │ │ ├── img_guide_fourth.png │ │ ├── img_guide_second.png │ │ ├── img_guide_third.png │ │ ├── img_loading_complete.png │ │ ├── img_loading_wait.png │ │ └── img_reply_logo.png │ │ ├── drawable │ │ ├── ic__signup_title.xml │ │ ├── ic_account_management_clover.xml │ │ ├── ic_bottomsheet_trash.xml │ │ ├── ic_dialog_clover.xml │ │ ├── ic_diary_delete_trash.xml │ │ ├── ic_failure_dialog.xml │ │ ├── ic_home_bottom_clover.xml │ │ ├── ic_home_disabled_reply_clover.xml │ │ ├── ic_home_draft_saved_clover.xml │ │ ├── ic_home_enabled_diary_clover.xml │ │ ├── ic_home_kebab.xml │ │ ├── ic_home_list.xml │ │ ├── ic_home_mid_clover.xml │ │ ├── ic_home_setting.xml │ │ ├── ic_home_top_clover.xml │ │ ├── ic_home_under_arrow.xml │ │ ├── ic_home_ungiven_clover.xml │ │ ├── ic_home_unread_reply.xml │ │ ├── ic_home_waiting_reply_clover.xml │ │ ├── ic_launcher_dev_background.xml │ │ ├── ic_launcher_foreground.xml │ │ ├── ic_listview_arrow_down.xml │ │ ├── ic_listview_calendar.xml │ │ ├── ic_listview_clover.xml │ │ ├── ic_listview_delete.xml │ │ ├── ic_listview_kebab_menu.xml │ │ ├── ic_logo.xml │ │ ├── ic_nickname_back.xml │ │ ├── ic_nickname_change_clean.xml │ │ ├── ic_nickname_change_dismiss.xml │ │ ├── ic_nickname_delete.xml │ │ ├── ic_not_found.xml │ │ ├── ic_notification_setting_switch_thumb.xml │ │ ├── ic_picker_dismiss.xml │ │ ├── ic_reply_diary_new.xml │ │ ├── ic_replyloading_player.xml │ │ ├── ic_setting_back.xml │ │ ├── ic_setting_next.xml │ │ ├── ic_signup_kakao.xml │ │ ├── ic_signup_logo.xml │ │ ├── ic_signup_logotitle.xml │ │ ├── ic_splash_logo.xml │ │ ├── ic_terms_check_off_23.xml │ │ ├── ic_terms_check_off_25.xml │ │ ├── ic_terms_check_on_23.xml │ │ ├── ic_terms_check_on_25.xml │ │ ├── ic_terms_next.xml │ │ ├── ic_timereminder_down.xml │ │ ├── ic_toast_check_on_18.xml │ │ ├── ic_toast_error.xml │ │ ├── ic_writediary__help_close.xml │ │ ├── ic_writediary_add.xml │ │ ├── ic_writediary_help.xml │ │ ├── ic_writediary_kebab.xml │ │ ├── img_account_management_kakao.png │ │ ├── img_failure_screen_clody.png │ │ ├── img_google_button_logo.png │ │ ├── img_guide_first.png │ │ ├── img_guide_fourth.png │ │ ├── img_guide_second.png │ │ ├── img_guide_third.png │ │ ├── img_inspection_dialog.png │ │ ├── img_loading_complete.png │ │ ├── img_loading_wait.png │ │ ├── img_reply_logo.png │ │ └── img_splash_logo.png │ │ ├── font │ │ ├── pretendard_medium.ttf │ │ ├── pretendard_regular.ttf │ │ └── pretendard_semibold.ttf │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ ├── ic_launcher_dev.xml │ │ ├── ic_launcher_dev_round.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.webp │ │ ├── ic_launcher_dev.webp │ │ ├── ic_launcher_dev_foreground.webp │ │ ├── ic_launcher_dev_round.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.webp │ │ ├── ic_launcher_dev.webp │ │ ├── ic_launcher_dev_foreground.webp │ │ ├── ic_launcher_dev_round.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.webp │ │ ├── ic_launcher_dev.webp │ │ ├── ic_launcher_dev_foreground.webp │ │ ├── ic_launcher_dev_round.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.webp │ │ ├── ic_launcher_dev.webp │ │ ├── ic_launcher_dev_foreground.webp │ │ ├── ic_launcher_dev_round.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.webp │ │ ├── ic_launcher_dev.webp │ │ ├── ic_launcher_dev_foreground.webp │ │ ├── ic_launcher_dev_round.webp │ │ └── ic_launcher_round.webp │ │ ├── raw │ │ ├── excepted_rody.json │ │ └── writing_rody.json │ │ ├── values-ko │ │ └── strings.xml │ │ ├── values │ │ ├── colors.xml │ │ ├── ic_launcher_background.xml │ │ ├── strings.xml │ │ └── themes.xml │ │ └── xml │ │ ├── backup_rules.xml │ │ └── data_extraction_rules.xml │ └── test │ └── java │ └── com │ └── sopt │ └── clody │ ├── ExampleUnitTest.kt │ └── datasource │ └── FakeDiaryRemoteDataSource.kt ├── build.gradle.kts ├── gradle.properties ├── gradle ├── libs.versions.toml └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle.kts /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_size = 4 7 | indent_style = space 8 | max_line_length = 150 9 | insert_final_newline = true 10 | trim_trailing_whitespace = true 11 | 12 | [*.{kt,kts}] 13 | ij_kotlin_allow_trailing_comma = true 14 | ij_kotlin_allow_trailing_comma_on_call_site = true -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/clody-issue-template.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: clody issue template 3 | about: Describe this issue template's purpose here. 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## 💡 Issue 11 | 12 | 13 | ## 📝 todo 14 | 15 | - [ ] 16 | - [ ] 17 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## 📌 ISSUE 2 | 3 | closed # 4 | 5 | ## 📄 Work Description 6 | 7 | 8 | ## ✨ PR Point 9 | 10 | 11 | 12 | ## 📸 ScreenShot/Video 13 | 14 | -------------------------------------------------------------------------------- /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 22 | 23 | # Firebase Crashlytics 24 | -keepattributes SourceFile,LineNumberTable 25 | -keep public class * extends java.lang.Exception 26 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/sopt/clody/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody 2 | 3 | import androidx.test.ext.junit.runners.AndroidJUnit4 4 | import androidx.test.platform.app.InstrumentationRegistry 5 | import org.junit.Assert.assertEquals 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | /** 10 | * Instrumented test, which will execute on an Android device. 11 | * 12 | * See [testing documentation](http://d.android.com/tools/testing). 13 | */ 14 | @RunWith(AndroidJUnit4::class) 15 | class ExampleInstrumentedTest { 16 | @Test 17 | fun useAppContext() { 18 | // Context of the app under test. 19 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 20 | assertEquals("com.sopt.clody", appContext.packageName) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/src/debug/res/values-ko/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 돈키 4 | 5 | -------------------------------------------------------------------------------- /app/src/debug/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Donkey 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/ic_launcher-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-Clody/Clody_Android/65643ebe37474c522f7ac125690bc739768a38c0/app/src/main/ic_launcher-playstore.png -------------------------------------------------------------------------------- /app/src/main/ic_launcher_dev-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-Clody/Clody_Android/65643ebe37474c522f7ac125690bc739768a38c0/app/src/main/ic_launcher_dev-playstore.png -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/ClodyApplication.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody 2 | 3 | import android.app.Application 4 | import co.ab180.airbridge.Airbridge 5 | import co.ab180.airbridge.AirbridgeOptionBuilder 6 | import com.airbnb.mvrx.Mavericks 7 | import com.google.firebase.FirebaseApp 8 | import com.kakao.sdk.common.KakaoSdk 9 | import com.sopt.clody.presentation.utils.amplitude.AmplitudeUtils.initAmplitude 10 | import dagger.hilt.android.HiltAndroidApp 11 | import timber.log.Timber 12 | 13 | @HiltAndroidApp 14 | class ClodyApplication : Application() { 15 | override fun onCreate() { 16 | super.onCreate() 17 | Timber.plant(Timber.DebugTree()) 18 | initKakaoSdk() 19 | FirebaseApp.initializeApp(this) 20 | Mavericks.initialize(this) 21 | initAmplitude(applicationContext) 22 | initAirBridge() 23 | } 24 | 25 | private fun initKakaoSdk() { 26 | KakaoSdk.init(this, BuildConfig.KAKAO_API_KEY) 27 | } 28 | 29 | private fun initAirBridge() { 30 | val option = AirbridgeOptionBuilder("clody", "3ba2277abcd044f29356dc0ee32165ff").build() 31 | Airbridge.initializeSDK(this, option) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/ClodyDeeplinkActivity.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody 2 | 3 | import android.os.Bundle 4 | import androidx.appcompat.app.AppCompatActivity 5 | import co.ab180.airbridge.Airbridge 6 | 7 | class ClodyDeeplinkActivity : AppCompatActivity() { 8 | 9 | override fun onCreate(savedInstanceState: Bundle?) { 10 | super.onCreate(savedInstanceState) 11 | } 12 | 13 | override fun onResume() { 14 | super.onResume() 15 | 16 | val isFirstCalled = Airbridge.handleDeferredDeeplink { uri -> 17 | // when handleDeferredDeeplink is called firstly after install 18 | if (uri != null) { 19 | // show proper content using uri (YOUR_SCHEME://...) 20 | } 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/ClodyFirebaseMessagingService.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody 2 | 3 | import android.content.Context 4 | import com.google.firebase.messaging.FirebaseMessagingService 5 | import com.google.firebase.messaging.RemoteMessage 6 | 7 | class ClodyFirebaseMessagingService : FirebaseMessagingService() { 8 | 9 | override fun onNewToken(token: String) { 10 | super.onNewToken(token) 11 | saveTokenToPreferences(token) 12 | } 13 | 14 | override fun onMessageReceived(remoteMessage: RemoteMessage) { 15 | super.onMessageReceived(remoteMessage) 16 | // TODO: 메시지 처리 로직 17 | } 18 | 19 | private fun saveTokenToPreferences(token: String) { 20 | val sharedPreferences = getSharedPreferences("fcm_prefs", Context.MODE_PRIVATE) 21 | val editor = sharedPreferences.edit() 22 | editor.putString("fcm_token", token) 23 | editor.apply() 24 | } 25 | 26 | companion object { 27 | fun getTokenFromPreferences(context: Context): String? { 28 | val sharedPreferences = context.getSharedPreferences("fcm_prefs", Context.MODE_PRIVATE) 29 | return sharedPreferences.getString("fcm_token", null) 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/core/ad/RewardAdShower.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.core.ad 2 | 3 | import android.app.Activity 4 | 5 | interface RewardAdShower { 6 | fun showAd(activity: Activity, onAdRewarded: () -> Unit, onAdDismissed: () -> Unit) 7 | } 8 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/core/fcm/FcmTokenProvider.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.core.fcm 2 | 3 | import com.google.firebase.ktx.Firebase 4 | import com.google.firebase.messaging.ktx.messaging 5 | import kotlinx.coroutines.tasks.await 6 | import timber.log.Timber 7 | import javax.inject.Inject 8 | 9 | class FcmTokenProvider @Inject constructor() { 10 | suspend fun getToken(): String? { 11 | return try { 12 | Firebase.messaging.token.await() 13 | } catch (e: Exception) { 14 | Timber.e("FCM 토큰 수신 실패: ${e.message}") 15 | null 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/core/login/KakaoAccessToken.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.core.login 2 | 3 | @JvmInline 4 | value class KakaoAccessToken(override val value: String) : LoginAccessToken 5 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/core/login/LoginAccessToken.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.core.login 2 | 3 | interface LoginAccessToken { 4 | val value: String 5 | } 6 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/core/login/LoginException.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.core.login 2 | 3 | sealed class LoginException(override val message: String?) : Exception(message) { 4 | class CancelException(message: String?) : LoginException(message) 5 | class AuthException(message: String?) : LoginException(message) 6 | } 7 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/core/login/LoginModule.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.core.login 2 | 3 | import dagger.Binds 4 | import dagger.Module 5 | import dagger.hilt.InstallIn 6 | import dagger.hilt.components.SingletonComponent 7 | 8 | @Module 9 | @InstallIn(SingletonComponent::class) 10 | abstract class LoginModule { 11 | 12 | @Binds 13 | abstract fun bindLoginSdk( 14 | kakaoLoginSdk: KakaoLoginSdk, 15 | ): LoginSdk 16 | } 17 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/core/login/LoginSdk.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.core.login 2 | 3 | import android.content.Context 4 | 5 | interface LoginSdk { 6 | suspend fun login(context: Context): Result 7 | suspend fun logout(): Result 8 | suspend fun unlink(): Result 9 | } 10 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/core/network/NetworkConnectivityModule.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.core.network 2 | 3 | import android.content.Context 4 | import dagger.Module 5 | import dagger.Provides 6 | import dagger.hilt.InstallIn 7 | import dagger.hilt.android.qualifiers.ApplicationContext 8 | import dagger.hilt.components.SingletonComponent 9 | import javax.inject.Singleton 10 | 11 | @Module 12 | @InstallIn(SingletonComponent::class) 13 | object NetworkConnectivityModule { 14 | 15 | @Provides 16 | @Singleton 17 | fun provideNetworkConnectivityObserver( 18 | @ApplicationContext context: Context, 19 | ): NetworkConnectivityObserver { 20 | return NetworkConnectivityObserver(context) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/core/network/NetworkStatus.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.core.network 2 | 3 | sealed class NetworkStatus { 4 | data object Available : NetworkStatus() 5 | data object Unavailable : NetworkStatus() 6 | } 7 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/core/review/InAppReviewManager.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.core.review 2 | 3 | import android.app.Activity 4 | import com.google.android.play.core.review.ReviewManagerFactory 5 | import com.sopt.clody.presentation.utils.appupdate.AppUpdateUtils 6 | import timber.log.Timber 7 | 8 | object InAppReviewManager { 9 | fun showPopup(activity: Activity) { 10 | if (activity.isFinishing || activity.isDestroyed) return 11 | 12 | val reviewManager = ReviewManagerFactory.create(activity) 13 | val request = reviewManager.requestReviewFlow() 14 | 15 | request.addOnCompleteListener { task -> 16 | if (task.isSuccessful) { 17 | val reviewInfo = task.result 18 | reviewManager.launchReviewFlow(activity, reviewInfo) 19 | } else { 20 | try { 21 | AppUpdateUtils.navigateToMarket(activity) 22 | } catch (e: Exception) { 23 | e.printStackTrace() 24 | Timber.e(e, "Failed to open store for app review") 25 | } 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/core/security/LoginSecurityChecker.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.core.security 2 | 3 | import android.content.Context 4 | 5 | interface LoginSecurityChecker { 6 | fun isDeviceRooted(): Boolean 7 | fun isChromeInstalled(context: Context): Boolean 8 | } 9 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/core/security/SecurityModule.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.core.security 2 | 3 | import dagger.Binds 4 | import dagger.Module 5 | import dagger.hilt.InstallIn 6 | import dagger.hilt.components.SingletonComponent 7 | import javax.inject.Singleton 8 | 9 | @Module 10 | @InstallIn(SingletonComponent::class) 11 | abstract class SecurityModule { 12 | 13 | @Binds 14 | @Singleton 15 | abstract fun bindLoginSecurityChecker( 16 | impl: DefaultLoginSecurityChecker, 17 | ): LoginSecurityChecker 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/data/ad/RewardAdShowerImpl.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.data.ad 2 | 3 | import android.app.Activity 4 | import com.google.android.gms.ads.AdError 5 | import com.google.android.gms.ads.FullScreenContentCallback 6 | import com.sopt.clody.core.ad.RewardAdShower 7 | import com.sopt.clody.data.remote.datasource.AdRemoteDataSource 8 | import javax.inject.Inject 9 | 10 | class RewardAdShowerImpl @Inject constructor( 11 | private val adRemoteDataSource: AdRemoteDataSource, 12 | ) : RewardAdShower { 13 | override fun showAd(activity: Activity, onAdRewarded: () -> Unit, onAdDismissed: () -> Unit) { 14 | val rewardedAd = adRemoteDataSource.getRewardedAd() 15 | rewardedAd?.let { ad -> 16 | ad.fullScreenContentCallback = object : FullScreenContentCallback() { 17 | override fun onAdDismissedFullScreenContent() { 18 | onAdDismissed() 19 | } 20 | 21 | override fun onAdFailedToShowFullScreenContent(adError: AdError) { 22 | onAdDismissed() 23 | } 24 | 25 | override fun onAdShowedFullScreenContent() {} 26 | } 27 | 28 | ad.show(activity) { 29 | onAdRewarded() 30 | } 31 | } ?: onAdDismissed() 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/data/datastore/DataStoreModule.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.data.datastore 2 | 3 | import android.content.Context 4 | import dagger.Module 5 | import dagger.Provides 6 | import dagger.hilt.InstallIn 7 | import dagger.hilt.android.qualifiers.ApplicationContext 8 | import dagger.hilt.components.SingletonComponent 9 | import javax.inject.Singleton 10 | 11 | @Module 12 | @InstallIn(SingletonComponent::class) 13 | object DataStoreModule { 14 | 15 | @Provides 16 | @Singleton 17 | fun provideOAuthDataStore( 18 | @ApplicationContext context: Context, 19 | ): OAuthDataStore { 20 | return OAuthDataStore(context) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/data/datastore/OAuthDataStoreKeys.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.data.datastore 2 | 3 | import androidx.datastore.preferences.core.stringPreferencesKey 4 | 5 | object OAuthDataStoreKeys { 6 | val OAUTH_PLATFORM = stringPreferencesKey("oauth_platform") 7 | val GOOGLE_ID_TOKEN = stringPreferencesKey("google_id_token") 8 | val KAKAO_ID_TOKEN = stringPreferencesKey("kakao_id_token") 9 | } 10 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/data/datastore/OAuthProvider.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.data.datastore 2 | 3 | enum class OAuthProvider(val platform: String) { 4 | GOOGLE("google"), 5 | KAKAO("kakao"), 6 | } 7 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/data/datastore/TokenDataStore.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.data.datastore 2 | 3 | interface TokenDataStore { 4 | var accessToken: String 5 | var refreshToken: String 6 | fun clearInfo() 7 | } 8 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/data/datastore/TokenDataStoreImpl.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.data.datastore 2 | 3 | import android.content.SharedPreferences 4 | import com.sopt.clody.di.qualifier.TokenPrefs 5 | import javax.inject.Inject 6 | 7 | class TokenDataStoreImpl @Inject constructor( 8 | @TokenPrefs private val sharedPreferences: SharedPreferences, 9 | ) : TokenDataStore { 10 | override var accessToken: String 11 | get() = sharedPreferences.getString(ACCESS_TOKEN, "") ?: "" 12 | set(value) = sharedPreferences.edit().putString(ACCESS_TOKEN, value).apply() 13 | 14 | override var refreshToken: String 15 | get() = sharedPreferences.getString(REFRESH_TOKEN, "") ?: "" 16 | set(value) = sharedPreferences.edit().putString(REFRESH_TOKEN, value).apply() 17 | 18 | override fun clearInfo() { 19 | sharedPreferences.edit().clear().apply() 20 | } 21 | 22 | companion object { 23 | private const val ACCESS_TOKEN = "ACCESS_TOKEN" 24 | private const val REFRESH_TOKEN = "REFRESH_TOKEN" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/data/local/datasource/AppReviewLocalDataSource.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.data.local.datasource 2 | 3 | interface AppReviewLocalDataSource { 4 | var shouldShowPopup: Boolean 5 | } 6 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/data/local/datasource/FirstDraftLocalDataSource.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.data.local.datasource 2 | 3 | /** 4 | * 임시 저장 최초 사용 여부 판단을 위한 SharedPreferences 5 | * @property isDraftUsed 임시 저장 사용 여부 6 | * @property isFirstUse 임시 저장 최초 사용 여부 7 | */ 8 | interface FirstDraftLocalDataSource { 9 | var isDraftUsed: Boolean 10 | var isFirstUse: Boolean 11 | } 12 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/data/local/datasource/LocalDataSource.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.data.local.datasource 2 | 3 | interface LocalDataSource { 4 | fun getLocalData(): String 5 | } 6 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/data/local/datasourceimpl/AppReviewLocalDataSourceImpl.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.data.local.datasourceimpl 2 | 3 | import android.content.SharedPreferences 4 | import androidx.core.content.edit 5 | import com.sopt.clody.data.local.datasource.AppReviewLocalDataSource 6 | import com.sopt.clody.di.qualifier.ReviewPrefs 7 | import javax.inject.Inject 8 | 9 | class AppReviewLocalDataSourceImpl @Inject constructor( 10 | @ReviewPrefs private val sharedPreferences: SharedPreferences, 11 | ) : AppReviewLocalDataSource { 12 | 13 | override var shouldShowPopup: Boolean 14 | get() = sharedPreferences.getBoolean(SHOULD_SHOW_POPUP, true) 15 | set(value) = sharedPreferences.edit { putBoolean(SHOULD_SHOW_POPUP, value) } 16 | 17 | companion object { 18 | private const val SHOULD_SHOW_POPUP = "shouldShowPopup" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/data/local/datasourceimpl/FirstDraftLocalDataSourceImpl.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.data.local.datasourceimpl 2 | 3 | import android.content.SharedPreferences 4 | import androidx.core.content.edit 5 | import com.sopt.clody.data.local.datasource.FirstDraftLocalDataSource 6 | import com.sopt.clody.di.qualifier.FirstDraftPrefs 7 | import javax.inject.Inject 8 | 9 | class FirstDraftLocalDataSourceImpl @Inject constructor( 10 | @FirstDraftPrefs private val sharedPreferences: SharedPreferences, 11 | ) : FirstDraftLocalDataSource { 12 | override var isDraftUsed: Boolean 13 | get() = sharedPreferences.getBoolean(IS_DRAFT_USED, false) 14 | set(value) = sharedPreferences.edit { putBoolean(IS_DRAFT_USED, value) } 15 | 16 | override var isFirstUse: Boolean 17 | get() = sharedPreferences.getBoolean(IS_FIRST_USE, false) 18 | set(value) = sharedPreferences.edit { putBoolean(IS_FIRST_USE, value) } 19 | 20 | companion object { 21 | private const val IS_DRAFT_USED = "IS_DRAFT_USED" 22 | private const val IS_FIRST_USE = "IS_FIRST_USE" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/data/local/datasourceimpl/LocalDataSourceImpl.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.data.local.datasourceimpl 2 | 3 | import com.sopt.clody.data.local.datasource.LocalDataSource 4 | import javax.inject.Inject 5 | 6 | class LocalDataSourceImpl @Inject constructor() : LocalDataSource { 7 | override fun getLocalData(): String { 8 | return "Local Data" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/data/remote/api/AccountManagementService.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.data.remote.api 2 | 3 | import com.sopt.clody.data.remote.dto.base.ApiResponse 4 | import com.sopt.clody.data.remote.dto.request.ModifyNicknameRequestDto 5 | import com.sopt.clody.data.remote.dto.response.ModifyNicknameResponseDto 6 | import com.sopt.clody.data.remote.dto.response.UserInfoResponseDto 7 | import retrofit2.http.Body 8 | import retrofit2.http.DELETE 9 | import retrofit2.http.GET 10 | import retrofit2.http.PATCH 11 | 12 | interface AccountManagementService { 13 | @GET("api/v1/user/info") 14 | suspend fun getUserInfo(): ApiResponse 15 | 16 | @PATCH("api/v1/user/nickname") 17 | suspend fun modifyNickname( 18 | @Body body: ModifyNicknameRequestDto, 19 | ): ApiResponse 20 | 21 | @DELETE("api/v1/user/revoke") 22 | suspend fun revokeAccount(): ApiResponse 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/data/remote/api/AdService.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.data.remote.api 2 | 3 | import com.sopt.clody.data.remote.dto.base.NullableApiResponse 4 | import com.sopt.clody.data.remote.dto.request.AdRequestDto 5 | import retrofit2.http.Body 6 | import retrofit2.http.PATCH 7 | import retrofit2.http.POST 8 | 9 | interface AdService { 10 | @POST("api/v1/reply/ad/start") 11 | suspend fun startAd( 12 | @Body adRequestDto: AdRequestDto, 13 | ): NullableApiResponse 14 | 15 | @PATCH("api/v1/reply/ad/end") 16 | suspend fun endAd( 17 | @Body adRequestDto: AdRequestDto, 18 | ): NullableApiResponse 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/data/remote/api/AuthService.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.data.remote.api 2 | 3 | import com.sopt.clody.data.remote.dto.base.ApiResponse 4 | import com.sopt.clody.data.remote.dto.request.GoogleSignUpRequestDto 5 | import com.sopt.clody.data.remote.dto.request.LoginRequestDto 6 | import com.sopt.clody.data.remote.dto.request.SignUpRequestDto 7 | import com.sopt.clody.data.remote.dto.response.LoginResponseDto 8 | import com.sopt.clody.data.remote.dto.response.SignUpResponseDto 9 | import retrofit2.http.Body 10 | import retrofit2.http.Header 11 | import retrofit2.http.POST 12 | 13 | interface AuthService { 14 | @POST("api/v1/auth/signin") 15 | suspend fun postLogin( 16 | @Header("Authorization") accessToken: String, 17 | @Body body: LoginRequestDto, 18 | ): ApiResponse 19 | 20 | @POST("api/v1/auth/signup") 21 | suspend fun signUp( 22 | @Header("Authorization") authorization: String, 23 | @Body signUpRequestDto: SignUpRequestDto, 24 | ): ApiResponse 25 | 26 | @POST("api/v1/auth/oauth2/google") 27 | suspend fun signUpWithGoogle( 28 | @Body body: GoogleSignUpRequestDto, 29 | ): ApiResponse 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/data/remote/api/NotificationService.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.data.remote.api 2 | 3 | import com.sopt.clody.data.remote.dto.base.ApiResponse 4 | import com.sopt.clody.data.remote.dto.request.SendNotificationRequestDto 5 | import com.sopt.clody.data.remote.dto.response.NotificationInfoResponseDto 6 | import com.sopt.clody.data.remote.dto.response.SendNotificationResponseDto 7 | import retrofit2.http.Body 8 | import retrofit2.http.GET 9 | import retrofit2.http.POST 10 | 11 | interface NotificationService { 12 | @GET("api/v1/alarm") 13 | suspend fun getNotificationInfo(): ApiResponse 14 | 15 | @POST("api/v1/alarm") 16 | suspend fun sendNotification( 17 | @Body sendNotificationRequestDto: SendNotificationRequestDto, 18 | ): ApiResponse 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/data/remote/api/TokenReissueService.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.data.remote.api 2 | 3 | import com.sopt.clody.data.remote.dto.base.ApiResponse 4 | import com.sopt.clody.data.remote.dto.response.TokenReissueResponseDto 5 | import retrofit2.http.GET 6 | import retrofit2.http.Header 7 | 8 | interface TokenReissueService { 9 | @GET("api/v1/auth/reissue") 10 | suspend fun reissue( 11 | @Header("Authorization") refreshToken: String, 12 | ): ApiResponse 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/data/remote/datasource/AccountManagementDataSource.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.data.remote.datasource 2 | 3 | import com.sopt.clody.data.remote.dto.base.ApiResponse 4 | import com.sopt.clody.data.remote.dto.request.ModifyNicknameRequestDto 5 | import com.sopt.clody.data.remote.dto.response.ModifyNicknameResponseDto 6 | import com.sopt.clody.data.remote.dto.response.UserInfoResponseDto 7 | 8 | interface AccountManagementDataSource { 9 | suspend fun getUserInfo(): ApiResponse 10 | suspend fun modifyNickname(modifyNicknameRequestDto: ModifyNicknameRequestDto): ApiResponse 11 | suspend fun revokeAccount(): ApiResponse 12 | } 13 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/data/remote/datasource/AuthDataSource.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.data.remote.datasource 2 | 3 | import com.sopt.clody.data.remote.dto.base.ApiResponse 4 | import com.sopt.clody.data.remote.dto.request.GoogleSignUpRequestDto 5 | import com.sopt.clody.data.remote.dto.request.LoginRequestDto 6 | import com.sopt.clody.data.remote.dto.request.SignUpRequestDto 7 | import com.sopt.clody.data.remote.dto.response.LoginResponseDto 8 | import com.sopt.clody.data.remote.dto.response.SignUpResponseDto 9 | 10 | interface AuthDataSource { 11 | suspend fun signIn(authorization: String, requestSignInDto: LoginRequestDto): ApiResponse 12 | suspend fun signUp(authorization: String, requestSignUpDto: SignUpRequestDto): ApiResponse 13 | suspend fun signUpWithGoogle(googleSignUpRequestDto: GoogleSignUpRequestDto): ApiResponse 14 | } 15 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/data/remote/datasource/DiaryRemoteDataSource.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.data.remote.datasource 2 | 3 | import com.sopt.clody.data.remote.dto.request.SaveDraftDiaryRequestDto 4 | import com.sopt.clody.data.remote.dto.response.DailyDiariesResponseDto 5 | import com.sopt.clody.data.remote.dto.response.DiaryTimeResponseDto 6 | import com.sopt.clody.data.remote.dto.response.DraftDiariesResponseDto 7 | import com.sopt.clody.data.remote.dto.response.MonthlyCalendarResponseDto 8 | import com.sopt.clody.data.remote.dto.response.MonthlyDiaryResponseDto 9 | import com.sopt.clody.data.remote.dto.response.ReplyDiaryResponseDto 10 | import com.sopt.clody.data.remote.dto.response.WriteDiaryResponseDto 11 | 12 | interface DiaryRemoteDataSource { 13 | suspend fun writeDiary(lang: String, date: String, content: List): Result 14 | suspend fun deleteDailyDiary(year: Int, month: Int, date: Int): Result 15 | suspend fun getDailyDiariesData(year: Int, month: Int, date: Int): Result 16 | suspend fun getDiaryTime(year: Int, month: Int, date: Int): Result 17 | suspend fun getMonthlyCalendarData(year: Int, month: Int): Result 18 | suspend fun getMonthlyDiary(year: Int, month: Int): Result 19 | suspend fun getReplyDiary(year: Int, month: Int, date: Int): Result 20 | suspend fun fetchDraftDiary(year: Int, month: Int, date: Int): Result 21 | suspend fun saveDraftDiary(request: SaveDraftDiaryRequestDto): Result 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/data/remote/datasource/NotificationDataSource.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.data.remote.datasource 2 | 3 | import com.sopt.clody.data.remote.dto.base.ApiResponse 4 | import com.sopt.clody.data.remote.dto.request.SendNotificationRequestDto 5 | import com.sopt.clody.data.remote.dto.response.NotificationInfoResponseDto 6 | import com.sopt.clody.data.remote.dto.response.SendNotificationResponseDto 7 | 8 | interface NotificationDataSource { 9 | suspend fun getNotificationInfo(): ApiResponse 10 | suspend fun sendNotification(sendNotificationRequestDto: SendNotificationRequestDto): ApiResponse 11 | } 12 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/data/remote/datasource/RemoteConfigDataSource.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.data.remote.datasource 2 | 3 | import com.google.firebase.remoteconfig.FirebaseRemoteConfig 4 | import com.sopt.clody.BuildConfig 5 | import kotlinx.coroutines.tasks.await 6 | import java.time.LocalDateTime 7 | import java.time.format.DateTimeFormatter 8 | import javax.inject.Inject 9 | import javax.inject.Singleton 10 | 11 | @Singleton 12 | class RemoteConfigDataSource @Inject constructor( 13 | private val remoteConfig: FirebaseRemoteConfig, 14 | ) { 15 | suspend fun fetch() { 16 | remoteConfig.fetchAndActivate().await() 17 | } 18 | 19 | fun getLatestVersion(): String = 20 | remoteConfig.getString(KEY_LATEST_VERSION).ifEmpty { BuildConfig.VERSION_NAME } 21 | 22 | fun getMinimumVersion(): String = 23 | remoteConfig.getString(KEY_MINIMUM_VERSION).ifEmpty { BuildConfig.VERSION_NAME } 24 | 25 | fun getInspectionStart(): LocalDateTime? = 26 | remoteConfig.getString(KEY_INSPECTION_START).takeIf { it.isNotBlank() }?.let { 27 | runCatching { LocalDateTime.parse(it, formatter) }.getOrNull() 28 | } 29 | 30 | fun getInspectionEnd(): LocalDateTime? = 31 | remoteConfig.getString(KEY_INSPECTION_END).takeIf { it.isNotBlank() }?.let { 32 | runCatching { LocalDateTime.parse(it, formatter) }.getOrNull() 33 | } 34 | 35 | companion object { 36 | private const val KEY_LATEST_VERSION = "latest_version" 37 | private const val KEY_MINIMUM_VERSION = "min_required_version" 38 | private const val KEY_INSPECTION_START = "inspection_start_android" 39 | private const val KEY_INSPECTION_END = "inspection_end_android" 40 | 41 | private val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss") 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/data/remote/datasource/TokenReissueDataSource.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.data.remote.datasource 2 | 3 | import com.sopt.clody.data.remote.dto.base.ApiResponse 4 | import com.sopt.clody.data.remote.dto.response.TokenReissueResponseDto 5 | 6 | interface TokenReissueDataSource { 7 | suspend fun getReissueToken(authorization: String): ApiResponse 8 | } 9 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/data/remote/datasourceimpl/AccountManagementDataSourceImpl.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.data.remote.datasourceimpl 2 | 3 | import com.sopt.clody.data.remote.api.AccountManagementService 4 | import com.sopt.clody.data.remote.datasource.AccountManagementDataSource 5 | import com.sopt.clody.data.remote.dto.base.ApiResponse 6 | import com.sopt.clody.data.remote.dto.request.ModifyNicknameRequestDto 7 | import com.sopt.clody.data.remote.dto.response.ModifyNicknameResponseDto 8 | import com.sopt.clody.data.remote.dto.response.UserInfoResponseDto 9 | import javax.inject.Inject 10 | 11 | class AccountManagementDataSourceImpl @Inject constructor( 12 | private val accountManagementService: AccountManagementService, 13 | ) : AccountManagementDataSource { 14 | override suspend fun getUserInfo(): ApiResponse = 15 | accountManagementService.getUserInfo() 16 | 17 | override suspend fun modifyNickname(modifyNicknameRequestDto: ModifyNicknameRequestDto): ApiResponse = 18 | accountManagementService.modifyNickname(modifyNicknameRequestDto) 19 | 20 | override suspend fun revokeAccount(): ApiResponse = 21 | accountManagementService.revokeAccount() 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/data/remote/datasourceimpl/AuthDataSourceImpl.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.data.remote.datasourceimpl 2 | 3 | import com.sopt.clody.data.remote.api.AuthService 4 | import com.sopt.clody.data.remote.datasource.AuthDataSource 5 | import com.sopt.clody.data.remote.dto.base.ApiResponse 6 | import com.sopt.clody.data.remote.dto.request.GoogleSignUpRequestDto 7 | import com.sopt.clody.data.remote.dto.request.LoginRequestDto 8 | import com.sopt.clody.data.remote.dto.request.SignUpRequestDto 9 | import com.sopt.clody.data.remote.dto.response.LoginResponseDto 10 | import com.sopt.clody.data.remote.dto.response.SignUpResponseDto 11 | import javax.inject.Inject 12 | 13 | class AuthDataSourceImpl @Inject constructor( 14 | private val authService: AuthService, 15 | ) : AuthDataSource { 16 | override suspend fun signIn(authorization: String, requestSignInDto: LoginRequestDto): ApiResponse = 17 | authService.postLogin(authorization, requestSignInDto) 18 | 19 | override suspend fun signUp(authorization: String, requestSignUpDto: SignUpRequestDto): ApiResponse = 20 | authService.signUp(authorization, requestSignUpDto) 21 | 22 | override suspend fun signUpWithGoogle(googleSignUpRequestDto: GoogleSignUpRequestDto): ApiResponse = 23 | authService.signUpWithGoogle(googleSignUpRequestDto) 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/data/remote/datasourceimpl/NotificationDataSourceImpl.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.data.remote.datasourceimpl 2 | 3 | import com.sopt.clody.data.remote.api.NotificationService 4 | import com.sopt.clody.data.remote.datasource.NotificationDataSource 5 | import com.sopt.clody.data.remote.dto.base.ApiResponse 6 | import com.sopt.clody.data.remote.dto.request.SendNotificationRequestDto 7 | import com.sopt.clody.data.remote.dto.response.NotificationInfoResponseDto 8 | import com.sopt.clody.data.remote.dto.response.SendNotificationResponseDto 9 | import javax.inject.Inject 10 | 11 | class NotificationDataSourceImpl @Inject constructor( 12 | private val notificationService: NotificationService, 13 | ) : NotificationDataSource { 14 | override suspend fun getNotificationInfo(): ApiResponse = 15 | notificationService.getNotificationInfo() 16 | 17 | override suspend fun sendNotification(sendNotificationRequestDto: SendNotificationRequestDto): ApiResponse = 18 | notificationService.sendNotification(sendNotificationRequestDto) 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/data/remote/datasourceimpl/TokenReissueDataSourceImpl.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.data.remote.datasourceimpl 2 | 3 | import com.sopt.clody.data.remote.api.TokenReissueService 4 | import com.sopt.clody.data.remote.datasource.TokenReissueDataSource 5 | import com.sopt.clody.data.remote.dto.base.ApiResponse 6 | import com.sopt.clody.data.remote.dto.response.TokenReissueResponseDto 7 | import javax.inject.Inject 8 | 9 | class TokenReissueDataSourceImpl @Inject constructor( 10 | private val tokenReissueService: TokenReissueService, 11 | ) : TokenReissueDataSource { 12 | override suspend fun getReissueToken(authorization: String): ApiResponse = 13 | tokenReissueService.reissue(authorization) 14 | } 15 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/data/remote/dto/base/ApiResponse.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.data.remote.dto.base 2 | 3 | import kotlinx.serialization.SerialName 4 | import kotlinx.serialization.Serializable 5 | 6 | @Serializable 7 | data class ApiResponse( 8 | @SerialName("status") val status: Int, 9 | @SerialName("message") val message: String, 10 | @SerialName("data") val data: T, 11 | ) 12 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/data/remote/dto/base/NullableApiResponse.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.data.remote.dto.base 2 | 3 | import kotlinx.serialization.SerialName 4 | import kotlinx.serialization.Serializable 5 | 6 | @Serializable 7 | data class NullableApiResponse( 8 | @SerialName("status") val status: Int, 9 | @SerialName("message") val message: String, 10 | @SerialName("data") val data: T? = null, 11 | ) 12 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/data/remote/dto/request/AdRequestDto.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.data.remote.dto.request 2 | 3 | import kotlinx.serialization.SerialName 4 | import kotlinx.serialization.Serializable 5 | 6 | @Serializable 7 | data class AdRequestDto( 8 | @SerialName("year") val year: Int, 9 | @SerialName("month") val month: Int, 10 | @SerialName("date") val date: Int, 11 | ) 12 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/data/remote/dto/request/GoogleSignUpRequestDto.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.data.remote.dto.request 2 | 3 | import kotlinx.serialization.SerialName 4 | import kotlinx.serialization.Serializable 5 | 6 | @Serializable 7 | data class GoogleSignUpRequestDto( 8 | @SerialName("idToken") val idToken: String, 9 | @SerialName("fcmToken") val fcmToken: String, 10 | ) 11 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/data/remote/dto/request/LoginRequestDto.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.data.remote.dto.request 2 | 3 | import kotlinx.serialization.SerialName 4 | import kotlinx.serialization.Serializable 5 | 6 | @Serializable 7 | data class LoginRequestDto( 8 | @SerialName("platform") val platform: String, 9 | @SerialName("fcmToken") val fcmToken: String, 10 | ) 11 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/data/remote/dto/request/ModifyNicknameRequestDto.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.data.remote.dto.request 2 | 3 | import kotlinx.serialization.SerialName 4 | import kotlinx.serialization.Serializable 5 | 6 | @Serializable 7 | data class ModifyNicknameRequestDto( 8 | @SerialName("name") val name: String, 9 | ) 10 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/data/remote/dto/request/SaveDraftDiaryRequestDto.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.data.remote.dto.request 2 | 3 | import kotlinx.serialization.SerialName 4 | import kotlinx.serialization.Serializable 5 | 6 | @Serializable 7 | data class SaveDraftDiaryRequestDto( 8 | @SerialName("date") val date: String, 9 | @SerialName("draftDiaries") val draftDiaries: List, 10 | ) 11 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/data/remote/dto/request/SendNotificationRequestDto.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.data.remote.dto.request 2 | 3 | import kotlinx.serialization.SerialName 4 | import kotlinx.serialization.Serializable 5 | 6 | @Serializable 7 | data class SendNotificationRequestDto( 8 | @SerialName("isDiaryAlarm") val isDiaryAlarm: Boolean, 9 | @SerialName("isDraftAlarm") val isDraftAlarm: Boolean, 10 | @SerialName("isReplyAlarm") val isReplyAlarm: Boolean, 11 | @SerialName("time") val time: String, 12 | @SerialName("fcmToken") val fcmToken: String, 13 | ) 14 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/data/remote/dto/request/SignUpRequestDto.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.data.remote.dto.request 2 | 3 | import kotlinx.serialization.SerialName 4 | import kotlinx.serialization.Serializable 5 | 6 | @Serializable 7 | data class SignUpRequestDto( 8 | @SerialName("platform") val platform: String, 9 | @SerialName("name") val name: String, 10 | @SerialName("fcmToken") val fcmToken: String, 11 | ) 12 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/data/remote/dto/request/WriteDiaryRequestDto.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.data.remote.dto.request 2 | 3 | import kotlinx.serialization.SerialName 4 | import kotlinx.serialization.Serializable 5 | 6 | @Serializable 7 | data class WriteDiaryRequestDto( 8 | @SerialName("date") val date: String, 9 | @SerialName("content") val content: List, 10 | ) 11 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/data/remote/dto/response/DailyDiariesResponseDto.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.data.remote.dto.response 2 | 3 | import kotlinx.serialization.SerialName 4 | import kotlinx.serialization.Serializable 5 | 6 | @Serializable 7 | data class DailyDiariesResponseDto( 8 | @SerialName("diaries") val diaries: List, 9 | @SerialName("isDeleted") val isDeleted: Boolean, 10 | @SerialName("isDraft") val isDraft: Boolean, 11 | ) { 12 | @Serializable 13 | data class Diary( 14 | @SerialName("content")val content: String, 15 | ) 16 | } 17 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/data/remote/dto/response/DiaryTimeResponseDto.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.data.remote.dto.response 2 | 3 | import kotlinx.serialization.SerialName 4 | import kotlinx.serialization.Serializable 5 | 6 | @Serializable 7 | data class DiaryTimeResponseDto( 8 | @SerialName("HH") val HH: Int, 9 | @SerialName("mm") val mm: Int, 10 | @SerialName("ss") val ss: Int, 11 | @SerialName("date") val date: String, 12 | @SerialName("isFirst") val isFirst: Boolean, 13 | @SerialName("isFromAd") val isFromAd: Boolean, 14 | ) 15 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/data/remote/dto/response/DraftDiariesResponseDto.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.data.remote.dto.response 2 | 3 | import com.sopt.clody.domain.model.DraftDiaryContents 4 | import kotlinx.serialization.SerialName 5 | import kotlinx.serialization.Serializable 6 | 7 | @Serializable 8 | data class DraftDiariesResponseDto( 9 | @SerialName("draftDiaries") val draftDiaries: List, 10 | ) { 11 | fun toDomain() = DraftDiaryContents( 12 | draftDiaries = draftDiaries ?: emptyList(), 13 | ) 14 | } 15 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/data/remote/dto/response/LoginResponseDto.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.data.remote.dto.response 2 | 3 | import kotlinx.serialization.SerialName 4 | import kotlinx.serialization.Serializable 5 | 6 | @Serializable 7 | data class LoginResponseDto( 8 | @SerialName("accessToken") val accessToken: String, 9 | @SerialName("refreshToken") val refreshToken: String, 10 | @SerialName("isBeginner") val isBeginner: Boolean = false, 11 | ) 12 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/data/remote/dto/response/ModifyNicknameResponseDto.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.data.remote.dto.response 2 | 3 | import kotlinx.serialization.SerialName 4 | import kotlinx.serialization.Serializable 5 | 6 | @Serializable 7 | data class ModifyNicknameResponseDto( 8 | @SerialName("name") val name: String, 9 | ) 10 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/data/remote/dto/response/MonthlyCalendarResponseDto.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.data.remote.dto.response 2 | 3 | import com.sopt.clody.domain.type.ReplyStatus 4 | import kotlinx.serialization.SerialName 5 | import kotlinx.serialization.Serializable 6 | 7 | @Serializable 8 | data class MonthlyCalendarResponseDto( 9 | @SerialName("totalCloverCount") val totalCloverCount: Int, 10 | @SerialName("diaries") val diaries: List, 11 | ) { 12 | @Serializable 13 | data class Diary( 14 | @SerialName("diaryCount") val diaryCount: Int, 15 | @SerialName("replyStatus") val replyStatus: ReplyStatus, 16 | @SerialName("isDeleted") val isDeleted: Boolean, 17 | @SerialName("date") val date: String, 18 | ) 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/data/remote/dto/response/MonthlyDiaryResponseDto.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.data.remote.dto.response 2 | 3 | import com.sopt.clody.domain.type.ReplyStatus 4 | import kotlinx.serialization.SerialName 5 | import kotlinx.serialization.Serializable 6 | 7 | @Serializable 8 | data class MonthlyDiaryResponseDto( 9 | @SerialName("totalCloverCount") val totalCloverCount: Int, 10 | @SerialName("diaries") val diaries: List, 11 | ) { 12 | @Serializable 13 | data class DailyDiary( 14 | @SerialName("diaryCount") val diaryCount: Int, 15 | @SerialName("replyStatus") val replyStatus: ReplyStatus, 16 | @SerialName("date") val date: String, 17 | @SerialName("diary") val diary: List, 18 | @SerialName("isDeleted") val isDeleted: Boolean, 19 | ) { 20 | @Serializable 21 | data class DailyDiaryContent( 22 | @SerialName("content") val content: String, 23 | ) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/data/remote/dto/response/NotificationInfoResponseDto.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.data.remote.dto.response 2 | 3 | import kotlinx.serialization.SerialName 4 | import kotlinx.serialization.Serializable 5 | 6 | @Serializable 7 | data class NotificationInfoResponseDto( 8 | @SerialName("isDiaryAlarm") val isDiaryAlarm: Boolean, 9 | @SerialName("isDraftAlarm") val isDraftAlarm: Boolean, 10 | @SerialName("isReplyAlarm") val isReplyAlarm: Boolean, 11 | @SerialName("time") val time: String, 12 | ) 13 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/data/remote/dto/response/ReplyDiaryResponseDto.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.data.remote.dto.response 2 | 3 | import kotlinx.serialization.Serializable 4 | 5 | @Serializable 6 | data class ReplyDiaryResponseDto( 7 | val content: String?, 8 | val nickname: String, 9 | val month: Int, 10 | val date: Int, 11 | ) 12 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/data/remote/dto/response/SendNotificationResponseDto.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.data.remote.dto.response 2 | 3 | import kotlinx.serialization.SerialName 4 | import kotlinx.serialization.Serializable 5 | 6 | @Serializable 7 | data class SendNotificationResponseDto( 8 | @SerialName("isDiaryAlarm") val isDiaryAlarm: Boolean, 9 | @SerialName("isDraftAlarm") val isDraftAlarm: Boolean, 10 | @SerialName("isReplyAlarm") val isReplyAlarm: Boolean, 11 | @SerialName("time") val time: String, 12 | @SerialName("fcmToken") val fcmToken: String, 13 | ) 14 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/data/remote/dto/response/SignUpResponseDto.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.data.remote.dto.response 2 | 3 | import kotlinx.serialization.SerialName 4 | import kotlinx.serialization.Serializable 5 | 6 | @Serializable 7 | data class SignUpResponseDto( 8 | @SerialName("accessToken") val accessToken: String, 9 | @SerialName("refreshToken") val refreshToken: String, 10 | ) 11 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/data/remote/dto/response/TokenReissueResponseDto.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.data.remote.dto.response 2 | 3 | import kotlinx.serialization.SerialName 4 | import kotlinx.serialization.Serializable 5 | 6 | @Serializable 7 | data class TokenReissueResponseDto( 8 | @SerialName("accessToken") 9 | val accessToken: String, 10 | @SerialName("refreshToken") 11 | val refreshToken: String, 12 | ) 13 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/data/remote/dto/response/UserInfoResponseDto.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.data.remote.dto.response 2 | 3 | import kotlinx.serialization.SerialName 4 | import kotlinx.serialization.Serializable 5 | 6 | @Serializable 7 | data class UserInfoResponseDto( 8 | @SerialName("email") val email: String, 9 | @SerialName("name") val name: String, 10 | @SerialName("platform") val platform: String, 11 | ) 12 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/data/remote/dto/response/WriteDiaryResponseDto.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.data.remote.dto.response 2 | 3 | import kotlinx.serialization.Serializable 4 | 5 | @Serializable 6 | data class WriteDiaryResponseDto( 7 | val createdAt: String, 8 | val replyType: String, 9 | val isFromDraft: Boolean, 10 | ) 11 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/data/remote/util/ApiError.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.data.remote.util 2 | 3 | data class ApiError( 4 | override val message: String, 5 | ) : Exception() 6 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/data/remote/util/HandleApiResponse.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.data.remote.util 2 | 3 | import com.sopt.clody.data.remote.dto.base.ApiResponse 4 | 5 | fun ApiResponse.handleApiResponse(): Result { 6 | return when (this.status) { 7 | in 200..299 -> { 8 | if (this.data != null) { 9 | Result.success(this.data) 10 | } else { 11 | Result.failure(Exception("No data available")) 12 | } 13 | } 14 | in 300..399 -> { 15 | Result.failure(Exception("Redirection error: ${this.message}")) 16 | } 17 | in 400..499 -> { 18 | Result.failure(Exception("Client error: ${this.message}")) 19 | } 20 | in 500..599 -> { 21 | Result.failure(Exception("Server error: ${this.message}")) 22 | } 23 | else -> { 24 | Result.failure(Exception("Unexpected error: ${this.message}")) 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/data/remote/util/SafeApiCall.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.data.remote.util 2 | 3 | import com.sopt.clody.data.remote.dto.base.ApiResponse 4 | import com.sopt.clody.presentation.utils.network.ErrorMessageProvider 5 | import java.io.IOException 6 | import kotlin.coroutines.cancellation.CancellationException 7 | 8 | suspend fun safeApiCall( 9 | errorMessageProvider: ErrorMessageProvider, 10 | action: suspend () -> ApiResponse, 11 | ): Result { 12 | return try { 13 | val response = action() 14 | response.data?.let { Result.success(it) } 15 | ?: Result.failure(ApiError(errorMessageProvider.getTemporaryError())) 16 | } catch (exception: Throwable) { 17 | if (exception is CancellationException) throw exception 18 | 19 | val error = when (exception) { 20 | is IOException -> ApiError(errorMessageProvider.getNetworkError()) 21 | else -> ApiError(errorMessageProvider.getTemporaryError()) 22 | } 23 | Result.failure(error) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/data/remote/util/TimeZoneInterceptor.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.data.remote.util 2 | 3 | import okhttp3.Interceptor 4 | import okhttp3.Response 5 | import java.time.ZoneId 6 | import javax.inject.Inject 7 | 8 | class TimeZoneInterceptor @Inject constructor() : Interceptor { 9 | 10 | private val userTimeZone = ZoneId.systemDefault().id 11 | 12 | override fun intercept(chain: Interceptor.Chain): Response { 13 | val originalRequest = chain.request() 14 | val url = originalRequest.url.toString() 15 | 16 | return if (shouldAddTimeZoneHeader(url)) { 17 | proceedWithTimeZoneHeader(chain, originalRequest) 18 | } else { 19 | chain.proceed(originalRequest) 20 | } 21 | } 22 | 23 | private fun shouldAddTimeZoneHeader(url: String) = 24 | url.contains("api/v1/diary") || 25 | url.contains("api/v1/diary/time") || 26 | url.contains("api/v1/calendar") || 27 | url.contains("api/v1/calendar/list") || 28 | url.contains("api/v1/reply") || 29 | url.contains("api/v1/reply/ad/start") || 30 | url.contains("api/v1/reply/ad/end") || 31 | url.contains("api/v1/draft") 32 | 33 | private fun proceedWithTimeZoneHeader(chain: Interceptor.Chain, request: okhttp3.Request): Response = 34 | chain.proceed(request.newBuilder().addHeader(TIME_ZONE, userTimeZone).build()) 35 | 36 | companion object { 37 | private const val TIME_ZONE = "Time-Zone" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/data/repositoryimpl/AccountManagementRepositoryImpl.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.data.repositoryimpl 2 | 3 | import com.sopt.clody.data.remote.datasource.AccountManagementDataSource 4 | import com.sopt.clody.data.remote.dto.request.ModifyNicknameRequestDto 5 | import com.sopt.clody.data.remote.dto.response.ModifyNicknameResponseDto 6 | import com.sopt.clody.data.remote.dto.response.UserInfoResponseDto 7 | import com.sopt.clody.data.remote.util.handleApiResponse 8 | import com.sopt.clody.domain.repository.AccountManagementRepository 9 | import javax.inject.Inject 10 | 11 | class AccountManagementRepositoryImpl @Inject constructor( 12 | private val accountManagementDataSource: AccountManagementDataSource, 13 | ) : AccountManagementRepository { 14 | override suspend fun getUserInfo(): Result = 15 | runCatching { 16 | accountManagementDataSource.getUserInfo().handleApiResponse().getOrThrow() 17 | } 18 | 19 | override suspend fun modifyNickname(modifyNicknameRequestDto: ModifyNicknameRequestDto): Result = 20 | runCatching { 21 | accountManagementDataSource.modifyNickname(modifyNicknameRequestDto).handleApiResponse().getOrThrow() 22 | } 23 | 24 | override suspend fun revokeAccount(): Result = 25 | runCatching { 26 | accountManagementDataSource.revokeAccount().handleApiResponse().getOrThrow() 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/data/repositoryimpl/AdRepositoryImpl.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.data.repositoryimpl 2 | 3 | import com.sopt.clody.data.remote.datasource.AdRemoteDataSource 4 | import com.sopt.clody.domain.repository.AdRepository 5 | import javax.inject.Inject 6 | 7 | class AdRepositoryImpl @Inject constructor( 8 | private val adRemoteDataSource: AdRemoteDataSource, 9 | ) : AdRepository { 10 | override suspend fun startAd(year: Int, month: Int, day: Int): Result { 11 | return adRemoteDataSource.startAd(year, month, day) 12 | } 13 | 14 | override suspend fun endAd(year: Int, month: Int, day: Int): Result { 15 | return adRemoteDataSource.endAd(year, month, day) 16 | } 17 | 18 | override suspend fun loadRewardedAd(): Result { 19 | return adRemoteDataSource.loadRewardedAd() 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/data/repositoryimpl/AuthRepositoryImpl.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.data.repositoryimpl 2 | 3 | import com.sopt.clody.data.remote.datasource.AuthDataSource 4 | import com.sopt.clody.data.remote.dto.request.GoogleSignUpRequestDto 5 | import com.sopt.clody.data.remote.dto.request.LoginRequestDto 6 | import com.sopt.clody.data.remote.dto.request.SignUpRequestDto 7 | import com.sopt.clody.data.remote.dto.response.LoginResponseDto 8 | import com.sopt.clody.data.remote.dto.response.SignUpResponseDto 9 | import com.sopt.clody.data.remote.util.handleApiResponse 10 | import com.sopt.clody.domain.repository.AuthRepository 11 | import javax.inject.Inject 12 | 13 | class AuthRepositoryImpl @Inject constructor( 14 | private val authDataSource: AuthDataSource, 15 | ) : AuthRepository { 16 | override suspend fun signIn(authorization: String, requestSignInDto: LoginRequestDto): Result = 17 | runCatching { 18 | authDataSource.signIn(authorization, requestSignInDto).handleApiResponse().getOrThrow() 19 | } 20 | 21 | override suspend fun signUp(authorization: String, requestSignUpDto: SignUpRequestDto): Result = 22 | runCatching { 23 | authDataSource.signUp(authorization, requestSignUpDto).handleApiResponse().getOrThrow() 24 | } 25 | 26 | override suspend fun signUpWithGoogle(googleSignUpRequestDto: GoogleSignUpRequestDto): Result = 27 | runCatching { 28 | authDataSource.signUpWithGoogle(googleSignUpRequestDto).handleApiResponse().getOrThrow() 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/data/repositoryimpl/DraftRepositoryImpl.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.data.repositoryimpl 2 | 3 | import com.sopt.clody.data.local.datasource.FirstDraftLocalDataSource 4 | import com.sopt.clody.domain.repository.DraftRepository 5 | import javax.inject.Inject 6 | 7 | class DraftRepositoryImpl @Inject constructor( 8 | private val firstDraftLocalDataSource: FirstDraftLocalDataSource, 9 | ) : DraftRepository { 10 | override fun getIsDraftUsed(): Boolean = firstDraftLocalDataSource.isDraftUsed 11 | 12 | override fun setIsDraftUsed(state: Boolean) { 13 | firstDraftLocalDataSource.isDraftUsed = state 14 | } 15 | 16 | override fun getIsFirstUse(): Boolean = firstDraftLocalDataSource.isFirstUse 17 | 18 | override fun setIsFirstUse(state: Boolean) { 19 | firstDraftLocalDataSource.isFirstUse = state 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/data/repositoryimpl/NotificationRepositoryImpl.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.data.repositoryimpl 2 | 3 | import com.sopt.clody.data.remote.datasource.NotificationDataSource 4 | import com.sopt.clody.data.remote.dto.request.SendNotificationRequestDto 5 | import com.sopt.clody.data.remote.dto.response.NotificationInfoResponseDto 6 | import com.sopt.clody.data.remote.dto.response.SendNotificationResponseDto 7 | import com.sopt.clody.data.remote.util.handleApiResponse 8 | import com.sopt.clody.domain.repository.NotificationRepository 9 | import javax.inject.Inject 10 | 11 | class NotificationRepositoryImpl @Inject constructor( 12 | private val notificationDataSource: NotificationDataSource, 13 | ) : NotificationRepository { 14 | override suspend fun getNotificationInfo(): Result = 15 | runCatching { 16 | notificationDataSource.getNotificationInfo().handleApiResponse().getOrThrow() 17 | } 18 | 19 | override suspend fun sendNotification(sendNotificationRequestDto: SendNotificationRequestDto): Result = 20 | runCatching { 21 | notificationDataSource.sendNotification(sendNotificationRequestDto).handleApiResponse().getOrThrow() 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/data/repositoryimpl/ReviewRepositoryImpl.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.data.repositoryimpl 2 | 3 | import com.sopt.clody.data.local.datasource.AppReviewLocalDataSource 4 | import com.sopt.clody.domain.repository.ReviewRepository 5 | import javax.inject.Inject 6 | 7 | class ReviewRepositoryImpl @Inject constructor( 8 | private val appReviewLocalDataSource: AppReviewLocalDataSource, 9 | ) : ReviewRepository { 10 | override fun getShouldShowPopup(): Boolean = appReviewLocalDataSource.shouldShowPopup 11 | 12 | override fun setShouldShowPopup(state: Boolean) { 13 | appReviewLocalDataSource.shouldShowPopup = state 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/data/repositoryimpl/TokenReissueRepositoryImpl.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.data.repositoryimpl 2 | 3 | import com.sopt.clody.data.remote.datasource.TokenReissueDataSource 4 | import com.sopt.clody.data.remote.dto.response.TokenReissueResponseDto 5 | import com.sopt.clody.domain.repository.TokenReissueRepository 6 | import javax.inject.Inject 7 | 8 | class TokenReissueRepositoryImpl @Inject constructor( 9 | private val tokenReissueDataSource: TokenReissueDataSource, 10 | ) : TokenReissueRepository { 11 | override suspend fun getReissueToken(authorization: String): Result = 12 | runCatching { 13 | val authorizationHeader = "Bearer $authorization" 14 | tokenReissueDataSource.getReissueToken(authorizationHeader).data 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/data/repositoryimpl/TokenRepositoryImpl.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.data.repositoryimpl 2 | 3 | import com.sopt.clody.data.datastore.TokenDataStore 4 | import com.sopt.clody.domain.repository.TokenRepository 5 | import javax.inject.Inject 6 | 7 | class TokenRepositoryImpl @Inject constructor( 8 | private val tokenDataStore: TokenDataStore, 9 | ) : TokenRepository { 10 | override fun getAccessToken(): String = tokenDataStore.accessToken 11 | override fun getRefreshToken(): String = tokenDataStore.refreshToken 12 | override fun setTokens(accessToken: String, refreshToken: String) { 13 | tokenDataStore.accessToken = accessToken 14 | tokenDataStore.refreshToken = refreshToken 15 | } 16 | override fun clearInfo() = tokenDataStore.clearInfo() 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/di/AdModule.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.di 2 | 3 | import com.sopt.clody.core.ad.RewardAdShower 4 | import com.sopt.clody.data.ad.RewardAdShowerImpl 5 | import dagger.Binds 6 | import dagger.Module 7 | import dagger.hilt.InstallIn 8 | import dagger.hilt.components.SingletonComponent 9 | 10 | @Module 11 | @InstallIn(SingletonComponent::class) 12 | abstract class AdModule { 13 | @Binds 14 | abstract fun bindRewardAdShower( 15 | impl: RewardAdShowerImpl, 16 | ): RewardAdShower 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/di/ApiModule.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.di 2 | 3 | import com.sopt.clody.data.remote.api.AccountManagementService 4 | import com.sopt.clody.data.remote.api.AdService 5 | import com.sopt.clody.data.remote.api.AuthService 6 | import com.sopt.clody.data.remote.api.DiaryService 7 | import com.sopt.clody.data.remote.api.NotificationService 8 | import com.sopt.clody.data.remote.api.TokenReissueService 9 | import dagger.Module 10 | import dagger.Provides 11 | import dagger.hilt.InstallIn 12 | import dagger.hilt.components.SingletonComponent 13 | import retrofit2.Retrofit 14 | import javax.inject.Singleton 15 | 16 | @Module 17 | @InstallIn(SingletonComponent::class) 18 | object ApiModule { 19 | @Provides 20 | @Singleton 21 | fun provideAuthService(retrofit: Retrofit): AuthService = 22 | retrofit.create(AuthService::class.java) 23 | 24 | @Provides 25 | @Singleton 26 | fun provideTokenReissueService(reissueRetrofit: Retrofit): TokenReissueService = 27 | reissueRetrofit.create(TokenReissueService::class.java) 28 | 29 | @Provides 30 | @Singleton 31 | fun provideDiaryService(retrofit: Retrofit): DiaryService = 32 | retrofit.create(DiaryService::class.java) 33 | 34 | @Provides 35 | @Singleton 36 | fun provideAccountManagementService(retrofit: Retrofit): AccountManagementService = 37 | retrofit.create(AccountManagementService::class.java) 38 | 39 | @Provides 40 | @Singleton 41 | fun provideNotificationService(retrofit: Retrofit): NotificationService = 42 | retrofit.create(NotificationService::class.java) 43 | 44 | @Provides 45 | @Singleton 46 | fun provideAdService(retrofit: Retrofit): AdService = 47 | retrofit.create(AdService::class.java) 48 | } 49 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/di/AppUpdateModule.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.di 2 | 3 | import com.google.firebase.remoteconfig.FirebaseRemoteConfig 4 | import com.google.firebase.remoteconfig.FirebaseRemoteConfigSettings 5 | import com.sopt.clody.data.remote.appupdate.AppUpdateCheckerImpl 6 | import com.sopt.clody.data.remote.datasource.RemoteConfigDataSource 7 | import com.sopt.clody.domain.appupdate.AppUpdateChecker 8 | import dagger.Module 9 | import dagger.Provides 10 | import dagger.hilt.InstallIn 11 | import dagger.hilt.components.SingletonComponent 12 | import javax.inject.Singleton 13 | 14 | @Module 15 | @InstallIn(SingletonComponent::class) 16 | object AppUpdateModule { 17 | 18 | @Provides 19 | @Singleton 20 | fun provideFirebaseRemoteConfig(): FirebaseRemoteConfig { 21 | val remoteConfig = FirebaseRemoteConfig.getInstance() 22 | val configSettings = FirebaseRemoteConfigSettings.Builder() 23 | .setMinimumFetchIntervalInSeconds(0) 24 | .build() 25 | remoteConfig.setConfigSettingsAsync(configSettings) 26 | return remoteConfig 27 | } 28 | 29 | @Provides 30 | @Singleton 31 | fun provideAppUpdateChecker( 32 | remoteConfigDataSource: RemoteConfigDataSource, 33 | ): AppUpdateChecker = AppUpdateCheckerImpl(remoteConfigDataSource) 34 | } 35 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/di/LocalDataSourceModule.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.di 2 | 3 | import android.content.SharedPreferences 4 | import com.sopt.clody.data.datastore.TokenDataStore 5 | import com.sopt.clody.data.datastore.TokenDataStoreImpl 6 | import com.sopt.clody.data.local.datasource.AppReviewLocalDataSource 7 | import com.sopt.clody.data.local.datasource.FirstDraftLocalDataSource 8 | import com.sopt.clody.data.local.datasourceimpl.AppReviewLocalDataSourceImpl 9 | import com.sopt.clody.data.local.datasourceimpl.FirstDraftLocalDataSourceImpl 10 | import com.sopt.clody.di.qualifier.FirstDraftPrefs 11 | import com.sopt.clody.di.qualifier.ReviewPrefs 12 | import com.sopt.clody.di.qualifier.TokenPrefs 13 | import dagger.Module 14 | import dagger.Provides 15 | import dagger.hilt.InstallIn 16 | import dagger.hilt.components.SingletonComponent 17 | import javax.inject.Singleton 18 | 19 | @Module 20 | @InstallIn(SingletonComponent::class) 21 | object LocalDataSourceModule { 22 | 23 | @Provides 24 | @Singleton 25 | fun provideTokenDataStore(@TokenPrefs sharedPreferences: SharedPreferences): TokenDataStore = 26 | TokenDataStoreImpl(sharedPreferences) 27 | 28 | @Provides 29 | @Singleton 30 | fun provideFirstDraftLocalDataSource(@FirstDraftPrefs sharedPreferences: SharedPreferences): FirstDraftLocalDataSource = 31 | FirstDraftLocalDataSourceImpl(sharedPreferences) 32 | 33 | @Provides 34 | @Singleton 35 | fun provideAppReviewLocalDataSource(@ReviewPrefs sharedPreferences: SharedPreferences): AppReviewLocalDataSource = 36 | AppReviewLocalDataSourceImpl(sharedPreferences) 37 | } 38 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/di/SharedPreferencesModule.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.di 2 | 3 | import android.content.Context 4 | import android.content.SharedPreferences 5 | import com.sopt.clody.di.qualifier.FirstDraftPrefs 6 | import com.sopt.clody.di.qualifier.ReviewPrefs 7 | import com.sopt.clody.di.qualifier.TokenPrefs 8 | import dagger.Module 9 | import dagger.Provides 10 | import dagger.hilt.InstallIn 11 | import dagger.hilt.android.qualifiers.ApplicationContext 12 | import dagger.hilt.components.SingletonComponent 13 | import javax.inject.Singleton 14 | 15 | @Module 16 | @InstallIn(SingletonComponent::class) 17 | object SharedPreferencesModule { 18 | 19 | @Provides 20 | @Singleton 21 | @TokenPrefs 22 | fun provideTokenSharedPreferences(@ApplicationContext context: Context): SharedPreferences { 23 | return context.getSharedPreferences("token_prefs", Context.MODE_PRIVATE) 24 | } 25 | 26 | @Provides 27 | @Singleton 28 | @FirstDraftPrefs 29 | fun provideFirstDraftSharedPreferences(@ApplicationContext context: Context): SharedPreferences { 30 | return context.getSharedPreferences("first_draft_prefs", Context.MODE_PRIVATE) 31 | } 32 | 33 | @Provides 34 | @Singleton 35 | @ReviewPrefs 36 | fun provideReviewSharedPreferences(@ApplicationContext context: Context): SharedPreferences { 37 | return context.getSharedPreferences("review_prefs", Context.MODE_PRIVATE) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/di/qualifier/Qualifier.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.di.qualifier 2 | 3 | import javax.inject.Qualifier 4 | 5 | @Qualifier 6 | @Retention(AnnotationRetention.BINARY) 7 | annotation class TokenPrefs 8 | 9 | @Qualifier 10 | @Retention(AnnotationRetention.BINARY) 11 | annotation class FirstDraftPrefs 12 | 13 | @Qualifier 14 | @Retention(AnnotationRetention.BINARY) 15 | annotation class ReviewPrefs 16 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/domain/appupdate/AppUpdateChecker.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.domain.appupdate 2 | 3 | import com.sopt.clody.domain.model.AppUpdateState 4 | 5 | interface AppUpdateChecker { 6 | suspend fun getAppUpdateState(currentVersion: String): AppUpdateState 7 | suspend fun isUnderInspection(): Boolean 8 | suspend fun getInspectionTimeText(): Pair? 9 | } 10 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/domain/model/AppUpdateState.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.domain.model 2 | 3 | sealed interface AppUpdateState { 4 | data object Latest : AppUpdateState 5 | data class SoftUpdate(val latestVersion: String) : AppUpdateState 6 | data class HardUpdate(val latestVersion: String) : AppUpdateState 7 | } 8 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/domain/model/CalendarMonthlyInfo.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.domain.model 2 | 3 | import com.sopt.clody.domain.type.ReplyStatus 4 | import java.time.LocalDate 5 | import java.time.ZoneId 6 | 7 | /** 8 | * 홈 화면의 월별 달력에서 사용되는 정보 9 | * 10 | * @property totalCloverCount 지금까지 모은 클로버(답장)의 개수. 연 단위로 카운트 11 | * @property calendarDailyInfoList 해당 월의 일별 정보 12 | * 13 | * */ 14 | data class CalendarMonthlyInfo( 15 | val totalCloverCount: Int = 0, 16 | val calendarDailyInfoList: List = listOf(), 17 | ) { 18 | /** 19 | * 홈 화면 월별 달력을 구성하는 일별 일기 정보 20 | * 21 | * @property diaryCount 해당 일에 작성한 일기의 개수 22 | * @property replyStatus 해당 일에 작성한 일기의 답장 상태 23 | * @property date 해당 일의 날짜, "2025-08-21" 형식 24 | * @property isDeleted 해당 일에 작성한 일기가 삭제 이력의 여부 25 | * 26 | * */ 27 | data class CalendarDailyInfo( 28 | val diaryCount: Int = 0, 29 | val replyStatus: ReplyStatus = ReplyStatus.UNREADY, 30 | val date: String = "", 31 | val isDeleted: Boolean = false, 32 | ) { 33 | fun isToday(): Boolean = date == LocalDate.now().toString() 34 | 35 | fun enableWriteDiary(): Boolean { 36 | val userTimeZone = ZoneId.systemDefault().id 37 | val today = LocalDate.now().toString() 38 | val yesterday = LocalDate.now().minusDays(1).toString() 39 | val isAvailableDay = if (userTimeZone == "Asia/Seoul") { 40 | date == today || date == yesterday 41 | } else { 42 | date == today 43 | } 44 | return diaryCount == 0 && isAvailableDay 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/domain/model/DailyDiaryInfo.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.domain.model 2 | 3 | /** 4 | * 홈 화면에서 월별 달력 하단에 일별 일기 정보를 위한 데이터 클래스 5 | * 6 | * @property diaryList 해당 일에 작성한 일기의 내용 7 | * @property isDraft 해당 일에 임시 저장 일기의 존재 여부 8 | * 9 | */ 10 | data class DailyDiaryInfo( 11 | val diaryList: List = listOf(), 12 | val isDraft: Boolean = false, 13 | ) 14 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/domain/model/DraftDiaryContents.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.domain.model 2 | 3 | data class DraftDiaryContents( 4 | val draftDiaries: List, 5 | ) 6 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/domain/repository/AccountManagementRepository.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.domain.repository 2 | 3 | import com.sopt.clody.data.remote.dto.request.ModifyNicknameRequestDto 4 | import com.sopt.clody.data.remote.dto.response.ModifyNicknameResponseDto 5 | import com.sopt.clody.data.remote.dto.response.UserInfoResponseDto 6 | 7 | interface AccountManagementRepository { 8 | suspend fun getUserInfo(): Result 9 | 10 | suspend fun modifyNickname( 11 | modifyNicknameRequestDto: ModifyNicknameRequestDto, 12 | ): Result 13 | 14 | suspend fun revokeAccount(): Result 15 | } 16 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/domain/repository/AdRepository.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.domain.repository 2 | 3 | interface AdRepository { 4 | suspend fun startAd(year: Int, month: Int, day: Int): Result 5 | suspend fun endAd(year: Int, month: Int, day: Int): Result 6 | suspend fun loadRewardedAd(): Result 7 | } 8 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/domain/repository/AuthRepository.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.domain.repository 2 | 3 | import com.sopt.clody.data.remote.dto.request.GoogleSignUpRequestDto 4 | import com.sopt.clody.data.remote.dto.request.LoginRequestDto 5 | import com.sopt.clody.data.remote.dto.request.SignUpRequestDto 6 | import com.sopt.clody.data.remote.dto.response.LoginResponseDto 7 | import com.sopt.clody.data.remote.dto.response.SignUpResponseDto 8 | 9 | interface AuthRepository { 10 | suspend fun signIn(authorization: String, requestSignInDto: LoginRequestDto): Result 11 | suspend fun signUp(authorization: String, requestSignUpDto: SignUpRequestDto): Result 12 | suspend fun signUpWithGoogle(googleSignUpRequestDto: GoogleSignUpRequestDto): Result 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/domain/repository/DiaryRepository.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.domain.repository 2 | 3 | import com.sopt.clody.data.remote.dto.response.DailyDiariesResponseDto 4 | import com.sopt.clody.data.remote.dto.response.DiaryTimeResponseDto 5 | import com.sopt.clody.data.remote.dto.response.MonthlyCalendarResponseDto 6 | import com.sopt.clody.data.remote.dto.response.MonthlyDiaryResponseDto 7 | import com.sopt.clody.data.remote.dto.response.ReplyDiaryResponseDto 8 | import com.sopt.clody.data.remote.dto.response.WriteDiaryResponseDto 9 | import com.sopt.clody.domain.model.DraftDiaryContents 10 | 11 | interface DiaryRepository { 12 | suspend fun writeDiary(lang: String, date: String, content: List): Result 13 | suspend fun deleteDailyDiary(year: Int, month: Int, day: Int): Result 14 | suspend fun getDailyDiariesData(year: Int, month: Int, date: Int): Result 15 | suspend fun getDiaryTime(year: Int, month: Int, date: Int): Result 16 | suspend fun getMonthlyCalendarData(year: Int, month: Int): Result 17 | suspend fun getMonthlyDiary(year: Int, month: Int): Result 18 | suspend fun getReplyDiary(year: Int, month: Int, date: Int): Result 19 | suspend fun fetchDraftDiary(year: Int, month: Int, date: Int): Result 20 | suspend fun saveDraftDiary(date: String, contents: List): Result 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/domain/repository/DraftRepository.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.domain.repository 2 | 3 | interface DraftRepository { 4 | fun getIsDraftUsed(): Boolean 5 | fun setIsDraftUsed(state: Boolean) 6 | fun getIsFirstUse(): Boolean 7 | fun setIsFirstUse(state: Boolean) 8 | } 9 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/domain/repository/NotificationRepository.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.domain.repository 2 | 3 | import com.sopt.clody.data.remote.dto.request.SendNotificationRequestDto 4 | import com.sopt.clody.data.remote.dto.response.NotificationInfoResponseDto 5 | import com.sopt.clody.data.remote.dto.response.SendNotificationResponseDto 6 | 7 | interface NotificationRepository { 8 | suspend fun getNotificationInfo(): Result 9 | suspend fun sendNotification(sendNotificationRequestDto: SendNotificationRequestDto): Result 10 | } 11 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/domain/repository/ReviewRepository.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.domain.repository 2 | 3 | interface ReviewRepository { 4 | fun getShouldShowPopup(): Boolean 5 | fun setShouldShowPopup(state: Boolean) 6 | } 7 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/domain/repository/TokenReissueRepository.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.domain.repository 2 | 3 | import com.sopt.clody.data.remote.dto.response.TokenReissueResponseDto 4 | 5 | interface TokenReissueRepository { 6 | suspend fun getReissueToken( 7 | authorization: String, 8 | ): Result 9 | } 10 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/domain/repository/TokenRepository.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.domain.repository 2 | 3 | interface TokenRepository { 4 | fun getAccessToken(): String 5 | fun getRefreshToken(): String 6 | fun setTokens(accessToken: String, refreshToken: String) 7 | fun clearInfo() 8 | } 9 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/domain/type/Notification.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.domain.type 2 | 3 | enum class Notification { 4 | DIARY, DRAFT, REPLY 5 | } 6 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/domain/type/ReplyStatus.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.domain.type 2 | 3 | import kotlinx.serialization.Serializable 4 | 5 | @Serializable 6 | enum class ReplyStatus { 7 | UNREADY, READY_READ, READY_NOT_READ, HAS_DRAFT, INVALID_DRAFT; 8 | 9 | val isUnreadOrNotRead: Boolean 10 | get() = this == UNREADY || this == READY_NOT_READ 11 | } 12 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/domain/usecase/FetchDraftDiaryUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.domain.usecase 2 | 3 | import com.sopt.clody.domain.repository.DiaryRepository 4 | import javax.inject.Inject 5 | 6 | class FetchDraftDiaryUseCase @Inject constructor( 7 | private val diaryRepository: DiaryRepository, 8 | ) { 9 | suspend operator fun invoke(year: Int, month: Int, day: Int) = 10 | diaryRepository.fetchDraftDiary(year, month, day) 11 | } 12 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/domain/usecase/SaveDraftDiaryUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.domain.usecase 2 | 3 | import com.sopt.clody.domain.repository.DiaryRepository 4 | import javax.inject.Inject 5 | 6 | class SaveDraftDiaryUseCase @Inject constructor( 7 | private val diaryRepository: DiaryRepository, 8 | ) { 9 | suspend operator fun invoke(date: String, contents: List) = 10 | diaryRepository.saveDraftDiary(date, contents) 11 | } 12 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/domain/usecase/WriteDiaryUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.domain.usecase 2 | 3 | import com.sopt.clody.domain.repository.DiaryRepository 4 | import javax.inject.Inject 5 | 6 | class WriteDiaryUseCase @Inject constructor( 7 | private val diaryRepository: DiaryRepository, 8 | ) { 9 | suspend operator fun invoke(lang: String, date: String, content: List) = 10 | diaryRepository.writeDiary(lang, date, content) 11 | } 12 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/domain/util/VersionComparator.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.domain.util 2 | 3 | object VersionComparator { 4 | fun compare(current: String, latest: String): Int { 5 | val currentParts = current.split(".").map { it.toIntOrNull() ?: 0 } 6 | val latestParts = latest.split(".").map { it.toIntOrNull() ?: 0 } 7 | 8 | for (i in 0 until maxOf(currentParts.size, latestParts.size)) { 9 | val c = currentParts.getOrNull(i) ?: 0 10 | val l = latestParts.getOrNull(i) ?: 0 11 | if (c < l) return -1 12 | if (c > l) return 1 13 | } 14 | return 0 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/presentation/di/LanguageModule.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.presentation.di 2 | 3 | import com.sopt.clody.presentation.utils.language.LanguageProvider 4 | import com.sopt.clody.presentation.utils.language.LanguageProviderImpl 5 | import dagger.Binds 6 | import dagger.Module 7 | import dagger.hilt.InstallIn 8 | import dagger.hilt.components.SingletonComponent 9 | 10 | @Module 11 | @InstallIn(SingletonComponent::class) 12 | interface LanguageModule { 13 | 14 | @Binds 15 | fun bindLanguageProvider( 16 | languageProviderImpl: LanguageProviderImpl, 17 | ): LanguageProvider 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/presentation/di/ViewModelsModule.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.presentation.di 2 | 3 | import com.airbnb.mvrx.hilt.AssistedViewModelFactory 4 | import com.airbnb.mvrx.hilt.MavericksViewModelComponent 5 | import com.airbnb.mvrx.hilt.ViewModelKey 6 | import com.sopt.clody.presentation.ui.auth.signup.SignUpViewModel 7 | import com.sopt.clody.presentation.ui.home.screen.HomeViewModel 8 | import com.sopt.clody.presentation.ui.login.LoginViewModel 9 | import com.sopt.clody.presentation.ui.splash.SplashViewModel 10 | import dagger.Binds 11 | import dagger.Module 12 | import dagger.hilt.InstallIn 13 | import dagger.multibindings.IntoMap 14 | 15 | @Module 16 | @InstallIn(MavericksViewModelComponent::class) 17 | interface ViewModelsModule { 18 | 19 | @Binds 20 | @IntoMap 21 | @ViewModelKey(SplashViewModel::class) 22 | fun bindSplashViewModelFactory( 23 | factory: SplashViewModel.Factory, 24 | ): AssistedViewModelFactory<*, *> 25 | 26 | @Binds 27 | @IntoMap 28 | @ViewModelKey(LoginViewModel::class) 29 | fun bindLoginViewModelFactory( 30 | factory: LoginViewModel.Factory, 31 | ): AssistedViewModelFactory<*, *> 32 | 33 | @Binds 34 | @IntoMap 35 | @ViewModelKey(SignUpViewModel::class) 36 | fun bindSignUpViewModelFactory( 37 | factory: SignUpViewModel.Factory, 38 | ): AssistedViewModelFactory<*, *> 39 | 40 | @Binds 41 | @IntoMap 42 | @ViewModelKey(HomeViewModel::class) 43 | fun bindHomeViewModelFactory( 44 | factory: HomeViewModel.Factory, 45 | ): AssistedViewModelFactory<*, *> 46 | } 47 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/presentation/ui/auth/guide/navigation/GuideNavigation.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.presentation.ui.auth.guide.navigation 2 | 3 | import androidx.navigation.NavController 4 | import androidx.navigation.NavGraphBuilder 5 | import androidx.navigation.NavOptionsBuilder 6 | import androidx.navigation.compose.composable 7 | import com.sopt.clody.presentation.ui.auth.guide.GuideRoute 8 | import com.sopt.clody.presentation.utils.navigation.Route 9 | 10 | fun NavGraphBuilder.guideScreen( 11 | navigateToHome: () -> Unit, 12 | ) { 13 | composable { 14 | GuideRoute( 15 | navigateToHome = navigateToHome, 16 | ) 17 | } 18 | } 19 | 20 | fun NavController.navigateToGuide( 21 | navOptions: NavOptionsBuilder.() -> Unit = {}, 22 | ) { 23 | navigate(Route.Guide, navOptions) 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/presentation/ui/auth/signup/NicknameMessage.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.presentation.ui.auth.signup 2 | 3 | import androidx.annotation.StringRes 4 | import androidx.compose.runtime.Composable 5 | import androidx.compose.ui.res.stringResource 6 | import com.sopt.clody.R 7 | 8 | enum class NicknameMessage( 9 | @StringRes val message: Int, 10 | ) { 11 | DEFAULT(R.string.nickname_message_default), 12 | INVALID(R.string.nickname_message_invalid), 13 | ; 14 | 15 | @Composable 16 | fun getMessage(): String = stringResource(message) 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/presentation/ui/auth/signup/navigation/SignUpNavigation.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.presentation.ui.auth.signup.navigation 2 | 3 | import androidx.navigation.NavController 4 | import androidx.navigation.NavGraphBuilder 5 | import androidx.navigation.NavOptionsBuilder 6 | import androidx.navigation.compose.composable 7 | import com.sopt.clody.presentation.ui.auth.signup.SignUpRoute 8 | import com.sopt.clody.presentation.utils.navigation.Route 9 | 10 | fun NavGraphBuilder.signUpScreen( 11 | navigateToHome: () -> Unit, 12 | navigateToPrevious: () -> Unit, 13 | ) { 14 | composable { 15 | SignUpRoute( 16 | navigateToHome = navigateToHome, 17 | navigateToPrevious = navigateToPrevious, 18 | ) 19 | } 20 | } 21 | fun NavController.navigateToSignUp( 22 | navOptions: NavOptionsBuilder.() -> Unit = {}, 23 | ) { 24 | navigate(Route.SignUp, navOptions) 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/presentation/ui/auth/timereminder/TimeReminderNavigation.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.presentation.ui.auth.timereminder 2 | 3 | import androidx.navigation.NavGraphBuilder 4 | import androidx.navigation.NavHostController 5 | import androidx.navigation.NavOptionsBuilder 6 | import androidx.navigation.compose.composable 7 | import com.sopt.clody.presentation.utils.navigation.Route 8 | 9 | fun NavGraphBuilder.timeReminderScreen( 10 | navigateToGuide: () -> Unit, 11 | ) { 12 | composable { 13 | TimeReminderRoute( 14 | navigateToGuide = navigateToGuide, 15 | ) 16 | } 17 | } 18 | 19 | fun NavHostController.navigateToTimeReminder( 20 | navOptions: NavOptionsBuilder.() -> Unit = {}, 21 | ) { 22 | navigate(Route.TimeReminder, navOptions) 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/presentation/ui/auth/timereminder/TimeReminderState.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.presentation.ui.auth.timereminder 2 | 3 | sealed class TimeReminderState { 4 | data object Idle : TimeReminderState() 5 | data object Loading : TimeReminderState() 6 | data class Success(val message: String) : TimeReminderState() 7 | data class Failure(val error: String) : TimeReminderState() 8 | } 9 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/presentation/ui/component/LoadingScreen.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.presentation.ui.component 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.foundation.layout.wrapContentSize 7 | import androidx.compose.material3.CircularProgressIndicator 8 | import androidx.compose.runtime.Composable 9 | import androidx.compose.ui.Alignment 10 | import androidx.compose.ui.Modifier 11 | import androidx.compose.ui.graphics.Color 12 | import androidx.compose.ui.tooling.preview.Preview 13 | import com.sopt.clody.ui.theme.ClodyTheme 14 | 15 | @Composable 16 | fun LoadingScreen( 17 | backgroundColor: Color = ClodyTheme.colors.white, 18 | ) { 19 | Box( 20 | modifier = Modifier 21 | .fillMaxSize() 22 | .background(backgroundColor), 23 | contentAlignment = Alignment.Center, 24 | ) { 25 | CircularProgressIndicator( 26 | modifier = Modifier.wrapContentSize(Alignment.Center), 27 | color = ClodyTheme.colors.mainYellow, 28 | ) 29 | } 30 | } 31 | 32 | @Composable 33 | @Preview 34 | fun LoadingScreenPreview() { 35 | LoadingScreen() 36 | } 37 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/presentation/ui/component/button/ClodyReplyButton.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.presentation.ui.component.button 2 | 3 | import androidx.compose.foundation.layout.height 4 | import androidx.compose.foundation.shape.RoundedCornerShape 5 | import androidx.compose.material3.Button 6 | import androidx.compose.material3.ButtonDefaults 7 | import androidx.compose.material3.Text 8 | import androidx.compose.runtime.Composable 9 | import androidx.compose.ui.Modifier 10 | import androidx.compose.ui.tooling.preview.Preview 11 | import androidx.compose.ui.unit.dp 12 | import com.sopt.clody.ui.theme.ClodyTheme 13 | 14 | @Composable 15 | fun ClodyReplyButton( 16 | onClick: () -> Unit, 17 | text: String, 18 | enabled: Boolean, 19 | modifier: Modifier = Modifier, 20 | ) { 21 | val backgroundColor = if (enabled) ClodyTheme.colors.gray02 else ClodyTheme.colors.gray02 22 | val contentColor = if (enabled) ClodyTheme.colors.white else ClodyTheme.colors.white 23 | 24 | Button( 25 | onClick = onClick, 26 | colors = ButtonDefaults.buttonColors( 27 | containerColor = backgroundColor, 28 | contentColor = contentColor, 29 | disabledContainerColor = ClodyTheme.colors.gray07, 30 | disabledContentColor = ClodyTheme.colors.gray04, 31 | ), 32 | shape = RoundedCornerShape(10.dp), 33 | enabled = enabled, 34 | modifier = modifier 35 | .height(50.dp), 36 | ) { 37 | Text( 38 | text = text, 39 | color = contentColor, 40 | style = ClodyTheme.typography.body2SemiBold, 41 | ) 42 | } 43 | } 44 | 45 | @Preview 46 | @Composable 47 | fun ClodyReplyButtonPreview() { 48 | ClodyReplyButton( 49 | onClick = {}, 50 | text = "클로디 버튼", 51 | enabled = true, 52 | ) 53 | } 54 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/presentation/ui/component/timepicker/PickerState.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.presentation.ui.component.timepicker 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.runtime.Stable 5 | import androidx.compose.runtime.getValue 6 | import androidx.compose.runtime.mutableStateOf 7 | import androidx.compose.runtime.remember 8 | import androidx.compose.runtime.setValue 9 | 10 | @Stable 11 | class PickerState { 12 | var selectedItem: String by mutableStateOf("") 13 | } 14 | 15 | @Composable 16 | fun rememberPickerState() = remember { PickerState() } 17 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/presentation/ui/diarylist/component/DailyDiaryCardContent.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.presentation.ui.diarylist.component 2 | 3 | import androidx.compose.foundation.layout.Column 4 | import androidx.compose.foundation.layout.Row 5 | import androidx.compose.foundation.layout.Spacer 6 | import androidx.compose.foundation.layout.fillMaxWidth 7 | import androidx.compose.foundation.layout.padding 8 | import androidx.compose.foundation.layout.width 9 | import androidx.compose.material3.Text 10 | import androidx.compose.runtime.Composable 11 | import androidx.compose.ui.Modifier 12 | import androidx.compose.ui.unit.dp 13 | import com.sopt.clody.ui.theme.ClodyTheme 14 | 15 | @Composable 16 | fun DailyDiaryCardContent( 17 | diary: List, 18 | ) { 19 | Column( 20 | modifier = Modifier 21 | .fillMaxWidth() 22 | .padding(horizontal = 20.dp) 23 | .padding(bottom = 2.dp), 24 | ) { 25 | diary.forEachIndexed { index, content -> 26 | Row( 27 | modifier = Modifier.padding(bottom = 24.dp), 28 | ) { 29 | Text( 30 | text = "${index + 1}.", 31 | color = ClodyTheme.colors.gray01, 32 | style = ClodyTheme.typography.body3SemiBold, 33 | ) 34 | Spacer(modifier = Modifier.width(10.dp)) 35 | Text( 36 | text = content, 37 | color = ClodyTheme.colors.gray03, 38 | style = ClodyTheme.typography.body3Medium, 39 | ) 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/presentation/ui/diarylist/component/EmptyDiaryList.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.presentation.ui.diarylist.component 2 | 3 | import androidx.compose.foundation.layout.Box 4 | import androidx.compose.foundation.layout.fillMaxSize 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.res.stringResource 10 | import androidx.compose.ui.text.style.TextAlign 11 | import com.sopt.clody.R 12 | import com.sopt.clody.ui.theme.ClodyTheme 13 | 14 | @Composable 15 | fun EmptyDiaryList() { 16 | Box( 17 | modifier = Modifier.fillMaxSize(), 18 | contentAlignment = Alignment.Center, 19 | ) { 20 | Text( 21 | text = stringResource(R.string.diary_list_empty_diary_list), 22 | color = ClodyTheme.colors.gray06, 23 | textAlign = TextAlign.Center, 24 | style = ClodyTheme.typography.body2SemiBold, 25 | ) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/presentation/ui/diarylist/navigation/DiaryListNavigation.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.presentation.ui.diarylist.navigation 2 | 3 | import androidx.navigation.NavController 4 | import androidx.navigation.NavGraphBuilder 5 | import androidx.navigation.compose.composable 6 | import androidx.navigation.toRoute 7 | import com.sopt.clody.domain.type.ReplyStatus 8 | import com.sopt.clody.presentation.ui.diarylist.screen.DiaryListRoute 9 | import com.sopt.clody.presentation.utils.navigation.Route 10 | 11 | fun NavGraphBuilder.diaryListScreen( 12 | navigateToHome: (year: Int, month: Int) -> Unit, 13 | navigateToReplyLoading: ( 14 | year: Int, 15 | month: Int, 16 | date: Int, 17 | from: Route.ReplyLoading.ReplyLoadingFrom, 18 | replyStatus: ReplyStatus, 19 | ) -> Unit, 20 | ) { 21 | composable { backStackEntry -> 22 | backStackEntry.toRoute().apply { 23 | DiaryListRoute( 24 | selectedYearFromHome = selectedYearFromHome, 25 | selectedMonthFromHome = selectedMonthFromHome, 26 | navigateToHome = navigateToHome, 27 | navigateToReplyLoading = navigateToReplyLoading, 28 | ) 29 | } 30 | } 31 | } 32 | 33 | fun NavController.navigateToDiaryList( 34 | selectedYearFromHome: Int, 35 | selectedMonthFromHome: Int, 36 | ) { 37 | navigate(Route.DiaryList(selectedYearFromHome, selectedMonthFromHome)) 38 | } 39 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/presentation/ui/diarylist/screen/DiaryDeleteState.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.presentation.ui.diarylist.screen 2 | 3 | sealed class DiaryDeleteState { 4 | data object Idle : DiaryDeleteState() 5 | data object Loading : DiaryDeleteState() 6 | data object Success : DiaryDeleteState() 7 | data class Failure(val errorMessage: String) : DiaryDeleteState() 8 | } 9 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/presentation/ui/diarylist/screen/DiaryListState.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.presentation.ui.diarylist.screen 2 | 3 | import com.sopt.clody.data.remote.dto.response.MonthlyDiaryResponseDto 4 | 5 | sealed class DiaryListState { 6 | data object Idle : DiaryListState() 7 | data object Loading : DiaryListState() 8 | data class Success(val data: MonthlyDiaryResponseDto) : DiaryListState() 9 | data class Failure(val errorMessage: String) : DiaryListState() 10 | } 11 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/presentation/ui/login/LoginContract.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.presentation.ui.login 2 | 3 | import android.content.Context 4 | import com.airbnb.mvrx.MavericksState 5 | import com.sopt.clody.data.datastore.OAuthProvider 6 | 7 | class LoginContract { 8 | 9 | data class LoginState( 10 | val loginType: OAuthProvider? = null, 11 | val isLoading: Boolean = false, 12 | val errorMessage: String? = null, 13 | ) : MavericksState 14 | 15 | sealed class LoginIntent { 16 | data class LoginOAuth(val provider: OAuthProvider, val context: Context? = null, val idToken: String? = null) : LoginIntent() 17 | data object ClearError : LoginIntent() 18 | } 19 | 20 | sealed interface LoginSideEffect { 21 | data object NavigateToHome : LoginSideEffect 22 | data object NavigateToSignUp : LoginSideEffect 23 | data class ShowError(val message: String) : LoginSideEffect 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/presentation/ui/login/LoginType.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.presentation.ui.login 2 | 3 | enum class LoginType { 4 | KAKAO, GOOGLE 5 | } 6 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/presentation/ui/login/navigation/LoginNavigation.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.presentation.ui.login.navigation 2 | 3 | import androidx.navigation.NavController 4 | import androidx.navigation.NavGraphBuilder 5 | import androidx.navigation.NavOptionsBuilder 6 | import androidx.navigation.compose.composable 7 | import com.sopt.clody.presentation.ui.login.LoginRoute 8 | import com.sopt.clody.presentation.utils.navigation.Route 9 | 10 | fun NavGraphBuilder.loginScreen( 11 | navigateToSignUp: () -> Unit, 12 | navigateToHome: () -> Unit, 13 | ) { 14 | composable { 15 | LoginRoute( 16 | navigateToSignUp = navigateToSignUp, 17 | navigateToHome = navigateToHome, 18 | ) 19 | } 20 | } 21 | 22 | fun NavController.navigateToLogin( 23 | navOptions: NavOptionsBuilder.() -> Unit = {}, 24 | ) { 25 | navigate(Route.Login, navOptions) 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/presentation/ui/main/ClodyApp.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.presentation.ui.main 2 | 3 | import android.content.Intent 4 | import androidx.compose.foundation.layout.Box 5 | import androidx.compose.foundation.layout.fillMaxSize 6 | import androidx.compose.runtime.Composable 7 | import androidx.compose.runtime.LaunchedEffect 8 | import androidx.compose.ui.Modifier 9 | import com.sopt.clody.presentation.utils.amplitude.AmplitudeConstraints 10 | import com.sopt.clody.presentation.utils.amplitude.AmplitudeUtils 11 | 12 | /** 13 | * 루트 Composable 함수. 14 | * 앱 전반의 테마와 시스템 UI 설정, Scaffold 레이아웃을 관리하고 15 | * [ClodyNavHost]를 통해 실제 네비게이션 경로를 구성. 16 | */ 17 | @Composable 18 | fun ClodyApp( 19 | appState: ClodyAppState, 20 | startIntent: Intent, 21 | ) { 22 | LaunchedEffect(startIntent) { 23 | if (startIntent.hasExtra("google.message_id")) { 24 | AmplitudeUtils.trackEvent(AmplitudeConstraints.ALARM) 25 | } 26 | } 27 | 28 | Box(modifier = Modifier.fillMaxSize()) { 29 | ClodyNavHost( 30 | appState = appState, 31 | startIntent = startIntent, 32 | ) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/presentation/ui/main/ClodyAppState.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.presentation.ui.main 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.runtime.Stable 5 | import androidx.compose.runtime.remember 6 | import androidx.navigation.NavHostController 7 | import androidx.navigation.compose.rememberNavController 8 | import com.sopt.clody.presentation.utils.navigation.Route 9 | 10 | /** 11 | * 앱의 전역 상태를 관리하는 클래스. 12 | * 현재는 네비게이션 컨트롤러만 포함되어 있으며, 13 | * 추후 다른 상태(예: 사용자 인증 상태, 테마 설정 등) 추가 가능. 14 | */ 15 | @Stable 16 | class ClodyAppState( 17 | val navController: NavHostController, 18 | ) { 19 | val startDestination = Route.Splash 20 | } 21 | 22 | /** 23 | * [ClodyAppState]를 기억하고 유지하는 Composable 함수. 24 | * [rememberNavController]를 활용해 NavController를 생성. 25 | */ 26 | @Composable 27 | fun rememberClodyAppState( 28 | navController: NavHostController = rememberNavController(), 29 | ): ClodyAppState = remember { ClodyAppState(navController) } 30 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/presentation/ui/main/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.presentation.ui.main 2 | 3 | import android.annotation.SuppressLint 4 | import android.content.pm.ActivityInfo 5 | import android.os.Bundle 6 | import androidx.activity.ComponentActivity 7 | import androidx.activity.SystemBarStyle 8 | import androidx.activity.compose.setContent 9 | import androidx.activity.enableEdgeToEdge 10 | import com.sopt.clody.ui.theme.ClodyTheme 11 | import dagger.hilt.android.AndroidEntryPoint 12 | 13 | @AndroidEntryPoint 14 | class MainActivity : ComponentActivity() { 15 | 16 | @SuppressLint("SourceLockedOrientationActivity") 17 | override fun onCreate(savedInstanceState: Bundle?) { 18 | super.onCreate(savedInstanceState) 19 | requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT 20 | 21 | enableEdgeToEdge( 22 | statusBarStyle = SystemBarStyle.light( 23 | android.graphics.Color.TRANSPARENT, 24 | android.graphics.Color.TRANSPARENT, 25 | ), 26 | navigationBarStyle = SystemBarStyle.light( 27 | android.graphics.Color.WHITE, 28 | android.graphics.Color.WHITE, 29 | ), 30 | ) 31 | 32 | setContent { 33 | ClodyTheme { 34 | val appState = rememberClodyAppState() 35 | ClodyApp(appState = appState, startIntent = intent) 36 | } 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/presentation/ui/replydiary/ReplyDiaryState.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.presentation.ui.replydiary 2 | 3 | sealed class ReplyDiaryState { 4 | object Idle : ReplyDiaryState() 5 | object Loading : ReplyDiaryState() 6 | data class Success( 7 | val content: String, 8 | val nickname: String, 9 | val month: Int, 10 | val date: Int, 11 | ) : ReplyDiaryState() 12 | data class Failure(val error: String) : ReplyDiaryState() 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/presentation/ui/replydiary/navigation/ReplyDiaryNavigation.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.presentation.ui.replydiary.navigation 2 | 3 | import androidx.navigation.NavController 4 | import androidx.navigation.NavGraphBuilder 5 | import androidx.navigation.NavOptionsBuilder 6 | import androidx.navigation.compose.composable 7 | import androidx.navigation.toRoute 8 | import com.sopt.clody.domain.type.ReplyStatus 9 | import com.sopt.clody.presentation.ui.replydiary.ReplyDiaryRoute 10 | import com.sopt.clody.presentation.utils.navigation.Route 11 | 12 | fun NavGraphBuilder.replyDiaryScreen( 13 | navigateToHome: (year: Int, month: Int, date: Int, isFromReplyDiary: Boolean) -> Unit, 14 | ) { 15 | composable { backStackEntry -> 16 | backStackEntry.toRoute().apply { 17 | ReplyDiaryRoute( 18 | year = year, 19 | month = month, 20 | date = date, 21 | replyStatus = replyStatus, 22 | navigateToHome = navigateToHome, 23 | ) 24 | } 25 | } 26 | } 27 | 28 | fun NavController.navigateToReplyDiary( 29 | year: Int, 30 | month: Int, 31 | day: Int, 32 | replyStatus: ReplyStatus = ReplyStatus.UNREADY, 33 | navOptions: NavOptionsBuilder.() -> Unit = {}, 34 | ) { 35 | navigate(Route.ReplyDiary(year, month, day, replyStatus), navOptions) 36 | } 37 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/presentation/ui/replyloading/screen/ReplyLoadingState.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.presentation.ui.replyloading.screen 2 | 3 | import java.time.LocalDateTime 4 | 5 | sealed class ReplyLoadingState { 6 | data object Idle : ReplyLoadingState() 7 | data object Loading : ReplyLoadingState() 8 | data class Success(val targetDateTime: LocalDateTime) : ReplyLoadingState() 9 | data class Failure(val error: String) : ReplyLoadingState() 10 | } 11 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/presentation/ui/setting/component/AccountManagementRevokeOption.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.presentation.ui.setting.component 2 | 3 | import androidx.compose.foundation.clickable 4 | import androidx.compose.foundation.interaction.MutableInteractionSource 5 | import androidx.compose.foundation.layout.Row 6 | import androidx.compose.foundation.layout.Spacer 7 | import androidx.compose.foundation.layout.padding 8 | import androidx.compose.material3.Text 9 | import androidx.compose.runtime.Composable 10 | import androidx.compose.runtime.remember 11 | import androidx.compose.ui.Modifier 12 | import androidx.compose.ui.res.stringResource 13 | import androidx.compose.ui.unit.dp 14 | import com.sopt.clody.R 15 | import com.sopt.clody.ui.theme.ClodyTheme 16 | 17 | @Composable 18 | fun AccountManagementRevokeOption( 19 | updateRevokeDialog: (Boolean) -> Unit, 20 | ) { 21 | Row( 22 | modifier = Modifier.padding(24.dp), 23 | ) { 24 | Text( 25 | text = stringResource(R.string.account_management_revoke), 26 | color = ClodyTheme.colors.gray05, 27 | style = ClodyTheme.typography.body4Medium, 28 | ) 29 | Spacer(modifier = Modifier.weight(1f)) 30 | Text( 31 | text = stringResource(R.string.account_management_btn_revoke), 32 | modifier = Modifier.clickable( 33 | onClick = { updateRevokeDialog(true) }, 34 | indication = null, 35 | interactionSource = remember { MutableInteractionSource() }, 36 | ), 37 | color = ClodyTheme.colors.gray05, 38 | style = ClodyTheme.typography.body4Medium, 39 | ) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/presentation/ui/setting/component/SettingSeparateLine.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.presentation.ui.setting.component 2 | 3 | import androidx.compose.foundation.background 4 | import androidx.compose.foundation.layout.Box 5 | import androidx.compose.foundation.layout.fillMaxWidth 6 | import androidx.compose.foundation.layout.height 7 | import androidx.compose.runtime.Composable 8 | import androidx.compose.ui.Modifier 9 | import androidx.compose.ui.unit.dp 10 | import com.sopt.clody.ui.theme.ClodyTheme 11 | 12 | @Composable 13 | fun SettingSeparateLine() { 14 | Box( 15 | modifier = Modifier 16 | .fillMaxWidth() 17 | .height(8.dp) 18 | .background(ClodyTheme.colors.gray08), 19 | ) 20 | } 21 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/presentation/ui/setting/component/SettingTopAppBar.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.presentation.ui.setting.component 2 | 3 | import androidx.compose.foundation.Image 4 | import androidx.compose.material3.CenterAlignedTopAppBar 5 | import androidx.compose.material3.ExperimentalMaterial3Api 6 | import androidx.compose.material3.IconButton 7 | import androidx.compose.material3.Text 8 | import androidx.compose.material3.TopAppBarDefaults 9 | import androidx.compose.material3.rememberTopAppBarState 10 | import androidx.compose.runtime.Composable 11 | import androidx.compose.ui.res.painterResource 12 | import com.sopt.clody.R 13 | import com.sopt.clody.ui.theme.ClodyTheme 14 | 15 | @OptIn(ExperimentalMaterial3Api::class) 16 | @Composable 17 | fun SettingTopAppBar( 18 | title: String, 19 | onClickBack: () -> Unit, 20 | ) { 21 | val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState()) 22 | 23 | CenterAlignedTopAppBar( 24 | title = { 25 | Text(text = title, style = ClodyTheme.typography.head4) 26 | }, 27 | navigationIcon = { 28 | IconButton(onClick = onClickBack) { 29 | Image( 30 | painter = painterResource(id = R.drawable.ic_setting_back), 31 | contentDescription = null, 32 | ) 33 | } 34 | }, 35 | colors = TopAppBarDefaults.topAppBarColors(ClodyTheme.colors.white), 36 | scrollBehavior = scrollBehavior, 37 | ) 38 | } 39 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/presentation/ui/setting/component/SettingVersionInfo.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.presentation.ui.setting.component 2 | 3 | import androidx.compose.foundation.layout.Row 4 | import androidx.compose.foundation.layout.Spacer 5 | import androidx.compose.foundation.layout.fillMaxWidth 6 | import androidx.compose.foundation.layout.padding 7 | import androidx.compose.material3.Text 8 | import androidx.compose.runtime.Composable 9 | import androidx.compose.ui.Alignment 10 | import androidx.compose.ui.Modifier 11 | import androidx.compose.ui.res.stringResource 12 | import androidx.compose.ui.unit.dp 13 | import com.sopt.clody.R 14 | import com.sopt.clody.ui.theme.ClodyTheme 15 | 16 | @Composable 17 | fun SettingVersionInfo( 18 | versionInfo: String, 19 | ) { 20 | Row( 21 | modifier = Modifier 22 | .fillMaxWidth() 23 | .padding(top = 20.dp) 24 | .padding(horizontal = 20.dp), 25 | verticalAlignment = Alignment.CenterVertically, 26 | ) { 27 | Text( 28 | text = stringResource(R.string.setting_option_app_version), 29 | modifier = Modifier.padding(start = 4.dp), 30 | style = ClodyTheme.typography.body1Medium, 31 | ) 32 | Spacer(modifier = Modifier.weight(1f)) 33 | Text( 34 | text = versionInfo, 35 | color = ClodyTheme.colors.gray05, 36 | style = ClodyTheme.typography.body4Medium, 37 | ) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/presentation/ui/setting/notificationsetting/screen/NotificationChangeState.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.presentation.ui.setting.notificationsetting.screen 2 | 3 | import com.sopt.clody.data.remote.dto.response.SendNotificationResponseDto 4 | 5 | sealed class NotificationChangeState { 6 | data object Idle : NotificationChangeState() 7 | data object Loading : NotificationChangeState() 8 | data class Success(val data: SendNotificationResponseDto) : NotificationChangeState() 9 | data class Failure(val errorMessage: String) : NotificationChangeState() 10 | } 11 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/presentation/ui/setting/notificationsetting/screen/NotificationInfoState.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.presentation.ui.setting.notificationsetting.screen 2 | 3 | import com.sopt.clody.data.remote.dto.response.NotificationInfoResponseDto 4 | 5 | sealed class NotificationInfoState { 6 | data object Idle : NotificationInfoState() 7 | data object Loading : NotificationInfoState() 8 | data class Success(val data: NotificationInfoResponseDto) : NotificationInfoState() 9 | data class Failure(val errorMessage: String) : NotificationInfoState() 10 | } 11 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/presentation/ui/setting/notificationsetting/screen/NotificationTimeChangeState.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.presentation.ui.setting.notificationsetting.screen 2 | 3 | import com.sopt.clody.data.remote.dto.response.SendNotificationResponseDto 4 | 5 | sealed class NotificationTimeChangeState { 6 | data object Idle : NotificationTimeChangeState() 7 | data object Loading : NotificationTimeChangeState() 8 | data class Success(val data: SendNotificationResponseDto) : NotificationTimeChangeState() 9 | data class Failure(val errorMessage: String) : NotificationTimeChangeState() 10 | } 11 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/presentation/ui/setting/screen/LogOutState.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.presentation.ui.setting.screen 2 | 3 | sealed class LogOutState { 4 | object Idle : LogOutState() 5 | object Success : LogOutState() 6 | data class Failure(val error: String) : LogOutState() 7 | } 8 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/presentation/ui/setting/screen/RevokeAccountState.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.presentation.ui.setting.screen 2 | 3 | sealed class RevokeAccountState { 4 | data object Idle : RevokeAccountState() 5 | data object Loading : RevokeAccountState() 6 | data class Success(val data: Unit) : RevokeAccountState() 7 | data class Failure(val errorMessage: String) : RevokeAccountState() 8 | } 9 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/presentation/ui/setting/screen/SettingOptionUrls.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.presentation.ui.setting.screen 2 | 3 | enum class SettingOptionUrls( 4 | val enUrl: String, 5 | val koUrl: String, 6 | ) { 7 | NOTICES_URL( 8 | enUrl = "https://tropical-buckthorn-d17.notion.site/Notice-22ae3fedb3f480feb229e7dcc7a23887?source=copy_link", 9 | koUrl = "https://www.notion.so/1c7e3fedb3f48029b36cf9d76c5fb6d6?pvs=21", 10 | ), 11 | SUPPORT_FEEDBACK_URL( 12 | enUrl = "https://docs.google.com/forms/d/e/1FAIpQLSe1LJg6tYaWBY2ji3O1smCH1ux5ItbVyGVUQko-Mg609Xt9eg/viewform", 13 | koUrl = "https://forms.gle/WnLC7VwHacufVHiv7", 14 | ), 15 | TERMS_OF_SERVICE_URL( 16 | enUrl = "https://tropical-buckthorn-d17.notion.site/Clody-Terms-of-Use-22ae3fedb3f48092ace1fba817df8605?source=copy_link", 17 | koUrl = "https://www.notion.so/1c7e3fedb3f4802c8db1f3056c03973f?pvs=21", 18 | ), 19 | PRIVACY_POLICY_URL( 20 | enUrl = "https://tropical-buckthorn-d17.notion.site/Clody-Privacy-Policy-22ae3fedb3f4808ab8dcc8ba60ad6cd6?source=copy_link", 21 | koUrl = "https://www.notion.so/1c7e3fedb3f48024a334c8116255b378?pvs=21", 22 | ), 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/presentation/ui/setting/screen/UserInfoState.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.presentation.ui.setting.screen 2 | 3 | import com.sopt.clody.data.remote.dto.response.UserInfoResponseDto 4 | 5 | sealed class UserInfoState { 6 | data object Idle : UserInfoState() 7 | data object Loading : UserInfoState() 8 | data class Success(val data: UserInfoResponseDto) : UserInfoState() 9 | data class Failure(val errorMessage: String) : UserInfoState() 10 | } 11 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/presentation/ui/setting/screen/UserNicknameState.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.presentation.ui.setting.screen 2 | 3 | import com.sopt.clody.data.remote.dto.response.ModifyNicknameResponseDto 4 | 5 | sealed class UserNicknameState { 6 | data object Idle : UserNicknameState() 7 | data object Loading : UserNicknameState() 8 | data class Success(val data: ModifyNicknameResponseDto) : UserNicknameState() 9 | data class Failure(val errorMessage: String) : UserNicknameState() 10 | } 11 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/presentation/ui/splash/SplashContract.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.presentation.ui.splash 2 | 3 | import android.content.Intent 4 | import com.airbnb.mvrx.MavericksState 5 | import com.sopt.clody.domain.model.AppUpdateState 6 | 7 | class SplashContract { 8 | data class SplashState( 9 | val updateState: AppUpdateState? = null, 10 | val showInspectionDialog: Boolean = false, 11 | val inspectionTimeText: String? = null, 12 | ) : MavericksState 13 | 14 | sealed class SplashIntent { 15 | data class InitSplash(val startIntent: Intent) : SplashIntent() 16 | data class HandleHardUpdate(val isConfirm: Boolean) : SplashIntent() 17 | data object HandleSoftUpdateConfirm : SplashIntent() 18 | data object ClearUpdateState : SplashIntent() 19 | data object DismissInspectionDialog : SplashIntent() 20 | } 21 | 22 | sealed interface SplashSideEffect { 23 | data object NavigateToLogin : SplashSideEffect 24 | data object NavigateToHome : SplashSideEffect 25 | data object NavigateToMarket : SplashSideEffect 26 | data object NavigateToMarketAndFinish : SplashSideEffect 27 | data object FinishApp : SplashSideEffect 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/presentation/ui/splash/navigation/SplashNavigation.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.presentation.ui.splash.navigation 2 | 3 | import android.content.Intent 4 | import androidx.navigation.NavGraphBuilder 5 | import androidx.navigation.compose.composable 6 | import com.sopt.clody.presentation.ui.splash.SplashRoute 7 | import com.sopt.clody.presentation.utils.navigation.Route 8 | 9 | fun NavGraphBuilder.splashScreen( 10 | startIntent: Intent, 11 | navigateToLogin: () -> Unit, 12 | navigateToHome: () -> Unit, 13 | ) { 14 | composable { 15 | SplashRoute( 16 | startIntent = startIntent, 17 | onLoginRequired = navigateToLogin, 18 | onAlreadyLoggedIn = navigateToHome, 19 | ) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/presentation/ui/type/DailyStateButtonType.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.presentation.ui.type 2 | 3 | import com.sopt.clody.domain.model.CalendarMonthlyInfo 4 | import com.sopt.clody.domain.model.DailyDiaryInfo 5 | import com.sopt.clody.domain.type.ReplyStatus 6 | 7 | enum class DailyStateButtonType { 8 | DRAFT_ENABLED, REPLY_ENABLED, REPLY_DISABLED, DIARY_ENABLED, DIARY_DISABLED; 9 | 10 | companion object { 11 | fun getType( 12 | calendarDailyInfo: CalendarMonthlyInfo.CalendarDailyInfo, 13 | dailyDiaryInfo: DailyDiaryInfo, 14 | ): DailyStateButtonType { 15 | return when { 16 | calendarDailyInfo.isDeleted && calendarDailyInfo.diaryCount > 0 -> REPLY_DISABLED 17 | dailyDiaryInfo.isDraft -> DRAFT_ENABLED 18 | calendarDailyInfo.replyStatus == ReplyStatus.INVALID_DRAFT -> REPLY_DISABLED 19 | calendarDailyInfo.replyStatus == ReplyStatus.READY_READ || 20 | calendarDailyInfo.replyStatus == ReplyStatus.READY_NOT_READ || 21 | (calendarDailyInfo.replyStatus == ReplyStatus.UNREADY && calendarDailyInfo.diaryCount > 0) -> REPLY_ENABLED 22 | calendarDailyInfo.enableWriteDiary() -> DIARY_ENABLED 23 | else -> DIARY_DISABLED 24 | } 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/presentation/ui/writediary/component/button/SendButton.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.presentation.ui.writediary.component.button 2 | 3 | import androidx.compose.foundation.clickable 4 | import androidx.compose.foundation.interaction.MutableInteractionSource 5 | import androidx.compose.foundation.interaction.collectIsPressedAsState 6 | import androidx.compose.foundation.layout.Box 7 | import androidx.compose.foundation.layout.sizeIn 8 | import androidx.compose.material3.Text 9 | import androidx.compose.runtime.Composable 10 | import androidx.compose.runtime.getValue 11 | import androidx.compose.runtime.remember 12 | import androidx.compose.ui.Alignment 13 | import androidx.compose.ui.Modifier 14 | import androidx.compose.ui.res.stringResource 15 | import androidx.compose.ui.unit.dp 16 | import com.sopt.clody.R 17 | import com.sopt.clody.ui.theme.ClodyTheme 18 | 19 | @Composable 20 | fun SendButton( 21 | modifier: Modifier = Modifier, 22 | onClick: () -> Unit, 23 | ) { 24 | val interactionSource = remember { MutableInteractionSource() } 25 | val isPressed by interactionSource.collectIsPressedAsState() 26 | 27 | Box( 28 | modifier = modifier 29 | .sizeIn(minWidth = 48.dp, minHeight = 48.dp) 30 | .clickable( 31 | interactionSource = interactionSource, 32 | indication = null, 33 | onClick = onClick, 34 | ), 35 | contentAlignment = Alignment.Center, 36 | ) { 37 | Text( 38 | text = stringResource(R.string.write_diary_btn_send), 39 | color = if (isPressed) ClodyTheme.colors.gray07 else ClodyTheme.colors.gray01, 40 | style = ClodyTheme.typography.body2SemiBold, 41 | ) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/presentation/ui/writediary/component/tooltip/CalculateTooltipPopupPosition.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.presentation.ui.writediary.component.tooltip 2 | 3 | import android.view.View 4 | import androidx.compose.ui.layout.LayoutCoordinates 5 | import androidx.compose.ui.layout.boundsInWindow 6 | import androidx.compose.ui.unit.IntOffset 7 | 8 | fun calculateTooltipPopupPosition( 9 | view: View, 10 | coordinates: LayoutCoordinates?, 11 | isTop: Boolean = false, 12 | ): TooltipPopupPosition { 13 | coordinates ?: return TooltipPopupPosition() 14 | 15 | val visibleWindowBounds = android.graphics.Rect() 16 | view.getWindowVisibleDisplayFrame(visibleWindowBounds) 17 | 18 | val boundsInWindow = coordinates.boundsInWindow() 19 | 20 | val centerPositionX = boundsInWindow.right - (boundsInWindow.right - boundsInWindow.left) / 2 21 | 22 | val offsetX = centerPositionX - visibleWindowBounds.centerX() 23 | 24 | return if (isTop) { 25 | val offset = IntOffset( 26 | y = -coordinates.size.height, 27 | x = offsetX.toInt(), 28 | ) 29 | TooltipPopupPosition( 30 | offset = offset, 31 | alignment = TooltipAlignment.BottomCenter, 32 | centerPositionX = centerPositionX, 33 | ) 34 | } else { 35 | val offset = IntOffset( 36 | y = coordinates.size.height, 37 | x = offsetX.toInt(), 38 | ) 39 | TooltipPopupPosition( 40 | offset = offset, 41 | alignment = TooltipAlignment.TopCenter, 42 | centerPositionX = centerPositionX, 43 | ) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/presentation/ui/writediary/component/tooltip/NoRippleClickable.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.presentation.ui.writediary.component.tooltip 2 | 3 | import android.annotation.SuppressLint 4 | import androidx.compose.foundation.clickable 5 | import androidx.compose.foundation.interaction.MutableInteractionSource 6 | import androidx.compose.runtime.remember 7 | import androidx.compose.ui.Modifier 8 | import androidx.compose.ui.composed 9 | 10 | @SuppressLint("ModifierFactoryUnreferencedReceiver") 11 | fun Modifier.noRippleClickable(onClick: () -> Unit): Modifier = composed { 12 | clickable( 13 | indication = null, 14 | interactionSource = remember { MutableInteractionSource() }, 15 | ) { 16 | onClick() 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/presentation/ui/writediary/component/tooltip/TooltipAlignment.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.presentation.ui.writediary.component.tooltip 2 | 3 | enum class TooltipAlignment { 4 | BottomCenter, 5 | TopCenter, 6 | } 7 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/presentation/ui/writediary/component/tooltip/TooltipPopupPosition.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.presentation.ui.writediary.component.tooltip 2 | 3 | import androidx.compose.ui.unit.IntOffset 4 | 5 | data class TooltipPopupPosition( 6 | val offset: IntOffset = IntOffset(0, 0), 7 | val alignment: TooltipAlignment = TooltipAlignment.TopCenter, 8 | val centerPositionX: Float = 0f, 9 | ) 10 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/presentation/ui/writediary/component/tooltip/TooltopPopUp.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.presentation.ui.writediary.component.tooltip 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.runtime.getValue 5 | import androidx.compose.runtime.mutableStateOf 6 | import androidx.compose.runtime.remember 7 | import androidx.compose.runtime.setValue 8 | import androidx.compose.ui.Modifier 9 | import androidx.compose.ui.layout.onGloballyPositioned 10 | import androidx.compose.ui.platform.LocalView 11 | 12 | @Composable 13 | fun TooltipPopup( 14 | modifier: Modifier = Modifier, 15 | requesterView: @Composable (Modifier) -> Unit, 16 | tooltipContent: @Composable () -> Unit, 17 | isShowTooltip: Boolean, 18 | onDismissRequest: () -> Unit, 19 | ) { 20 | val view = LocalView.current.rootView 21 | var position by remember { mutableStateOf(TooltipPopupPosition()) } 22 | 23 | if (isShowTooltip) { 24 | DisplayTooltipPopup( 25 | position = position, 26 | onDismissRequest = onDismissRequest, 27 | content = tooltipContent, 28 | ) 29 | } 30 | 31 | requesterView( 32 | modifier 33 | .noRippleClickable { 34 | position = calculateTooltipPopupPosition(view, coordinates = null, isTop = true) 35 | } 36 | .onGloballyPositioned { coordinates -> 37 | position = calculateTooltipPopupPosition(view, coordinates, isTop = true) 38 | }, 39 | ) 40 | } 41 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/presentation/ui/writediary/navigation/WriteDiaryNavigation.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.presentation.ui.writediary.navigation 2 | 3 | import androidx.navigation.NavController 4 | import androidx.navigation.NavGraphBuilder 5 | import androidx.navigation.NavOptionsBuilder 6 | import androidx.navigation.compose.composable 7 | import androidx.navigation.toRoute 8 | import com.sopt.clody.presentation.ui.writediary.screen.WriteDiaryRoute 9 | import com.sopt.clody.presentation.utils.navigation.Route 10 | 11 | fun NavGraphBuilder.writeDiaryScreen( 12 | navigateToReplyLoading: (year: Int, month: Int, day: Int) -> Unit, 13 | navigateToHome: (year: Int, month: Int) -> Unit, 14 | navigateToPrevious: () -> Unit, 15 | ) { 16 | composable { backStackEntry -> 17 | backStackEntry.toRoute().apply { 18 | WriteDiaryRoute( 19 | year = year, 20 | month = month, 21 | date = date, 22 | navigateToReplyLoading = navigateToReplyLoading, 23 | navigateToHome = navigateToHome, 24 | navigateToPrevious = navigateToPrevious, 25 | ) 26 | } 27 | } 28 | } 29 | 30 | fun NavController.navigateToWriteDiary( 31 | year: Int, 32 | month: Int, 33 | day: Int, 34 | navOptions: NavOptionsBuilder.() -> Unit = {}, 35 | ) { 36 | navigate(Route.WriteDiary(year, month, day), navOptions) 37 | } 38 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/presentation/ui/writediary/screen/WriteDiaryState.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.presentation.ui.writediary.screen 2 | 3 | import kotlinx.serialization.Serializable 4 | 5 | @Serializable 6 | sealed class WriteDiaryState { 7 | data object Idle : WriteDiaryState() 8 | data object Loading : WriteDiaryState() 9 | data class Success(val createdAt: String) : WriteDiaryState() 10 | data class Failure(val error: String) : WriteDiaryState() 11 | data object NoReply : WriteDiaryState() 12 | } 13 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/presentation/utils/OpenExternalBrowser.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.presentation.utils 2 | 3 | import android.content.Context 4 | import android.content.Intent 5 | import android.net.Uri 6 | 7 | fun openExternalBrowser(context: Context, url: String) { 8 | val uri = Uri.parse(url) 9 | val intent = Intent(Intent.ACTION_VIEW, uri).apply { 10 | flags = Intent.FLAG_ACTIVITY_NEW_TASK 11 | } 12 | 13 | // 웹 브라우저 앱이 설치되어 있지 않은 경우 14 | context.packageManager.resolveActivity(intent, 0)?.let { 15 | context.startActivity(intent) 16 | } ?: return 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/presentation/utils/amplitude/AmplitudeConstraints.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.presentation.utils.amplitude 2 | 3 | object AmplitudeConstraints { 4 | // 온보딩 5 | const val ONBOARDING_ALARM = "onboarding_alarm" 6 | 7 | // 홈 8 | const val HOME = "home" 9 | const val HOME_CALENDAR_CLOVER = "home_calendar_clover" 10 | const val HOME_WRITING_DIARY = "home_writing_diary" 11 | const val HOME_REPLY = "home_reply" 12 | const val HOME_LIST_DIARY = "home_list_diary" 13 | const val HOME_DELETE_DIARY = "home_delete_diary" 14 | const val HOME_NO_DELETE_DIARY = "home_no_delete_diary" 15 | 16 | // 리스트 17 | const val LIST = "list" 18 | const val LIST_REPLY = "list_reply" 19 | const val LIST_DELETE_DIARY = "list_delete_diary" 20 | const val LIST_NO_DELETE_DIARY = "list_no_delete_diary" 21 | 22 | // 일기 작성 23 | const val WRITING_DIARY_ADD_LIST = "writing_diary_add_list" 24 | const val WRITING_DIARY_DELETE_LIST = "writing_diary_delete_list" 25 | const val WRITING_DIARY_BACK = "writing_diary_back" 26 | const val WRITING_DIARY_COMPLETE = "writing_diary_complete" 27 | const val WRITING_DIARY_NO_COMPLETE = "writing_diary_no_complete" 28 | 29 | // 답장 대기 30 | const val WAITING_DIARY = "waiting_diary" 31 | const val WAITING_DIARY_REPLY = "waiting_diary_reply" 32 | 33 | // 일기 답장 34 | const val REPLY = "reply" 35 | 36 | // 설정 37 | const val SETTING = "setting" 38 | const val LOGOUT = "logout" 39 | const val REVOKE = "revoke" 40 | 41 | // FCM 42 | const val ALARM = "alarm" 43 | } 44 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/presentation/utils/amplitude/AmplitudeUtils.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.presentation.utils.amplitude 2 | 3 | import android.content.Context 4 | import com.amplitude.android.Amplitude 5 | import com.amplitude.android.Configuration 6 | import com.amplitude.android.events.Identify 7 | import com.sopt.clody.BuildConfig 8 | 9 | object AmplitudeUtils { 10 | private lateinit var amplitude: Amplitude 11 | 12 | fun initAmplitude(context: Context) { 13 | amplitude = Amplitude( 14 | Configuration( 15 | apiKey = BuildConfig.AMPLITUDE_API_KEY, 16 | context = context, 17 | ), 18 | ) 19 | } 20 | 21 | fun trackEvent(eventName: String) { 22 | amplitude.track(eventType = eventName) 23 | } 24 | 25 | fun trackEventWithProperty(eventName: String, propertyName: String, propertyValue: T) { 26 | amplitude.track( 27 | eventType = eventName, 28 | eventProperties = mapOf(propertyName to propertyValue), 29 | ) 30 | } 31 | 32 | fun trackEventWithProperties(eventName: String, properties: Map) { 33 | amplitude.track(eventType = eventName, eventProperties = properties) 34 | } 35 | 36 | fun updateStringUserProperty(propertyName: String, propertyValue: String) { 37 | amplitude.identify(Identify().set(property = propertyName, value = propertyValue)) 38 | } 39 | 40 | fun updateIntUserProperty(propertyName: String, propertyValue: Int) { 41 | amplitude.identify(Identify().set(property = propertyName, value = propertyValue)) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/presentation/utils/base/ClodyPreview.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.presentation.utils.base 2 | 3 | import android.content.res.Configuration 4 | import androidx.compose.material3.Surface 5 | import androidx.compose.runtime.Composable 6 | import androidx.compose.ui.tooling.preview.Preview 7 | import androidx.compose.ui.tooling.preview.PreviewScreenSizes 8 | import com.sopt.clody.ui.theme.ClodyTheme 9 | 10 | // 아래 폴드는 예시이고 fontScale 같은 값도 조정이 가능합니다. 11 | // @Preview(name = "Galaxy Z Fold3 접힌화면 (840x2289)", widthDp = 320, heightDp = 870, showBackground = true) 12 | @Preview(uiMode = Configuration.UI_MODE_NIGHT_NO) 13 | @PreviewScreenSizes 14 | annotation class ClodyPreview 15 | 16 | @Composable 17 | fun BasePreview(content: @Composable () -> Unit = {}) { 18 | ClodyTheme { 19 | Surface(color = ClodyTheme.colors.white) { 20 | content() 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/presentation/utils/base/UiLoadState.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.presentation.utils.base 2 | 3 | enum class UiLoadState { 4 | Idle, 5 | Loading, 6 | Success, 7 | Error, 8 | } 9 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/presentation/utils/base/UiState.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.presentation.utils.base 2 | 3 | sealed interface UiState { 4 | data object Empty : UiState 5 | data object Loading : UiState 6 | data class Success(val data: T) : UiState 7 | data class Failure(val msg: String) : UiState 8 | 9 | fun toUiStateModel() = UiStateModel( 10 | isEmpty = this is Empty, 11 | isLoading = this is Loading, 12 | isSuccess = this is Success<*>, 13 | isFailure = this is Failure, 14 | ) 15 | } 16 | 17 | data class UiStateModel( 18 | val isEmpty: Boolean = false, 19 | val isLoading: Boolean = false, 20 | val isSuccess: Boolean = false, 21 | val isFailure: Boolean = false, 22 | ) 23 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/presentation/utils/extension/DateLabelExtension.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.presentation.utils.extension 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.ui.platform.LocalContext 5 | import androidx.compose.ui.res.stringResource 6 | import com.sopt.clody.R 7 | import kotlinx.datetime.Month 8 | import java.time.format.TextStyle 9 | import java.util.Locale 10 | 11 | @Composable 12 | fun Int.toLocalizedYearLabel(): String = stringResource(R.string.year_format, this) 13 | 14 | @Composable 15 | fun Int.toLocalizedMonthLabel(): String { 16 | val locales = LocalContext.current.resources.configuration.locales 17 | val locale = if (locales.isEmpty) Locale.getDefault() else locales[0] 18 | return if (locale.language == "ko") { 19 | stringResource(R.string.month_format, this) 20 | } else { 21 | Month.of(this).getDisplayName(TextStyle.FULL, Locale.ENGLISH) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/presentation/utils/extension/GetDayOfWeek.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.presentation.utils.extension 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.ui.platform.LocalConfiguration 5 | import java.time.LocalDate 6 | import java.time.format.TextStyle 7 | 8 | @Composable 9 | fun getDayOfWeek(year: Int, month: Int, day: Int): String { 10 | val date = LocalDate.of(year, month, day) 11 | val locale = LocalConfiguration.current.locales.let { if (it.isEmpty) java.util.Locale.getDefault() else it[0] } 12 | return date.dayOfWeek.getDisplayName(TextStyle.FULL, locale) 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/presentation/utils/extension/ThrottleExtensions.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.presentation.utils.extension 2 | 3 | import kotlinx.coroutines.flow.Flow 4 | import kotlinx.coroutines.flow.filter 5 | 6 | fun Flow.throttleFirst(throttleTimeMillis: Long): Flow { 7 | var lastEmissionTime = 0L 8 | return this.filter { // .filter을 통해 조건에 맞는 이벤트만 통과시킨다. 9 | val currentTime = System.currentTimeMillis() 10 | if (currentTime - lastEmissionTime >= throttleTimeMillis) { 11 | lastEmissionTime = currentTime 12 | true 13 | } else { 14 | false 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/presentation/utils/extension/TimePeriod.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.presentation.utils.extension 2 | 3 | import androidx.annotation.StringRes 4 | import androidx.compose.runtime.Composable 5 | import androidx.compose.ui.res.stringResource 6 | import com.sopt.clody.R 7 | 8 | enum class TimePeriod(@StringRes val labelResId: Int) { 9 | AM(R.string.time_am), 10 | PM(R.string.time_pm), 11 | ; 12 | 13 | @Composable 14 | fun getLabel(): String = stringResource(labelResId) 15 | } 16 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/presentation/utils/extension/YearMonthLabelUtil.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.presentation.utils.extension 2 | 3 | object YearMonthLabelUtil { 4 | const val MIN_YEAR = 2000 5 | const val MAX_YEAR = 2030 6 | } 7 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/presentation/utils/language/LanguageProvider.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.presentation.utils.language 2 | 3 | import com.sopt.clody.data.datastore.OAuthProvider 4 | import com.sopt.clody.presentation.ui.setting.screen.SettingOptionUrls 5 | 6 | interface LanguageProvider { 7 | fun getCurrentLanguageTag(): String 8 | fun getInspectionTimeText(start: String, end: String): String? 9 | fun getLoginType(): OAuthProvider 10 | fun getNicknameMaxLength(): Int 11 | fun getDiaryMaxLength(): Int 12 | fun getWebViewUrlFor(option: SettingOptionUrls): String 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/presentation/utils/navigation/NavControllerExtensions.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.presentation.utils.navigation 2 | 3 | import androidx.navigation.NavController 4 | 5 | /** 6 | * 현재 backStack 상단에서 popBackStack 동작을 수행하는 helper 함수. 7 | */ 8 | fun NavController.safePopBackStack() { 9 | if (currentBackStackEntry?.lifecycle?.currentState == androidx.lifecycle.Lifecycle.State.RESUMED) { 10 | popBackStack() 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/presentation/utils/network/ErrorMessageModule.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.presentation.utils.network 2 | 3 | import android.content.Context 4 | import dagger.Module 5 | import dagger.Provides 6 | import dagger.hilt.InstallIn 7 | import dagger.hilt.android.qualifiers.ApplicationContext 8 | import dagger.hilt.components.SingletonComponent 9 | import javax.inject.Singleton 10 | 11 | @Module 12 | @InstallIn(SingletonComponent::class) 13 | object ErrorMessageModule { 14 | 15 | @Provides 16 | @Singleton 17 | fun provideErrorMessageProvider( 18 | @ApplicationContext context: Context, 19 | ): ErrorMessageProvider { 20 | return ErrorMessageProviderImpl(context) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/presentation/utils/network/ErrorMessageProvider.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.presentation.utils.network 2 | 3 | import com.sopt.clody.data.remote.util.ApiError 4 | 5 | interface ErrorMessageProvider { 6 | fun getTemporaryError(): String 7 | fun getNetworkError(): String 8 | fun getServerError(): String 9 | fun getFetchTempDiaryFailedError(): String 10 | fun getUnknownError(): String 11 | fun getLoginFailedError(): String 12 | fun getSignupFailedError(): String 13 | fun getGoogleIdTokenMissingError(): String 14 | fun getNetworkCheckError(): String 15 | fun getApiError(apiError: ApiError): String 16 | } 17 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/presentation/utils/network/ErrorMessageProviderImpl.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.presentation.utils.network 2 | 3 | import android.content.Context 4 | import com.sopt.clody.R 5 | import com.sopt.clody.data.remote.util.ApiError 6 | import dagger.hilt.android.qualifiers.ApplicationContext 7 | import javax.inject.Inject 8 | 9 | class ErrorMessageProviderImpl @Inject constructor( 10 | @ApplicationContext private val context: Context, 11 | ) : ErrorMessageProvider { 12 | 13 | override fun getTemporaryError(): String { 14 | return context.getString(R.string.error_temporary) 15 | } 16 | 17 | override fun getNetworkError(): String { 18 | return context.getString(R.string.error_network) 19 | } 20 | 21 | override fun getServerError(): String { 22 | return context.getString(R.string.error_server) 23 | } 24 | 25 | override fun getFetchTempDiaryFailedError(): String { 26 | return context.getString(R.string.error_fetch_temp_diary_failed) 27 | } 28 | 29 | override fun getUnknownError(): String { 30 | return context.getString(R.string.error_unknown) 31 | } 32 | 33 | override fun getLoginFailedError(): String { 34 | return context.getString(R.string.error_login_failed) 35 | } 36 | 37 | override fun getSignupFailedError(): String { 38 | return context.getString(R.string.error_signup_failed) 39 | } 40 | 41 | override fun getGoogleIdTokenMissingError(): String { 42 | return context.getString(R.string.error_google_id_token_missing) 43 | } 44 | 45 | override fun getNetworkCheckError(): String { 46 | return context.getString(R.string.error_network_check) 47 | } 48 | 49 | override fun getApiError(apiError: ApiError): String { 50 | return apiError.message 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /app/src/main/java/com/sopt/clody/ui/theme/Theme.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody.ui.theme 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.runtime.CompositionLocalProvider 5 | import androidx.compose.runtime.ReadOnlyComposable 6 | import androidx.compose.runtime.remember 7 | import androidx.compose.ui.platform.LocalConfiguration 8 | 9 | @Composable 10 | fun provideTypographyByLocale(): ClodyTypography { 11 | val configuration = LocalConfiguration.current 12 | val locale = remember(configuration) { configuration.locales[0] } 13 | 14 | return if (locale.language == "ko") clodyKoreanTypography else clodyEnglishTypography 15 | } 16 | 17 | @Composable 18 | fun ClodyTheme( 19 | content: @Composable () -> Unit, 20 | ) { 21 | val colors = defaultClodyColors 22 | val typography = provideTypographyByLocale() 23 | 24 | CompositionLocalProvider( 25 | LocalClodyColors provides colors, 26 | LocalClodyTypography provides typography, 27 | content = content, 28 | ) 29 | } 30 | 31 | object ClodyTheme { 32 | val colors: ClodyColors 33 | @Composable 34 | @ReadOnlyComposable 35 | get() = LocalClodyColors.current 36 | 37 | val typography: ClodyTypography 38 | @Composable 39 | @ReadOnlyComposable 40 | get() = LocalClodyTypography.current 41 | } 42 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-ko/img_splash_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-Clody/Clody_Android/65643ebe37474c522f7ac125690bc739768a38c0/app/src/main/res/drawable-ko/img_splash_logo.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/img_account_management_kakao.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-Clody/Clody_Android/65643ebe37474c522f7ac125690bc739768a38c0/app/src/main/res/drawable-xhdpi/img_account_management_kakao.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/img_failure_screen_clody.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-Clody/Clody_Android/65643ebe37474c522f7ac125690bc739768a38c0/app/src/main/res/drawable-xhdpi/img_failure_screen_clody.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/img_guide_first.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-Clody/Clody_Android/65643ebe37474c522f7ac125690bc739768a38c0/app/src/main/res/drawable-xhdpi/img_guide_first.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/img_guide_fourth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-Clody/Clody_Android/65643ebe37474c522f7ac125690bc739768a38c0/app/src/main/res/drawable-xhdpi/img_guide_fourth.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/img_guide_second.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-Clody/Clody_Android/65643ebe37474c522f7ac125690bc739768a38c0/app/src/main/res/drawable-xhdpi/img_guide_second.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/img_guide_third.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-Clody/Clody_Android/65643ebe37474c522f7ac125690bc739768a38c0/app/src/main/res/drawable-xhdpi/img_guide_third.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/img_loading_complete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-Clody/Clody_Android/65643ebe37474c522f7ac125690bc739768a38c0/app/src/main/res/drawable-xhdpi/img_loading_complete.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/img_loading_wait.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-Clody/Clody_Android/65643ebe37474c522f7ac125690bc739768a38c0/app/src/main/res/drawable-xhdpi/img_loading_wait.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/img_reply_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-Clody/Clody_Android/65643ebe37474c522f7ac125690bc739768a38c0/app/src/main/res/drawable-xhdpi/img_reply_logo.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/img_account_management_kakao.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-Clody/Clody_Android/65643ebe37474c522f7ac125690bc739768a38c0/app/src/main/res/drawable-xxhdpi/img_account_management_kakao.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/img_failure_screen_clody.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-Clody/Clody_Android/65643ebe37474c522f7ac125690bc739768a38c0/app/src/main/res/drawable-xxhdpi/img_failure_screen_clody.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/img_guide_first.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-Clody/Clody_Android/65643ebe37474c522f7ac125690bc739768a38c0/app/src/main/res/drawable-xxhdpi/img_guide_first.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/img_guide_fourth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-Clody/Clody_Android/65643ebe37474c522f7ac125690bc739768a38c0/app/src/main/res/drawable-xxhdpi/img_guide_fourth.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/img_guide_second.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-Clody/Clody_Android/65643ebe37474c522f7ac125690bc739768a38c0/app/src/main/res/drawable-xxhdpi/img_guide_second.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/img_guide_third.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-Clody/Clody_Android/65643ebe37474c522f7ac125690bc739768a38c0/app/src/main/res/drawable-xxhdpi/img_guide_third.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/img_loading_complete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-Clody/Clody_Android/65643ebe37474c522f7ac125690bc739768a38c0/app/src/main/res/drawable-xxhdpi/img_loading_complete.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/img_loading_wait.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-Clody/Clody_Android/65643ebe37474c522f7ac125690bc739768a38c0/app/src/main/res/drawable-xxhdpi/img_loading_wait.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/img_reply_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-Clody/Clody_Android/65643ebe37474c522f7ac125690bc739768a38c0/app/src/main/res/drawable-xxhdpi/img_reply_logo.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_account_management_clover.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 15 | 18 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_failure_dialog.xml: -------------------------------------------------------------------------------- 1 | 6 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_home_bottom_clover.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 15 | 18 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_home_disabled_reply_clover.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_home_draft_saved_clover.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_home_enabled_diary_clover.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 9 | 13 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_home_kebab.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_home_list.xml: -------------------------------------------------------------------------------- 1 | 6 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_home_mid_clover.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 15 | 18 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_home_top_clover.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 15 | 18 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_home_under_arrow.xml: -------------------------------------------------------------------------------- 1 | 6 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_home_ungiven_clover.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 15 | 18 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_home_unread_reply.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_home_waiting_reply_clover.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 9 | 13 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_listview_arrow_down.xml: -------------------------------------------------------------------------------- 1 | 6 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_listview_calendar.xml: -------------------------------------------------------------------------------- 1 | 6 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_listview_clover.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 9 | 12 | 15 | 18 | 21 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_listview_delete.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_listview_kebab_menu.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_nickname_back.xml: -------------------------------------------------------------------------------- 1 | 6 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_nickname_change_clean.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 15 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_nickname_change_dismiss.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_nickname_delete.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 15 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_not_found.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 15 | 18 | 21 | 24 | 27 | 30 | 33 | 34 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_notification_setting_switch_thumb.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_picker_dismiss.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_reply_diary_new.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_replyloading_player.xml: -------------------------------------------------------------------------------- 1 | 6 | 11 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_setting_back.xml: -------------------------------------------------------------------------------- 1 | 6 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_setting_next.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_signup_kakao.xml: -------------------------------------------------------------------------------- 1 | 6 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_signup_logo.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 15 | 18 | 21 | 24 | 27 | 30 | 33 | 34 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_terms_check_off_23.xml: -------------------------------------------------------------------------------- 1 | 6 | 11 | 18 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_terms_check_off_25.xml: -------------------------------------------------------------------------------- 1 | 6 | 11 | 18 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_terms_check_on_23.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_terms_check_on_25.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_terms_next.xml: -------------------------------------------------------------------------------- 1 | 6 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_timereminder_down.xml: -------------------------------------------------------------------------------- 1 | 6 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_toast_check_on_18.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_toast_error.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_writediary__help_close.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_writediary_add.xml: -------------------------------------------------------------------------------- 1 | 6 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_writediary_help.xml: -------------------------------------------------------------------------------- 1 | 6 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_writediary_kebab.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/img_account_management_kakao.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-Clody/Clody_Android/65643ebe37474c522f7ac125690bc739768a38c0/app/src/main/res/drawable/img_account_management_kakao.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/img_failure_screen_clody.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-Clody/Clody_Android/65643ebe37474c522f7ac125690bc739768a38c0/app/src/main/res/drawable/img_failure_screen_clody.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/img_google_button_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-Clody/Clody_Android/65643ebe37474c522f7ac125690bc739768a38c0/app/src/main/res/drawable/img_google_button_logo.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/img_guide_first.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-Clody/Clody_Android/65643ebe37474c522f7ac125690bc739768a38c0/app/src/main/res/drawable/img_guide_first.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/img_guide_fourth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-Clody/Clody_Android/65643ebe37474c522f7ac125690bc739768a38c0/app/src/main/res/drawable/img_guide_fourth.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/img_guide_second.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-Clody/Clody_Android/65643ebe37474c522f7ac125690bc739768a38c0/app/src/main/res/drawable/img_guide_second.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/img_guide_third.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-Clody/Clody_Android/65643ebe37474c522f7ac125690bc739768a38c0/app/src/main/res/drawable/img_guide_third.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/img_inspection_dialog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-Clody/Clody_Android/65643ebe37474c522f7ac125690bc739768a38c0/app/src/main/res/drawable/img_inspection_dialog.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/img_loading_complete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-Clody/Clody_Android/65643ebe37474c522f7ac125690bc739768a38c0/app/src/main/res/drawable/img_loading_complete.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/img_loading_wait.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-Clody/Clody_Android/65643ebe37474c522f7ac125690bc739768a38c0/app/src/main/res/drawable/img_loading_wait.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/img_reply_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-Clody/Clody_Android/65643ebe37474c522f7ac125690bc739768a38c0/app/src/main/res/drawable/img_reply_logo.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/img_splash_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-Clody/Clody_Android/65643ebe37474c522f7ac125690bc739768a38c0/app/src/main/res/drawable/img_splash_logo.png -------------------------------------------------------------------------------- /app/src/main/res/font/pretendard_medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-Clody/Clody_Android/65643ebe37474c522f7ac125690bc739768a38c0/app/src/main/res/font/pretendard_medium.ttf -------------------------------------------------------------------------------- /app/src/main/res/font/pretendard_regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-Clody/Clody_Android/65643ebe37474c522f7ac125690bc739768a38c0/app/src/main/res/font/pretendard_regular.ttf -------------------------------------------------------------------------------- /app/src/main/res/font/pretendard_semibold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-Clody/Clody_Android/65643ebe37474c522f7ac125690bc739768a38c0/app/src/main/res/font/pretendard_semibold.ttf -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_dev.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_dev_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-Clody/Clody_Android/65643ebe37474c522f7ac125690bc739768a38c0/app/src/main/res/mipmap-hdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_dev.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-Clody/Clody_Android/65643ebe37474c522f7ac125690bc739768a38c0/app/src/main/res/mipmap-hdpi/ic_launcher_dev.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_dev_foreground.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-Clody/Clody_Android/65643ebe37474c522f7ac125690bc739768a38c0/app/src/main/res/mipmap-hdpi/ic_launcher_dev_foreground.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_dev_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-Clody/Clody_Android/65643ebe37474c522f7ac125690bc739768a38c0/app/src/main/res/mipmap-hdpi/ic_launcher_dev_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-Clody/Clody_Android/65643ebe37474c522f7ac125690bc739768a38c0/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-Clody/Clody_Android/65643ebe37474c522f7ac125690bc739768a38c0/app/src/main/res/mipmap-mdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_dev.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-Clody/Clody_Android/65643ebe37474c522f7ac125690bc739768a38c0/app/src/main/res/mipmap-mdpi/ic_launcher_dev.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_dev_foreground.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-Clody/Clody_Android/65643ebe37474c522f7ac125690bc739768a38c0/app/src/main/res/mipmap-mdpi/ic_launcher_dev_foreground.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_dev_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-Clody/Clody_Android/65643ebe37474c522f7ac125690bc739768a38c0/app/src/main/res/mipmap-mdpi/ic_launcher_dev_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-Clody/Clody_Android/65643ebe37474c522f7ac125690bc739768a38c0/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-Clody/Clody_Android/65643ebe37474c522f7ac125690bc739768a38c0/app/src/main/res/mipmap-xhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_dev.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-Clody/Clody_Android/65643ebe37474c522f7ac125690bc739768a38c0/app/src/main/res/mipmap-xhdpi/ic_launcher_dev.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_dev_foreground.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-Clody/Clody_Android/65643ebe37474c522f7ac125690bc739768a38c0/app/src/main/res/mipmap-xhdpi/ic_launcher_dev_foreground.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_dev_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-Clody/Clody_Android/65643ebe37474c522f7ac125690bc739768a38c0/app/src/main/res/mipmap-xhdpi/ic_launcher_dev_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-Clody/Clody_Android/65643ebe37474c522f7ac125690bc739768a38c0/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-Clody/Clody_Android/65643ebe37474c522f7ac125690bc739768a38c0/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_dev.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-Clody/Clody_Android/65643ebe37474c522f7ac125690bc739768a38c0/app/src/main/res/mipmap-xxhdpi/ic_launcher_dev.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_dev_foreground.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-Clody/Clody_Android/65643ebe37474c522f7ac125690bc739768a38c0/app/src/main/res/mipmap-xxhdpi/ic_launcher_dev_foreground.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_dev_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-Clody/Clody_Android/65643ebe37474c522f7ac125690bc739768a38c0/app/src/main/res/mipmap-xxhdpi/ic_launcher_dev_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-Clody/Clody_Android/65643ebe37474c522f7ac125690bc739768a38c0/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-Clody/Clody_Android/65643ebe37474c522f7ac125690bc739768a38c0/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_dev.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-Clody/Clody_Android/65643ebe37474c522f7ac125690bc739768a38c0/app/src/main/res/mipmap-xxxhdpi/ic_launcher_dev.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_dev_foreground.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-Clody/Clody_Android/65643ebe37474c522f7ac125690bc739768a38c0/app/src/main/res/mipmap-xxxhdpi/ic_launcher_dev_foreground.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_dev_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-Clody/Clody_Android/65643ebe37474c522f7ac125690bc739768a38c0/app/src/main/res/mipmap-xxxhdpi/ic_launcher_dev_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-Clody/Clody_Android/65643ebe37474c522f7ac125690bc739768a38c0/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.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 | #FFFFD84D 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/values/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFFFFF 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /app/src/test/java/com/sopt/clody/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.sopt.clody 2 | 3 | import org.junit.Assert.assertEquals 4 | import org.junit.Test 5 | 6 | /** 7 | * Example local unit test, which will execute on the development machine (host). 8 | * 9 | * See [testing documentation](http://d.android.com/tools/testing). 10 | */ 11 | class ExampleUnitTest { 12 | @Test 13 | fun addition_isCorrect() { 14 | assertEquals(4, 2 + 2) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | plugins { 3 | alias(libs.plugins.android.application) apply false 4 | alias(libs.plugins.kotlin.android) apply false 5 | alias(libs.plugins.kotlin.compose) apply false 6 | alias(libs.plugins.kotlin.serialization) apply false 7 | alias(libs.plugins.ksp) apply false 8 | alias(libs.plugins.hilt) apply false 9 | alias(libs.plugins.ktlint) apply false 10 | alias(libs.plugins.google.services) apply false 11 | alias(libs.plugins.firebase.crashlytics.plugin) apply false 12 | alias(libs.plugins.firebase.app.distribution) apply false 13 | } 14 | -------------------------------------------------------------------------------- /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=-Xmx2048m -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/Team-Clody/Clody_Android/65643ebe37474c522f7ac125690bc739768a38c0/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Tue Jun 25 23:50:23 KST 2024 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") 2 | pluginManagement { 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 | @Suppress("UnstableApiUsage") 16 | dependencyResolutionManagement { 17 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 18 | repositories { 19 | google() 20 | mavenCentral() 21 | maven(url = "https://jitpack.io") 22 | maven { url = java.net.URI("https://devrepo.kakao.com/nexus/content/groups/public/") } 23 | maven(url = "https://sdk-download.airbridge.io/maven") 24 | } 25 | } 26 | 27 | rootProject.name = "CLODY" 28 | include(":app") 29 | --------------------------------------------------------------------------------