From acb3b8d0c5ba25898c571c36afe4b70255b9c67e Mon Sep 17 00:00:00 2001 From: taetae98coding Date: Mon, 11 Nov 2024 03:00:12 +0900 Subject: [PATCH] 1.1.0 feat: server - FCM refactor: app - compose feat: app - FCM --- .github/workflows/build.yml | 35 +- .../workflows/firebase_app_distribution.yml | 7 +- .gitignore | 3 +- CHANGELOG.md | 13 +- Diary/Diary.xcodeproj/project.pbxproj | 134 ++- .../xcshareddata/swiftpm/Package.resolved | 8 +- .../{CI.xcscheme => DevRelease.xcscheme} | 10 +- Diary/Diary/AppDelegate.swift | 47 +- Diary/Diary/Diary.entitlements | 8 + Diary/Diary/Info.plist | 5 + Diary/ExportOptions.plist | 11 +- .../diary/core/coroutines/CoroutinesModule.kt | 7 + .../database/memory/MemoBackupMemoryDao.kt | 25 - .../database/room/dao/MemoBackupRoomDao.kt | 20 - .../core/diary/database/MemoBackupDao.kt | 2 - .../core/diary/service/fcm/FCMService.kt | 33 + app/data/account/build.gradle.kts | 1 - .../repository/AccountRepositoryImpl.kt | 29 - .../backup/repository/BackupRepositoryImpl.kt | 15 +- app/data/credential/README.md | 3 + app/data/credential/build.gradle.kts | 15 + .../data/credential/CredentialDataModule.kt | 8 + .../repository/CredentialRepositoryImpl.kt | 31 + app/data/fcm/README.md | 3 + app/data/fcm/build.gradle.kts | 15 + .../diary/data/fcm/FCMDataModule.kt | 8 + .../data/fcm/repository/FCMRepositoryImpl.kt | 20 + .../memo/repository/MemoRepositoryImpl.kt | 20 +- .../account/repository/AccountRepository.kt | 6 - .../domain/account/usecase/LoginUseCase.kt | 18 - .../domain/account/usecase/LogoutUseCase.kt | 13 - .../backup/repository/BackupRepository.kt | 8 +- .../domain/backup/usecase/BackupUseCase.kt | 27 +- app/domain/credential/README.md | 3 + app/domain/credential/build.gradle.kts | 15 + .../credential/CredentialDomainModule.kt | 8 + .../repository/CredentialRepository.kt | 12 + .../domain/credential}/usecase/JoinUseCase.kt | 6 +- .../domain/credential/usecase/LoginUseCase.kt | 33 + .../credential/usecase/LogoutUseCase.kt | 21 + app/domain/fcm/README.md | 3 + app/domain/fcm/build.gradle.kts | 13 + .../diary/domain/fcm/FCMDomainModule.kt | 8 + .../domain/fcm/repository/FCMRepository.kt | 6 + .../fcm/usecase/UpdateFCMTokenUseCase.kt | 24 + .../domain/fetch/usecase/FetchUseCase.kt | 19 +- app/domain/memo/build.gradle.kts | 1 + .../domain/memo/repository/MemoRepository.kt | 8 +- .../domain/memo/usecase/AddMemoUseCase.kt | 17 +- .../domain/memo/usecase/DeleteMemoUseCase.kt | 16 +- .../domain/memo/usecase/FinishMemoUseCase.kt | 16 +- .../domain/memo/usecase/RestartMemoUseCase.kt | 17 +- .../domain/memo/usecase/UpdateMemoUseCase.kt | 18 +- app/feature/account/build.gradle.kts | 1 + .../feature/account/join/JoinViewModel.kt | 4 +- .../feature/account/login/LoginViewModel.kt | 2 +- app/feature/more/build.gradle.kts | 1 + .../more/viewmodel/MoreAccountViewModel.kt | 2 +- app/platform/android/build.gradle.kts | 11 +- .../realReleaseRuntimeClasspath.txt | 143 +-- .../android/src/main/AndroidManifest.xml | 24 +- .../taetae98coding/diary/DiaryActivity.kt | 9 + .../taetae98coding/diary/DiaryApplication.kt | 26 +- .../taetae98coding/diary/KoinAndroidModule.kt | 8 + .../initializer/BackupManagerInitializer.kt | 23 + .../initializer/FCMManagerInitializer.kt | 23 + .../initializer/FetchManagerInitializer.kt | 23 + .../diary/initializer/KoinInitializer.kt | 2 + .../DefaultNotificationManager.kt | 41 + .../service/DiaryFirebaseMessagingService.kt | 45 + .../res/drawable/ic_android_black_24dp.xml | 5 + .../src/main/res/values-ko/strings.xml | 5 + .../android/src/main/res/values/strings.xml | 5 + app/platform/common/build.gradle.kts | 5 + .../taetae98coding/diary/app/AppModule.kt | 16 + .../diary/app/manager/FCMManager.kt | 22 + .../InitFirebaseMessagingManaver.kt | 18 + .../diary/initializer/IosInitializer.kt | 1 + build-logic/build.gradle.kts | 5 + .../main/kotlin/ext/KotlinMultiplatformExt.kt | 5 + .../plugin/cocoapods/CocoapodsPlugin.kt | 25 + .../kotlin/plugin/compose/ComposePlugin.kt | 2 + build.gradle.kts | 11 + .../model/request/fcm/DeleteFCMRequest.kt | 10 + .../model/request/fcm/UpsertFCMRequest.kt | 10 + .../graphs/dep_graph_app_data_account.svg | 92 +- .../graphs/dep_graph_app_data_credential.svg | 221 ++++ docs/images/graphs/dep_graph_app_data_fcm.svg | 141 +++ .../images/graphs/dep_graph_app_data_memo.svg | 154 ++- .../dep_graph_app_domain_credential.svg | 169 +++ .../graphs/dep_graph_app_domain_fcm.svg | 77 ++ .../graphs/dep_graph_app_domain_memo.svg | 122 +- .../graphs/dep_graph_app_feature_account.svg | 290 +++-- .../graphs/dep_graph_app_feature_calendar.svg | 288 +++-- .../graphs/dep_graph_app_feature_memo.svg | 228 ++-- .../graphs/dep_graph_app_feature_more.svg | 290 +++-- .../graphs/dep_graph_app_platform_android.svg | 1054 +++++++++------- .../graphs/dep_graph_app_platform_common.svg | 918 ++++++++------ .../graphs/dep_graph_app_platform_ios.svg | 1050 +++++++++------- .../graphs/dep_graph_app_platform_jvm.svg | 1062 ++++++++++------- .../graphs/dep_graph_app_platform_wasm.svg | 1004 +++++++++------- .../dep_graph_library_firebase_common.svg | 9 + .../dep_graph_library_firebase_messaging.svg | 17 + docs/images/graphs/dep_graph_server_app.svg | 252 ++-- .../graphs/dep_graph_server_data_fcm.svg | 57 + .../graphs/dep_graph_server_domain_fcm.svg | 33 + .../graphs/dep_graph_server_feature_fcm.svg | 57 + gradle.properties | 4 +- gradle/libs.versions.toml | 15 +- gradle/wrapper/gradle-wrapper.properties | 2 +- library/firebase-common/README.md | 3 + library/firebase-common/build.gradle.kts | 8 + .../diary/library/firebase/KFirebase.kt | 3 + library/firebase-messaging/README.md | 3 + library/firebase-messaging/build.gradle.kts | 48 + .../KFirebaseMessagingExt.android.kt | 6 + .../messaging/KFirebaseMessagingImpl.kt | 11 + .../firebase/messaging/KFirebaseMessaging.kt | 5 + .../messaging/KFirebaseMessagingExt.kt | 5 + .../messaging/KFirebaseMessagingExt.ios.kt | 6 + .../messaging/KFirebaseMessagingImpl.kt | 24 + .../KFirebaseMessagingExt.nonSupport.kt | 6 + .../messaging/KFirebaseMessagingImpl.kt | 7 + server/app/build.gradle.kts | 5 + server/app/dependencies/runtimeClasspath.txt | 109 +- .../taetae98coding/diary/Application.kt | 2 + .../diary/plugin/DatabasePlugin.kt | 3 +- .../diary/plugin/FirebasePlugin.kt | 19 + .../taetae98coding/diary/plugin/KoinPlugin.kt | 4 + .../diary/plugin/RoutingPlugin.kt | 2 + .../app/src/main/resources/application.yaml | 5 +- .../diary/core/database/FCMTokenTable.kt | 41 + server/data/fcm/README.md | 3 + server/data/fcm/build.gradle.kts | 11 + .../diary/data/fcm/FCMDataModule.kt | 8 + .../data/fcm/repository/FCMRepositoryImpl.kt | 16 + server/domain/fcm/README.md | 3 + server/domain/fcm/build.gradle.kts | 3 + .../diary/domain/fcm/FCMDomainModule.kt | 8 + .../domain/fcm/repository/FCMRepository.kt | 7 + .../fcm/usecase/DeleteFCMTokenUseCase.kt | 11 + .../fcm/usecase/UpsertFCMTokenUseCase.kt | 11 + server/feature/fcm/README.md | 3 + server/feature/fcm/build.gradle.kts | 7 + .../diary/feature/fcm/FCMRouting.kt | 46 + .../diary/feature/memo/MemoRouting.kt | 9 - settings.gradle.kts | 10 + 147 files changed, 6260 insertions(+), 3232 deletions(-) rename Diary/Diary.xcodeproj/xcshareddata/xcschemes/{CI.xcscheme => DevRelease.xcscheme} (92%) create mode 100644 Diary/Diary/Diary.entitlements create mode 100644 app/core/diary-service/src/commonMain/kotlin/io/github/taetae98coding/diary/core/diary/service/fcm/FCMService.kt create mode 100644 app/data/credential/README.md create mode 100644 app/data/credential/build.gradle.kts create mode 100644 app/data/credential/src/commonMain/kotlin/io/github/taetae98coding/diary/data/credential/CredentialDataModule.kt create mode 100644 app/data/credential/src/commonMain/kotlin/io/github/taetae98coding/diary/data/credential/repository/CredentialRepositoryImpl.kt create mode 100644 app/data/fcm/README.md create mode 100644 app/data/fcm/build.gradle.kts create mode 100644 app/data/fcm/src/commonMain/kotlin/io/github/taetae98coding/diary/data/fcm/FCMDataModule.kt create mode 100644 app/data/fcm/src/commonMain/kotlin/io/github/taetae98coding/diary/data/fcm/repository/FCMRepositoryImpl.kt delete mode 100644 app/domain/account/src/commonMain/kotlin/io/github/taetae98coding/diary/domain/account/usecase/LoginUseCase.kt delete mode 100644 app/domain/account/src/commonMain/kotlin/io/github/taetae98coding/diary/domain/account/usecase/LogoutUseCase.kt create mode 100644 app/domain/credential/README.md create mode 100644 app/domain/credential/build.gradle.kts create mode 100644 app/domain/credential/src/commonMain/kotlin/io/github/taetae98coding/diary/domain/credential/CredentialDomainModule.kt create mode 100644 app/domain/credential/src/commonMain/kotlin/io/github/taetae98coding/diary/domain/credential/repository/CredentialRepository.kt rename app/domain/{account/src/commonMain/kotlin/io/github/taetae98coding/diary/domain/account => credential/src/commonMain/kotlin/io/github/taetae98coding/diary/domain/credential}/usecase/JoinUseCase.kt (71%) create mode 100644 app/domain/credential/src/commonMain/kotlin/io/github/taetae98coding/diary/domain/credential/usecase/LoginUseCase.kt create mode 100644 app/domain/credential/src/commonMain/kotlin/io/github/taetae98coding/diary/domain/credential/usecase/LogoutUseCase.kt create mode 100644 app/domain/fcm/README.md create mode 100644 app/domain/fcm/build.gradle.kts create mode 100644 app/domain/fcm/src/commonMain/kotlin/io/github/taetae98coding/diary/domain/fcm/FCMDomainModule.kt create mode 100644 app/domain/fcm/src/commonMain/kotlin/io/github/taetae98coding/diary/domain/fcm/repository/FCMRepository.kt create mode 100644 app/domain/fcm/src/commonMain/kotlin/io/github/taetae98coding/diary/domain/fcm/usecase/UpdateFCMTokenUseCase.kt create mode 100644 app/platform/android/src/main/kotlin/io/github/taetae98coding/diary/KoinAndroidModule.kt create mode 100644 app/platform/android/src/main/kotlin/io/github/taetae98coding/diary/initializer/BackupManagerInitializer.kt create mode 100644 app/platform/android/src/main/kotlin/io/github/taetae98coding/diary/initializer/FCMManagerInitializer.kt create mode 100644 app/platform/android/src/main/kotlin/io/github/taetae98coding/diary/initializer/FetchManagerInitializer.kt create mode 100644 app/platform/android/src/main/kotlin/io/github/taetae98coding/diary/notification/DefaultNotificationManager.kt create mode 100644 app/platform/android/src/main/kotlin/io/github/taetae98coding/diary/service/DiaryFirebaseMessagingService.kt create mode 100644 app/platform/android/src/main/res/drawable/ic_android_black_24dp.xml create mode 100644 app/platform/android/src/main/res/values-ko/strings.xml create mode 100644 app/platform/android/src/main/res/values/strings.xml create mode 100644 app/platform/common/src/commonMain/kotlin/io/github/taetae98coding/diary/app/manager/FCMManager.kt create mode 100644 app/platform/ios/src/iosMain/kotlin/io/github/taetae98coding/diary/initializer/InitFirebaseMessagingManaver.kt create mode 100644 build-logic/src/main/kotlin/plugin/cocoapods/CocoapodsPlugin.kt create mode 100644 common/model/src/commonMain/kotlin/io/github/taetae98coding/diary/common/model/request/fcm/DeleteFCMRequest.kt create mode 100644 common/model/src/commonMain/kotlin/io/github/taetae98coding/diary/common/model/request/fcm/UpsertFCMRequest.kt create mode 100644 docs/images/graphs/dep_graph_app_data_credential.svg create mode 100644 docs/images/graphs/dep_graph_app_data_fcm.svg create mode 100644 docs/images/graphs/dep_graph_app_domain_credential.svg create mode 100644 docs/images/graphs/dep_graph_app_domain_fcm.svg create mode 100644 docs/images/graphs/dep_graph_library_firebase_common.svg create mode 100644 docs/images/graphs/dep_graph_library_firebase_messaging.svg create mode 100644 docs/images/graphs/dep_graph_server_data_fcm.svg create mode 100644 docs/images/graphs/dep_graph_server_domain_fcm.svg create mode 100644 docs/images/graphs/dep_graph_server_feature_fcm.svg create mode 100644 library/firebase-common/README.md create mode 100644 library/firebase-common/build.gradle.kts create mode 100644 library/firebase-common/src/commonMain/kotlin/io/github/taetae98coding/diary/library/firebase/KFirebase.kt create mode 100644 library/firebase-messaging/README.md create mode 100644 library/firebase-messaging/build.gradle.kts create mode 100644 library/firebase-messaging/src/androidMain/kotlin/io/github/taetae98coding/diary/library/firebase/messaging/KFirebaseMessagingExt.android.kt create mode 100644 library/firebase-messaging/src/androidMain/kotlin/io/github/taetae98coding/diary/library/firebase/messaging/KFirebaseMessagingImpl.kt create mode 100644 library/firebase-messaging/src/commonMain/kotlin/io/github/taetae98coding/diary/library/firebase/messaging/KFirebaseMessaging.kt create mode 100644 library/firebase-messaging/src/commonMain/kotlin/io/github/taetae98coding/diary/library/firebase/messaging/KFirebaseMessagingExt.kt create mode 100644 library/firebase-messaging/src/iosMain/kotlin/io/github/taetae98coding/diary/library/firebase/messaging/KFirebaseMessagingExt.ios.kt create mode 100644 library/firebase-messaging/src/iosMain/kotlin/io/github/taetae98coding/diary/library/firebase/messaging/KFirebaseMessagingImpl.kt create mode 100644 library/firebase-messaging/src/nonSupportMain/kotlin/io/github/taetae98coding/diary/library/firebase/messaging/KFirebaseMessagingExt.nonSupport.kt create mode 100644 library/firebase-messaging/src/nonSupportMain/kotlin/io/github/taetae98coding/diary/library/firebase/messaging/KFirebaseMessagingImpl.kt create mode 100644 server/app/src/main/kotlin/io/github/taetae98coding/diary/plugin/FirebasePlugin.kt create mode 100644 server/core/database/src/main/kotlin/io/github/taetae98coding/diary/core/database/FCMTokenTable.kt create mode 100644 server/data/fcm/README.md create mode 100644 server/data/fcm/build.gradle.kts create mode 100644 server/data/fcm/src/main/kotlin/io/github/taetae98coding/diary/data/fcm/FCMDataModule.kt create mode 100644 server/data/fcm/src/main/kotlin/io/github/taetae98coding/diary/data/fcm/repository/FCMRepositoryImpl.kt create mode 100644 server/domain/fcm/README.md create mode 100644 server/domain/fcm/build.gradle.kts create mode 100644 server/domain/fcm/src/main/kotlin/io/github/taetae98coding/diary/domain/fcm/FCMDomainModule.kt create mode 100644 server/domain/fcm/src/main/kotlin/io/github/taetae98coding/diary/domain/fcm/repository/FCMRepository.kt create mode 100644 server/domain/fcm/src/main/kotlin/io/github/taetae98coding/diary/domain/fcm/usecase/DeleteFCMTokenUseCase.kt create mode 100644 server/domain/fcm/src/main/kotlin/io/github/taetae98coding/diary/domain/fcm/usecase/UpsertFCMTokenUseCase.kt create mode 100644 server/feature/fcm/README.md create mode 100644 server/feature/fcm/build.gradle.kts create mode 100644 server/feature/fcm/src/main/kotlin/io/github/taetae98coding/diary/feature/fcm/FCMRouting.kt diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4eada261..f89876ac 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -84,5 +84,38 @@ jobs: echo '${{ secrets.APPLE_REAL_DEBUG_GOOGLE_SERVICE_INFO_PLIST }}' >> Diary/Secret/RealDebug/GoogleService-Info.plist echo '${{ secrets.APPLE_REAL_RELEASE_GOOGLE_SERVICE_INFO_PLIST }}' >> Diary/Secret/RealRelease/GoogleService-Info.plist + - name: Install the Apple certificate and provisioning profile + env: + BUILD_CERTIFICATE_BASE64: ${{ secrets.APPLE_DISTRIBUTION_CERTIFICATE }} + P12_PASSWORD: ${{ secrets.APPLE_DISTRIBUTION_CERTIFICATE_PASSWORD }} + BUILD_PROVISION_PROFILE_BASE64: ${{ secrets.APPLE_DISTRIBUTION_PROFILE }} + KEYCHAIN_PASSWORD: ${{ secrets.APPLE_DISTRIBUTION_TEMPORARY_KEYCHAIN_PASSWORD }} + run: | + # create variables + CERTIFICATE_PATH=$RUNNER_TEMP/build_certificate.p12 + PP_PATH=$RUNNER_TEMP/build_pp.mobileprovision + KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db + + # import certificate and provisioning profile from secrets + echo -n "$BUILD_CERTIFICATE_BASE64" | base64 --decode -o $CERTIFICATE_PATH + echo -n "$BUILD_PROVISION_PROFILE_BASE64" | base64 --decode -o $PP_PATH + + # create temporary keychain + security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH + security set-keychain-settings -lut 21600 $KEYCHAIN_PATH + security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH + + # import certificate to keychain + security import $CERTIFICATE_PATH -P "$P12_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH + security set-key-partition-list -S apple-tool:,apple: -k "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH + security list-keychain -d user -s $KEYCHAIN_PATH + + # apply provisioning profile + mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles + cp $PP_PATH ~/Library/MobileDevice/Provisioning\ Profiles + + - name: Build Cocoapods + run: ./gradlew buildCocoapods + - name: Build iOS - run: xcodebuild -project Diary/Diary.xcodeproj -scheme CI -destination 'platform=iOS Simulator,name=iPhone 16,OS=18.0' \ No newline at end of file + run: xcodebuild -project Diary/Diary.xcodeproj -scheme RealRelease -destination 'platform=iOS Simulator,name=iPhone 16,OS=18.0' \ No newline at end of file diff --git a/.github/workflows/firebase_app_distribution.yml b/.github/workflows/firebase_app_distribution.yml index ce7d4bf3..65dd7b68 100644 --- a/.github/workflows/firebase_app_distribution.yml +++ b/.github/workflows/firebase_app_distribution.yml @@ -121,8 +121,11 @@ jobs: mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles cp $PP_PATH ~/Library/MobileDevice/Provisioning\ Profiles + - name: Build Cocoapods + run: ./gradlew buildCocoapods + - name: Archive xcarchive - run: xcodebuild -project Diary/Diary.xcodeproj -scheme CI archive -archivePath Diary/build/Diary.xcarchive -allowProvisioningUpdates + run: xcodebuild -project Diary/Diary.xcodeproj -scheme RealRelease archive -archivePath Diary/build/Diary.xcarchive -allowProvisioningUpdates - name: Export ipa run: xcodebuild -exportArchive -archivePath Diary/build/Diary.xcarchive -exportPath Diary/build -exportOptionsPlist Diary/ExportOptions.plist -allowProvisioningUpdates @@ -131,7 +134,7 @@ jobs: uses: actions/upload-artifact@v4 with: name: Diary.ipa - path: Diary/build/Apps/Diary.ipa + path: Diary/build/Diary.ipa iOS-Distribution: needs: [iOS-Build] diff --git a/.gitignore b/.gitignore index 922858cd..4fed5787 100644 --- a/.gitignore +++ b/.gitignore @@ -11,4 +11,5 @@ **/xcuserdata **/dev.xcconfig -**/real.xcconfig \ No newline at end of file +**/real.xcconfig +**/*.podspec \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index c4f486ec..7c78aa2b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,18 @@ All notable changes to this project will be documented in this file. -## [1.0.0] - 2024-11-09 +## [1.1.0] - 2024-11-17 + +### 🚀 Features + +- Server - FCM +- App - FCM + +### 🚜 Refactor + +- App - compose + +## [1.0.0] - 2024-11-10 ### 🚀 Features diff --git a/Diary/Diary.xcodeproj/project.pbxproj b/Diary/Diary.xcodeproj/project.pbxproj index 8062eee1..cd88223f 100644 --- a/Diary/Diary.xcodeproj/project.pbxproj +++ b/Diary/Diary.xcodeproj/project.pbxproj @@ -7,9 +7,11 @@ objects = { /* Begin PBXBuildFile section */ - 3DD9A74F2CDFBAC60023C4EE /* FirebaseAnalyticsWithoutAdIdSupport in Frameworks */ = {isa = PBXBuildFile; productRef = 3DD9A74E2CDFBAC60023C4EE /* FirebaseAnalyticsWithoutAdIdSupport */; }; - 3DD9A7512CDFBAC60023C4EE /* FirebaseCrashlytics in Frameworks */ = {isa = PBXBuildFile; productRef = 3DD9A7502CDFBAC60023C4EE /* FirebaseCrashlytics */; }; - 3DD9A7532CDFBAC60023C4EE /* FirebasePerformance in Frameworks */ = {isa = PBXBuildFile; productRef = 3DD9A7522CDFBAC60023C4EE /* FirebasePerformance */; }; + 3DF5DACD2CE8FF3000E490F3 /* FirebaseAnalyticsWithoutAdIdSupport in Frameworks */ = {isa = PBXBuildFile; productRef = 3DF5DACC2CE8FF3000E490F3 /* FirebaseAnalyticsWithoutAdIdSupport */; }; + 3DF5DACF2CE8FF3000E490F3 /* FirebaseCore in Frameworks */ = {isa = PBXBuildFile; productRef = 3DF5DACE2CE8FF3000E490F3 /* FirebaseCore */; }; + 3DF5DAD12CE8FF3000E490F3 /* FirebaseCrashlytics in Frameworks */ = {isa = PBXBuildFile; productRef = 3DF5DAD02CE8FF3000E490F3 /* FirebaseCrashlytics */; }; + 3DF5DAD32CE8FF3000E490F3 /* FirebaseMessaging in Frameworks */ = {isa = PBXBuildFile; productRef = 3DF5DAD22CE8FF3000E490F3 /* FirebaseMessaging */; }; + 3DF5DAD52CE8FF3000E490F3 /* FirebasePerformance in Frameworks */ = {isa = PBXBuildFile; productRef = 3DF5DAD42CE8FF3000E490F3 /* FirebasePerformance */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -42,9 +44,11 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 3DD9A74F2CDFBAC60023C4EE /* FirebaseAnalyticsWithoutAdIdSupport in Frameworks */, - 3DD9A7512CDFBAC60023C4EE /* FirebaseCrashlytics in Frameworks */, - 3DD9A7532CDFBAC60023C4EE /* FirebasePerformance in Frameworks */, + 3DF5DAD32CE8FF3000E490F3 /* FirebaseMessaging in Frameworks */, + 3DF5DAD12CE8FF3000E490F3 /* FirebaseCrashlytics in Frameworks */, + 3DF5DAD52CE8FF3000E490F3 /* FirebasePerformance in Frameworks */, + 3DF5DACF2CE8FF3000E490F3 /* FirebaseCore in Frameworks */, + 3DF5DACD2CE8FF3000E490F3 /* FirebaseAnalyticsWithoutAdIdSupport in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -79,7 +83,6 @@ 3DDC71F22CCD5903001193A2 /* Sources */, 3DDC71F32CCD5903001193A2 /* Frameworks */, 3DDC71F42CCD5903001193A2 /* Resources */, - 3DD9A7552CDFC3240023C4EE /* ShellScript */, ); buildRules = ( ); @@ -90,9 +93,11 @@ ); name = Diary; packageProductDependencies = ( - 3DD9A74E2CDFBAC60023C4EE /* FirebaseAnalyticsWithoutAdIdSupport */, - 3DD9A7502CDFBAC60023C4EE /* FirebaseCrashlytics */, - 3DD9A7522CDFBAC60023C4EE /* FirebasePerformance */, + 3DF5DACC2CE8FF3000E490F3 /* FirebaseAnalyticsWithoutAdIdSupport */, + 3DF5DACE2CE8FF3000E490F3 /* FirebaseCore */, + 3DF5DAD02CE8FF3000E490F3 /* FirebaseCrashlytics */, + 3DF5DAD22CE8FF3000E490F3 /* FirebaseMessaging */, + 3DF5DAD42CE8FF3000E490F3 /* FirebasePerformance */, ); productName = Diary; productReference = 3DDC71F62CCD5903001193A2 /* Diary.app */; @@ -124,7 +129,7 @@ mainGroup = 3DDC71ED2CCD5903001193A2; minimizedProjectReferenceProxies = 1; packageReferences = ( - 3DD9A74D2CDFBAC60023C4EE /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */, + 3DF5DACB2CE8FF3000E490F3 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */, ); preferredProjectObjectVersion = 77; productRefGroup = 3DDC71F72CCD5903001193A2 /* Products */; @@ -163,29 +168,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "cd \"$SRCROOT/..\"\n\n\ncase \"${CONFIGURATION}\" in\n \"DevDebug\" )\ncp -r \"$SRCROOT/Secret/DevDebug/GoogleService-Info.plist\" \"${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app/GoogleService-Info.plist\" ;;\n \"DevRelease\" )\ncp -r \"$SRCROOT/Secret/DevRelease/GoogleService-Info.plist\" \"${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app/GoogleService-Info.plist\" ;;\n \"RealDebug\" )\ncp -r \"$SRCROOT/Secret/RealDebug/GoogleService-Info.plist\" \"${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app/GoogleService-Info.plist\" ;;\n \"RealRelease\" )\ncp -r \"$SRCROOT/Secret/RealRelease/GoogleService-Info.plist\" \"${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app/GoogleService-Info.plist\" ;;\n*)\n;;\nesac\n"; - }; - 3DD9A7552CDFC3240023C4EE /* ShellScript */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}", - "${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Resources/DWARF/${PRODUCT_NAME}", - "${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Info.plist", - "$(TARGET_BUILD_DIR)/$(UNLOCALIZED_RESOURCES_FOLDER_PATH)/GoogleService-Info.plist", - "$(TARGET_BUILD_DIR)/$(EXECUTABLE_PATH)", - ); - outputFileListPaths = ( - ); - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${BUILD_DIR%/Build/*}/SourcePackages/checkouts/firebase-ios-sdk/Crashlytics/run\"\n"; + shellScript = "cd \"$SRCROOT/..\"\n\ncase \"${CONFIGURATION}\" in\n \"DevDebug\" )\ncp -r \"$SRCROOT/Secret/DevDebug/GoogleService-Info.plist\" \"${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app/GoogleService-Info.plist\" ;;\n \"DevRelease\" )\ncp -r \"$SRCROOT/Secret/DevRelease/GoogleService-Info.plist\" \"${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app/GoogleService-Info.plist\" ;;\n \"RealDebug\" )\ncp -r \"$SRCROOT/Secret/RealDebug/GoogleService-Info.plist\" \"${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app/GoogleService-Info.plist\" ;;\n \"RealRelease\" )\ncp -r \"$SRCROOT/Secret/RealRelease/GoogleService-Info.plist\" \"${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app/GoogleService-Info.plist\" ;;\n*)\n;;\nesac\n"; }; 3DDC72072CCD5943001193A2 /* Run Script */ = { isa = PBXShellScriptBuildPhase; @@ -203,7 +186,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "cd \"$SRCROOT/..\"\n\n\ncase \"${CONFIGURATION}\" in\n \"DevDebug\" )\n./gradlew :app:platform:ios:embedAndSignAppleFrameworkForXcode -Pbuildkonfig.flavor=dev ;;\n \"DevRelease\" )\n./gradlew :app:platform:ios:embedAndSignAppleFrameworkForXcode -Pbuildkonfig.flavor=dev ;;\n \"RealDebug\" )\n./gradlew :app:platform:ios:embedAndSignAppleFrameworkForXcode -Pbuildkonfig.flavor=real ;;\n \"RealRelease\" )\n./gradlew :app:platform:ios:embedAndSignAppleFrameworkForXcode -Pbuildkonfig.flavor=real ;;\n*)\n;;\nesac\n"; + shellScript = "cd \"$SRCROOT/..\"\n\n./gradlew buildCocoapods\n\ncase \"${CONFIGURATION}\" in\n \"DevDebug\" )\n./gradlew :app:platform:ios:embedAndSignAppleFrameworkForXcode -Pbuildkonfig.flavor=dev ;;\n \"DevRelease\" )\n./gradlew :app:platform:ios:embedAndSignAppleFrameworkForXcode -Pbuildkonfig.flavor=dev ;;\n \"RealDebug\" )\n./gradlew :app:platform:ios:embedAndSignAppleFrameworkForXcode -Pbuildkonfig.flavor=real ;;\n \"RealRelease\" )\n./gradlew :app:platform:ios:embedAndSignAppleFrameworkForXcode -Pbuildkonfig.flavor=real ;;\n*)\n;;\nesac\n"; }; /* End PBXShellScriptBuildPhase section */ @@ -251,6 +234,7 @@ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "Apple Development: taejong kang (32WGJ7LQRX)"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -286,10 +270,14 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CODE_SIGN_ENTITLEMENTS = Diary/Diary.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 2; DEVELOPMENT_ASSET_PATHS = "\"Diary/Preview Content\""; - DEVELOPMENT_TEAM = 4TV6L66XZ8; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 4TV6L66XZ8; ENABLE_PREVIEWS = YES; ENABLE_USER_SCRIPT_SANDBOXING = NO; GENERATE_INFOPLIST_FILE = YES; @@ -306,9 +294,11 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.0; + MARKETING_VERSION = 1.1.0; PRODUCT_BUNDLE_IDENTIFIER = io.github.taetae98coding.diary.debug; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = DiaryRealDebug; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; @@ -348,6 +338,7 @@ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "Apple Development: taejong kang (32WGJ7LQRX)"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; @@ -376,10 +367,11 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = Diary/Diary.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; DEVELOPMENT_ASSET_PATHS = "\"Diary/Preview Content\""; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 4TV6L66XZ8; @@ -399,11 +391,11 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.0; + MARKETING_VERSION = 1.1.0; PRODUCT_BUNDLE_IDENTIFIER = io.github.taetae98coding.diary; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; - "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = adhoc; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = DiaryRealRelease; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; @@ -443,6 +435,7 @@ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "Apple Development: taejong kang (32WGJ7LQRX)"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -478,10 +471,14 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CODE_SIGN_ENTITLEMENTS = Diary/Diary.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 2; DEVELOPMENT_ASSET_PATHS = "\"Diary/Preview Content\""; - DEVELOPMENT_TEAM = 4TV6L66XZ8; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 4TV6L66XZ8; ENABLE_PREVIEWS = YES; ENABLE_USER_SCRIPT_SANDBOXING = NO; GENERATE_INFOPLIST_FILE = YES; @@ -498,9 +495,11 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.0; + MARKETING_VERSION = 1.1.0; PRODUCT_BUNDLE_IDENTIFIER = io.github.taetae98coding.diary.dev.debug; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = DiaryDevDebug; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; @@ -540,6 +539,7 @@ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "Apple Development: taejong kang (32WGJ7LQRX)"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; @@ -568,10 +568,14 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CODE_SIGN_ENTITLEMENTS = Diary/Diary.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 2; DEVELOPMENT_ASSET_PATHS = "\"Diary/Preview Content\""; - DEVELOPMENT_TEAM = 4TV6L66XZ8; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 4TV6L66XZ8; ENABLE_PREVIEWS = YES; ENABLE_USER_SCRIPT_SANDBOXING = NO; GENERATE_INFOPLIST_FILE = YES; @@ -588,9 +592,11 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.0; + MARKETING_VERSION = 1.1.0; PRODUCT_BUNDLE_IDENTIFIER = io.github.taetae98coding.diary.dev; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = DiaryDevRelease; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; @@ -625,30 +631,40 @@ /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ - 3DD9A74D2CDFBAC60023C4EE /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */ = { + 3DF5DACB2CE8FF3000E490F3 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/firebase/firebase-ios-sdk"; requirement = { kind = exactVersion; - version = 11.4.0; + version = 11.5.0; }; }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ - 3DD9A74E2CDFBAC60023C4EE /* FirebaseAnalyticsWithoutAdIdSupport */ = { + 3DF5DACC2CE8FF3000E490F3 /* FirebaseAnalyticsWithoutAdIdSupport */ = { isa = XCSwiftPackageProductDependency; - package = 3DD9A74D2CDFBAC60023C4EE /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; + package = 3DF5DACB2CE8FF3000E490F3 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; productName = FirebaseAnalyticsWithoutAdIdSupport; }; - 3DD9A7502CDFBAC60023C4EE /* FirebaseCrashlytics */ = { + 3DF5DACE2CE8FF3000E490F3 /* FirebaseCore */ = { isa = XCSwiftPackageProductDependency; - package = 3DD9A74D2CDFBAC60023C4EE /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; + package = 3DF5DACB2CE8FF3000E490F3 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; + productName = FirebaseCore; + }; + 3DF5DAD02CE8FF3000E490F3 /* FirebaseCrashlytics */ = { + isa = XCSwiftPackageProductDependency; + package = 3DF5DACB2CE8FF3000E490F3 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; productName = FirebaseCrashlytics; }; - 3DD9A7522CDFBAC60023C4EE /* FirebasePerformance */ = { + 3DF5DAD22CE8FF3000E490F3 /* FirebaseMessaging */ = { + isa = XCSwiftPackageProductDependency; + package = 3DF5DACB2CE8FF3000E490F3 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; + productName = FirebaseMessaging; + }; + 3DF5DAD42CE8FF3000E490F3 /* FirebasePerformance */ = { isa = XCSwiftPackageProductDependency; - package = 3DD9A74D2CDFBAC60023C4EE /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; + package = 3DF5DACB2CE8FF3000E490F3 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; productName = FirebasePerformance; }; /* End XCSwiftPackageProductDependency section */ diff --git a/Diary/Diary.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Diary/Diary.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 91a89a13..396fa237 100644 --- a/Diary/Diary.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Diary/Diary.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -24,8 +24,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/firebase/firebase-ios-sdk", "state" : { - "revision" : "8328630971a8fdd8072b36bb22bef732eb15e1f0", - "version" : "11.4.0" + "revision" : "dbdfdc44bee8b8e4eaa5ec27eb12b9338f3f2bc1", + "version" : "11.5.0" } }, { @@ -69,8 +69,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/google/gtm-session-fetcher.git", "state" : { - "revision" : "5cfe5f090c982de9c58605d2a82a4fc77b774fbd", - "version" : "4.1.0" + "revision" : "a2ab612cb980066ee56d90d60d8462992c07f24b", + "version" : "3.5.0" } }, { diff --git a/Diary/Diary.xcodeproj/xcshareddata/xcschemes/CI.xcscheme b/Diary/Diary.xcodeproj/xcshareddata/xcschemes/DevRelease.xcscheme similarity index 92% rename from Diary/Diary.xcodeproj/xcshareddata/xcschemes/CI.xcscheme rename to Diary/Diary.xcodeproj/xcshareddata/xcschemes/DevRelease.xcscheme index 4fbddea6..acc102d5 100644 --- a/Diary/Diary.xcodeproj/xcshareddata/xcschemes/CI.xcscheme +++ b/Diary/Diary.xcodeproj/xcshareddata/xcschemes/DevRelease.xcscheme @@ -24,14 +24,14 @@ + buildConfiguration = "DevRelease"> diff --git a/Diary/Diary/AppDelegate.swift b/Diary/Diary/AppDelegate.swift index a4e1a554..dc07bb18 100644 --- a/Diary/Diary/AppDelegate.swift +++ b/Diary/Diary/AppDelegate.swift @@ -1,12 +1,45 @@ import SwiftUI -import FirebaseCore - +import Firebase +import UserNotifications class AppDelegate: NSObject, UIApplicationDelegate { - func application(_ application: UIApplication, - didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { - FirebaseApp.configure() + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { + FirebaseApp.configure() + + initRemoteNotifications(application: application) + initFirebaseMessaging() + + return true + } + + func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { + Messaging.messaging().apnsToken = deviceToken + } + + + func initRemoteNotifications(application: UIApplication) { + UNUserNotificationCenter.current().delegate = self + + let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound] + UNUserNotificationCenter.current().requestAuthorization( + options: authOptions, + completionHandler: { _, _ in } + ) + + application.registerForRemoteNotifications() + } + + func initFirebaseMessaging() { + Messaging.messaging().delegate = self + } +} + +extension AppDelegate: UNUserNotificationCenterDelegate { + +} - return true - } +extension AppDelegate: MessagingDelegate { + func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) { + print("Firebase registration token: \(String(describing: fcmToken))") + } } diff --git a/Diary/Diary/Diary.entitlements b/Diary/Diary/Diary.entitlements new file mode 100644 index 00000000..903def2a --- /dev/null +++ b/Diary/Diary/Diary.entitlements @@ -0,0 +1,8 @@ + + + + + aps-environment + development + + diff --git a/Diary/Diary/Info.plist b/Diary/Diary/Info.plist index ba6b757e..6d3967f7 100644 --- a/Diary/Diary/Info.plist +++ b/Diary/Diary/Info.plist @@ -9,5 +9,10 @@ NSAllowsArbitraryLoads + UIBackgroundModes + + fetch + remote-notification + diff --git a/Diary/ExportOptions.plist b/Diary/ExportOptions.plist index 7739f167..2d11afcd 100644 --- a/Diary/ExportOptions.plist +++ b/Diary/ExportOptions.plist @@ -6,20 +6,13 @@ export method release-testing - provisioningProfiles - - io.github.taetae98coding.diary - adhoc - - signingCertificate - Apple Distribution signingStyle - manual + automatic stripSwiftSymbols teamID 4TV6L66XZ8 thinning - <thin-for-all-variants> + <none> diff --git a/app/core/coroutines/src/commonMain/kotlin/io/github/taetae98coding/diary/core/coroutines/CoroutinesModule.kt b/app/core/coroutines/src/commonMain/kotlin/io/github/taetae98coding/diary/core/coroutines/CoroutinesModule.kt index f34ab89e..747086b1 100644 --- a/app/core/coroutines/src/commonMain/kotlin/io/github/taetae98coding/diary/core/coroutines/CoroutinesModule.kt +++ b/app/core/coroutines/src/commonMain/kotlin/io/github/taetae98coding/diary/core/coroutines/CoroutinesModule.kt @@ -1,6 +1,8 @@ package io.github.taetae98coding.diary.core.coroutines import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.lifecycleScope +import kotlinx.coroutines.CoroutineScope import org.koin.core.annotation.ComponentScan import org.koin.core.annotation.Module import org.koin.core.annotation.Singleton @@ -12,4 +14,9 @@ public class CoroutinesModule { internal fun providesAppLifecycleOwner(): LifecycleOwner { return getAppLifecycleOwner() } + + @Singleton + internal fun providesAppCoroutineScope(lifecycleOwner: LifecycleOwner): CoroutineScope { + return lifecycleOwner.lifecycleScope + } } diff --git a/app/core/diary-database-memory/src/commonMain/kotlin/io/github/taetae98coding/diary/core/diary/database/memory/MemoBackupMemoryDao.kt b/app/core/diary-database-memory/src/commonMain/kotlin/io/github/taetae98coding/diary/core/diary/database/memory/MemoBackupMemoryDao.kt index 2ec338c9..b9757a62 100644 --- a/app/core/diary-database-memory/src/commonMain/kotlin/io/github/taetae98coding/diary/core/diary/database/memory/MemoBackupMemoryDao.kt +++ b/app/core/diary-database-memory/src/commonMain/kotlin/io/github/taetae98coding/diary/core/diary/database/memory/MemoBackupMemoryDao.kt @@ -5,9 +5,7 @@ import io.github.taetae98coding.diary.core.model.memo.MemoDto import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.mapLatest -import kotlinx.coroutines.flow.update import org.koin.core.annotation.Singleton @OptIn(ExperimentalCoroutinesApi::class) @@ -16,7 +14,6 @@ internal class MemoBackupMemoryDao( private val memoMemoryDao: MemoMemoryDao, ) : MemoBackupDao { private val flow = MutableStateFlow>>(emptyMap()) - private val updateFlow = mutableMapOf>() override suspend fun upsert(uid: String, memoId: String) { val set = buildSet { @@ -28,20 +25,6 @@ internal class MemoBackupMemoryDao( put(uid, set) } - flow.emit(map) - getInternalUpdateFlow(uid).update { it + 1 } - } - - override suspend fun delete(uid: String, memoId: String) { - val set = buildSet { - flow.value[uid]?.let { addAll(it) } - remove(memoId) - } - val map = buildMap { - putAll(flow.value) - put(uid, set) - } - flow.emit(map) } @@ -59,10 +42,6 @@ internal class MemoBackupMemoryDao( flow.emit(map) } - override fun getUpdateFlow(uid: String): Flow { - return getInternalUpdateFlow(uid).asStateFlow() - } - override fun countByUid(uid: String): Flow { return flow.mapLatest { it[uid]?.size ?: 0 } } @@ -72,8 +51,4 @@ internal class MemoBackupMemoryDao( map.values.filter { it.owner == uid } } } - - private fun getInternalUpdateFlow(uid: String): MutableStateFlow { - return updateFlow.getOrPut(uid) { MutableStateFlow(0) } - } } diff --git a/app/core/diary-database-room/src/commonMain/kotlin/io/github/taetae98coding/diary/core/diary/database/room/dao/MemoBackupRoomDao.kt b/app/core/diary-database-room/src/commonMain/kotlin/io/github/taetae98coding/diary/core/diary/database/room/dao/MemoBackupRoomDao.kt index 6d198b24..a196431e 100644 --- a/app/core/diary-database-room/src/commonMain/kotlin/io/github/taetae98coding/diary/core/diary/database/room/dao/MemoBackupRoomDao.kt +++ b/app/core/diary-database-room/src/commonMain/kotlin/io/github/taetae98coding/diary/core/diary/database/room/dao/MemoBackupRoomDao.kt @@ -8,9 +8,6 @@ import io.github.taetae98coding.diary.core.diary.database.room.mapper.toDto import io.github.taetae98coding.diary.core.model.memo.MemoDto import io.github.taetae98coding.diary.library.coroutines.mapCollectionLatest import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.update import org.koin.core.annotation.Factory @Factory @@ -19,21 +16,12 @@ internal class MemoBackupRoomDao( ) : MemoBackupDao { override suspend fun upsert(uid: String, memoId: String) { database.memoBackup().upsert(MemoBackupEntity(uid, memoId)) - getInternalUpdateFlow(uid).update { it + 1 } - } - - override suspend fun delete(uid: String, memoId: String) { - database.memoBackup().delete(MemoBackupEntity(uid, memoId)) } override suspend fun deleteByMemoIds(memoIds: List) { database.memoBackup().deleteByMemoIds(memoIds) } - override fun getUpdateFlow(uid: String): Flow { - return getInternalUpdateFlow(uid).asStateFlow() - } - override fun countByUid(uid: String): Flow { return database.memoBackup().countByUid(uid) } @@ -42,12 +30,4 @@ internal class MemoBackupRoomDao( return database.memoBackup().findByUid(uid) .mapCollectionLatest(MemoEntity::toDto) } - - companion object { - private val updateFlow = mutableMapOf>() - - private fun getInternalUpdateFlow(uid: String): MutableStateFlow { - return updateFlow.getOrPut(uid) { MutableStateFlow(0) } - } - } } diff --git a/app/core/diary-database/src/commonMain/kotlin/io/github/taetae98coding/diary/core/diary/database/MemoBackupDao.kt b/app/core/diary-database/src/commonMain/kotlin/io/github/taetae98coding/diary/core/diary/database/MemoBackupDao.kt index 6d21188b..a5d3cd34 100644 --- a/app/core/diary-database/src/commonMain/kotlin/io/github/taetae98coding/diary/core/diary/database/MemoBackupDao.kt +++ b/app/core/diary-database/src/commonMain/kotlin/io/github/taetae98coding/diary/core/diary/database/MemoBackupDao.kt @@ -5,10 +5,8 @@ import kotlinx.coroutines.flow.Flow public interface MemoBackupDao { public suspend fun upsert(uid: String, memoId: String) - public suspend fun delete(uid: String, memoId: String) public suspend fun deleteByMemoIds(memoIds: List) - public fun getUpdateFlow(uid: String): Flow public fun countByUid(uid: String): Flow public fun findByUid(uid: String): Flow> } diff --git a/app/core/diary-service/src/commonMain/kotlin/io/github/taetae98coding/diary/core/diary/service/fcm/FCMService.kt b/app/core/diary-service/src/commonMain/kotlin/io/github/taetae98coding/diary/core/diary/service/fcm/FCMService.kt new file mode 100644 index 00000000..c7010ad9 --- /dev/null +++ b/app/core/diary-service/src/commonMain/kotlin/io/github/taetae98coding/diary/core/diary/service/fcm/FCMService.kt @@ -0,0 +1,33 @@ +package io.github.taetae98coding.diary.core.diary.service.fcm + +import io.github.taetae98coding.diary.common.model.request.fcm.DeleteFCMRequest +import io.github.taetae98coding.diary.common.model.request.fcm.UpsertFCMRequest +import io.github.taetae98coding.diary.core.diary.service.DiaryServiceModule +import io.github.taetae98coding.diary.core.diary.service.ext.getOrThrow +import io.ktor.client.HttpClient +import io.ktor.client.request.post +import io.ktor.client.request.setBody +import io.ktor.http.ContentType +import io.ktor.http.contentType +import org.koin.core.annotation.Factory +import org.koin.core.annotation.Named + +@Factory +public class FCMService internal constructor( + @Named(DiaryServiceModule.DIARY_CLIENT) + private val client: HttpClient, +) { + public suspend fun upsert(token: String) { + return client.post("/fcm/upsert") { + contentType(ContentType.Application.Json) + setBody(UpsertFCMRequest(token)) + }.getOrThrow() + } + + public suspend fun delete(token: String) { + return client.post("/fcm/delete") { + contentType(ContentType.Application.Json) + setBody(DeleteFCMRequest(token)) + }.getOrThrow() + } +} diff --git a/app/data/account/build.gradle.kts b/app/data/account/build.gradle.kts index 884c43b4..63a9b8d0 100644 --- a/app/data/account/build.gradle.kts +++ b/app/data/account/build.gradle.kts @@ -7,7 +7,6 @@ kotlin { commonMain { dependencies { implementation(project(":app:core:account-preferences")) - implementation(project(":app:core:diary-service")) implementation(project(":app:domain:account")) } } diff --git a/app/data/account/src/commonMain/kotlin/io/github/taetae98coding/diary/data/account/repository/AccountRepositoryImpl.kt b/app/data/account/src/commonMain/kotlin/io/github/taetae98coding/diary/data/account/repository/AccountRepositoryImpl.kt index 3f8f7ebe..4443515d 100644 --- a/app/data/account/src/commonMain/kotlin/io/github/taetae98coding/diary/data/account/repository/AccountRepositoryImpl.kt +++ b/app/data/account/src/commonMain/kotlin/io/github/taetae98coding/diary/data/account/repository/AccountRepositoryImpl.kt @@ -1,33 +1,14 @@ package io.github.taetae98coding.diary.data.account.repository import io.github.taetae98coding.diary.core.account.preferences.AccountPreferences -import io.github.taetae98coding.diary.core.diary.service.account.AccountService -import io.github.taetae98coding.diary.core.model.account.AccountToken import io.github.taetae98coding.diary.domain.account.repository.AccountRepository -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.flow -import kotlinx.coroutines.flow.mapLatest import org.koin.core.annotation.Factory -@OptIn(ExperimentalCoroutinesApi::class) @Factory internal class AccountRepositoryImpl( private val preferencesDataSource: AccountPreferences, - private val remoteDataSource: AccountService, ) : AccountRepository { - override suspend fun join(email: String, password: String) { - remoteDataSource.join(email, password) - } - - override suspend fun save(email: String, token: AccountToken) { - preferencesDataSource.save(email, token.uid, token.token) - } - - override suspend fun clear() { - preferencesDataSource.clear() - } - override fun getEmail(): Flow { return preferencesDataSource.getEmail() } @@ -35,14 +16,4 @@ internal class AccountRepositoryImpl( override fun getUid(): Flow { return preferencesDataSource.getUid() } - - override fun fetchToken(email: String, password: String): Flow { - return flow { emit(remoteDataSource.login(email, password)) } - .mapLatest { - AccountToken( - uid = it.uid, - token = it.token, - ) - } - } } diff --git a/app/data/backup/src/commonMain/kotlin/io/github/taetae98coding/diary/data/backup/repository/BackupRepositoryImpl.kt b/app/data/backup/src/commonMain/kotlin/io/github/taetae98coding/diary/data/backup/repository/BackupRepositoryImpl.kt index 1482317a..0e6cb91b 100644 --- a/app/data/backup/src/commonMain/kotlin/io/github/taetae98coding/diary/data/backup/repository/BackupRepositoryImpl.kt +++ b/app/data/backup/src/commonMain/kotlin/io/github/taetae98coding/diary/data/backup/repository/BackupRepositoryImpl.kt @@ -5,19 +5,15 @@ import io.github.taetae98coding.diary.core.diary.service.memo.MemoService import io.github.taetae98coding.diary.core.model.mapper.toMemo import io.github.taetae98coding.diary.core.model.memo.MemoDto import io.github.taetae98coding.diary.domain.backup.repository.BackupRepository -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.first -import kotlinx.coroutines.flow.mapLatest import org.koin.core.annotation.Factory -@OptIn(ExperimentalCoroutinesApi::class) @Factory internal class BackupRepositoryImpl( private val memoBackupDao: MemoBackupDao, private val memoService: MemoService, ) : BackupRepository { - override suspend fun backupMemo(uid: String) { + override suspend fun backup(uid: String) { while (memoBackupDao.countByUid(uid).first() > 0) { val memoList = memoBackupDao.findByUid(uid).first() .map(MemoDto::toMemo) @@ -27,12 +23,7 @@ internal class BackupRepositoryImpl( } } - override fun getUpdateFlow(uid: String): Flow { - return memoBackupDao.getUpdateFlow(uid) - .mapLatest { } - } - - override fun countBackupMemo(uid: String): Flow { - return memoBackupDao.countByUid(uid) + override suspend fun upsertMemoBackupQueue(uid: String, memoId: String) { + memoBackupDao.upsert(uid, memoId) } } diff --git a/app/data/credential/README.md b/app/data/credential/README.md new file mode 100644 index 00000000..3e84b63e --- /dev/null +++ b/app/data/credential/README.md @@ -0,0 +1,3 @@ +# :app:data:credential module +## Dependency graph +![Dependency graph](../../../docs/images/graphs/dep_graph_app_data_credential.svg) diff --git a/app/data/credential/build.gradle.kts b/app/data/credential/build.gradle.kts new file mode 100644 index 00000000..87aa7143 --- /dev/null +++ b/app/data/credential/build.gradle.kts @@ -0,0 +1,15 @@ +plugins { + id("diary.app.data") +} + +kotlin { + sourceSets { + commonMain { + dependencies { + implementation(project(":app:core:account-preferences")) + implementation(project(":app:core:diary-service")) + implementation(project(":app:domain:credential")) + } + } + } +} diff --git a/app/data/credential/src/commonMain/kotlin/io/github/taetae98coding/diary/data/credential/CredentialDataModule.kt b/app/data/credential/src/commonMain/kotlin/io/github/taetae98coding/diary/data/credential/CredentialDataModule.kt new file mode 100644 index 00000000..0eb57a39 --- /dev/null +++ b/app/data/credential/src/commonMain/kotlin/io/github/taetae98coding/diary/data/credential/CredentialDataModule.kt @@ -0,0 +1,8 @@ +package io.github.taetae98coding.diary.data.credential + +import org.koin.core.annotation.ComponentScan +import org.koin.core.annotation.Module + +@Module +@ComponentScan +public class CredentialDataModule diff --git a/app/data/credential/src/commonMain/kotlin/io/github/taetae98coding/diary/data/credential/repository/CredentialRepositoryImpl.kt b/app/data/credential/src/commonMain/kotlin/io/github/taetae98coding/diary/data/credential/repository/CredentialRepositoryImpl.kt new file mode 100644 index 00000000..05c6e764 --- /dev/null +++ b/app/data/credential/src/commonMain/kotlin/io/github/taetae98coding/diary/data/credential/repository/CredentialRepositoryImpl.kt @@ -0,0 +1,31 @@ +package io.github.taetae98coding.diary.data.credential.repository + +import io.github.taetae98coding.diary.core.account.preferences.AccountPreferences +import io.github.taetae98coding.diary.core.diary.service.account.AccountService +import io.github.taetae98coding.diary.core.model.account.AccountToken +import io.github.taetae98coding.diary.domain.credential.repository.CredentialRepository +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow +import org.koin.core.annotation.Factory + +@Factory +internal class CredentialRepositoryImpl( + private val preferencesDataSource: AccountPreferences, + private val remoteDataSource: AccountService, +) : CredentialRepository { + override suspend fun join(email: String, password: String) { + remoteDataSource.join(email, password) + } + + override suspend fun save(email: String, token: AccountToken) { + preferencesDataSource.save(email, token.uid, token.token) + } + + override suspend fun clear() { + preferencesDataSource.clear() + } + + override fun fetchToken(email: String, password: String): Flow { + return flow { emit(remoteDataSource.login(email, password)) } + } +} diff --git a/app/data/fcm/README.md b/app/data/fcm/README.md new file mode 100644 index 00000000..1fca72a8 --- /dev/null +++ b/app/data/fcm/README.md @@ -0,0 +1,3 @@ +# :app:data:fcm module +## Dependency graph +![Dependency graph](../../../docs/images/graphs/dep_graph_app_data_fcm.svg) diff --git a/app/data/fcm/build.gradle.kts b/app/data/fcm/build.gradle.kts new file mode 100644 index 00000000..4e67d865 --- /dev/null +++ b/app/data/fcm/build.gradle.kts @@ -0,0 +1,15 @@ +plugins { + id("diary.app.data") +} + +kotlin { + sourceSets { + commonMain { + dependencies { + implementation(project(":app:core:diary-service")) + implementation(project(":app:domain:fcm")) + implementation(project(":library:firebase-messaging")) + } + } + } +} diff --git a/app/data/fcm/src/commonMain/kotlin/io/github/taetae98coding/diary/data/fcm/FCMDataModule.kt b/app/data/fcm/src/commonMain/kotlin/io/github/taetae98coding/diary/data/fcm/FCMDataModule.kt new file mode 100644 index 00000000..e9adca4d --- /dev/null +++ b/app/data/fcm/src/commonMain/kotlin/io/github/taetae98coding/diary/data/fcm/FCMDataModule.kt @@ -0,0 +1,8 @@ +package io.github.taetae98coding.diary.data.fcm + +import org.koin.core.annotation.ComponentScan +import org.koin.core.annotation.Module + +@Module +@ComponentScan +public class FCMDataModule diff --git a/app/data/fcm/src/commonMain/kotlin/io/github/taetae98coding/diary/data/fcm/repository/FCMRepositoryImpl.kt b/app/data/fcm/src/commonMain/kotlin/io/github/taetae98coding/diary/data/fcm/repository/FCMRepositoryImpl.kt new file mode 100644 index 00000000..db123e5c --- /dev/null +++ b/app/data/fcm/src/commonMain/kotlin/io/github/taetae98coding/diary/data/fcm/repository/FCMRepositoryImpl.kt @@ -0,0 +1,20 @@ +package io.github.taetae98coding.diary.data.fcm.repository + +import io.github.taetae98coding.diary.core.diary.service.fcm.FCMService +import io.github.taetae98coding.diary.domain.fcm.repository.FCMRepository +import io.github.taetae98coding.diary.library.firebase.messaging.KFirebaseMessaging +import org.koin.core.annotation.Factory + +@Factory +internal class FCMRepositoryImpl( + private val messaging: KFirebaseMessaging, + private val remoteDataSource: FCMService, +) : FCMRepository { + override suspend fun upsert() { + remoteDataSource.upsert(messaging.getToken()) + } + + override suspend fun delete() { + remoteDataSource.delete(messaging.getToken()) + } +} diff --git a/app/data/memo/src/commonMain/kotlin/io/github/taetae98coding/diary/data/memo/repository/MemoRepositoryImpl.kt b/app/data/memo/src/commonMain/kotlin/io/github/taetae98coding/diary/data/memo/repository/MemoRepositoryImpl.kt index e72bf0fa..334c4141 100644 --- a/app/data/memo/src/commonMain/kotlin/io/github/taetae98coding/diary/data/memo/repository/MemoRepositoryImpl.kt +++ b/app/data/memo/src/commonMain/kotlin/io/github/taetae98coding/diary/data/memo/repository/MemoRepositoryImpl.kt @@ -20,7 +20,7 @@ internal class MemoRepositoryImpl( private val localDataSource: MemoDao, private val backupDataSource: MemoBackupDao, ) : MemoRepository { - override suspend fun upsert(uid: String?, memo: Memo) { + override suspend fun upsert(memo: Memo) { val dto = MemoDto( id = memo.id, detail = memo.detail, @@ -32,30 +32,18 @@ internal class MemoRepositoryImpl( ) localDataSource.upsert(dto) - if (!uid.isNullOrBlank()) { - backupDataSource.upsert(uid, memo.id) - } } - override suspend fun update(uid: String?, memoId: String, detail: MemoDetail) { + override suspend fun update(memoId: String, detail: MemoDetail) { localDataSource.update(memoId, detail) - if (!uid.isNullOrBlank()) { - backupDataSource.upsert(uid, memoId) - } } - override suspend fun updateFinish(uid: String?, memoId: String, isFinish: Boolean) { + override suspend fun updateFinish(memoId: String, isFinish: Boolean) { localDataSource.updateFinish(memoId, isFinish) - if (!uid.isNullOrBlank()) { - backupDataSource.upsert(uid, memoId) - } } - override suspend fun updateDelete(uid: String?, memoId: String, isDelete: Boolean) { + override suspend fun updateDelete(memoId: String, isDelete: Boolean) { localDataSource.updateDelete(memoId, isDelete) - if (!uid.isNullOrBlank()) { - backupDataSource.upsert(uid, memoId) - } } override fun find(memoId: String): Flow { diff --git a/app/domain/account/src/commonMain/kotlin/io/github/taetae98coding/diary/domain/account/repository/AccountRepository.kt b/app/domain/account/src/commonMain/kotlin/io/github/taetae98coding/diary/domain/account/repository/AccountRepository.kt index 63bb8ea0..d5b02b31 100644 --- a/app/domain/account/src/commonMain/kotlin/io/github/taetae98coding/diary/domain/account/repository/AccountRepository.kt +++ b/app/domain/account/src/commonMain/kotlin/io/github/taetae98coding/diary/domain/account/repository/AccountRepository.kt @@ -1,14 +1,8 @@ package io.github.taetae98coding.diary.domain.account.repository -import io.github.taetae98coding.diary.core.model.account.AccountToken import kotlinx.coroutines.flow.Flow public interface AccountRepository { - public suspend fun join(email: String, password: String) - public suspend fun save(email: String, token: AccountToken) - public suspend fun clear() - - public fun fetchToken(email: String, password: String): Flow public fun getEmail(): Flow public fun getUid(): Flow } diff --git a/app/domain/account/src/commonMain/kotlin/io/github/taetae98coding/diary/domain/account/usecase/LoginUseCase.kt b/app/domain/account/src/commonMain/kotlin/io/github/taetae98coding/diary/domain/account/usecase/LoginUseCase.kt deleted file mode 100644 index c7fd3ca7..00000000 --- a/app/domain/account/src/commonMain/kotlin/io/github/taetae98coding/diary/domain/account/usecase/LoginUseCase.kt +++ /dev/null @@ -1,18 +0,0 @@ -package io.github.taetae98coding.diary.domain.account.usecase - -import io.github.taetae98coding.diary.domain.account.repository.AccountRepository -import kotlinx.coroutines.flow.first -import org.koin.core.annotation.Factory - -@Factory -public class LoginUseCase internal constructor( - private val repository: AccountRepository, -) { - public suspend operator fun invoke(email: String, password: String): Result { - return runCatching { - val token = repository.fetchToken(email, password).first() - - repository.save(email = email, token = token) - } - } -} diff --git a/app/domain/account/src/commonMain/kotlin/io/github/taetae98coding/diary/domain/account/usecase/LogoutUseCase.kt b/app/domain/account/src/commonMain/kotlin/io/github/taetae98coding/diary/domain/account/usecase/LogoutUseCase.kt deleted file mode 100644 index 75fe3e98..00000000 --- a/app/domain/account/src/commonMain/kotlin/io/github/taetae98coding/diary/domain/account/usecase/LogoutUseCase.kt +++ /dev/null @@ -1,13 +0,0 @@ -package io.github.taetae98coding.diary.domain.account.usecase - -import io.github.taetae98coding.diary.domain.account.repository.AccountRepository -import org.koin.core.annotation.Factory - -@Factory -public class LogoutUseCase internal constructor( - private val repository: AccountRepository, -) { - public suspend operator fun invoke(): Result { - return runCatching { repository.clear() } - } -} diff --git a/app/domain/backup/src/commonMain/kotlin/io/github/taetae98coding/diary/domain/backup/repository/BackupRepository.kt b/app/domain/backup/src/commonMain/kotlin/io/github/taetae98coding/diary/domain/backup/repository/BackupRepository.kt index 38f5474a..1bdd1c52 100644 --- a/app/domain/backup/src/commonMain/kotlin/io/github/taetae98coding/diary/domain/backup/repository/BackupRepository.kt +++ b/app/domain/backup/src/commonMain/kotlin/io/github/taetae98coding/diary/domain/backup/repository/BackupRepository.kt @@ -1,10 +1,6 @@ package io.github.taetae98coding.diary.domain.backup.repository -import kotlinx.coroutines.flow.Flow - public interface BackupRepository { - public suspend fun backupMemo(uid: String) - - public fun getUpdateFlow(uid: String): Flow - public fun countBackupMemo(uid: String): Flow + public suspend fun backup(uid: String) + public suspend fun upsertMemoBackupQueue(uid: String, memoId: String) } diff --git a/app/domain/backup/src/commonMain/kotlin/io/github/taetae98coding/diary/domain/backup/usecase/BackupUseCase.kt b/app/domain/backup/src/commonMain/kotlin/io/github/taetae98coding/diary/domain/backup/usecase/BackupUseCase.kt index 792c6244..3b18bedb 100644 --- a/app/domain/backup/src/commonMain/kotlin/io/github/taetae98coding/diary/domain/backup/usecase/BackupUseCase.kt +++ b/app/domain/backup/src/commonMain/kotlin/io/github/taetae98coding/diary/domain/backup/usecase/BackupUseCase.kt @@ -3,14 +3,9 @@ package io.github.taetae98coding.diary.domain.backup.usecase import io.github.taetae98coding.diary.core.model.account.Account import io.github.taetae98coding.diary.domain.account.usecase.GetAccountUseCase import io.github.taetae98coding.diary.domain.backup.repository.BackupRepository -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.collectLatest -import kotlinx.coroutines.flow.emptyFlow -import kotlinx.coroutines.flow.flatMapLatest -import kotlinx.coroutines.flow.mapLatest +import kotlinx.coroutines.flow.first import org.koin.core.annotation.Factory -@OptIn(ExperimentalCoroutinesApi::class) @Factory public class BackupUseCase internal constructor( private val getAccountUseCase: GetAccountUseCase, @@ -18,22 +13,10 @@ public class BackupUseCase internal constructor( ) { public suspend operator fun invoke(): Result { return runCatching { - getAccountUseCase().mapLatest { it.getOrNull() } - .flatMapLatest { account -> - if (account is Account.Member) { - repository.getUpdateFlow(account.uid) - .mapLatest { account.uid } - } else { - emptyFlow() - } - } - .collectLatest { uid -> - runCatching { backup(uid) } - } + val account = getAccountUseCase().first().getOrThrow() + if (account is Account.Member) { + repository.backup(account.uid) + } } } - - private suspend fun backup(uid: String) { - repository.backupMemo(uid) - } } diff --git a/app/domain/credential/README.md b/app/domain/credential/README.md new file mode 100644 index 00000000..f1fdcfde --- /dev/null +++ b/app/domain/credential/README.md @@ -0,0 +1,3 @@ +# :app:domain:credential module +## Dependency graph +![Dependency graph](../../../docs/images/graphs/dep_graph_app_domain_credential.svg) diff --git a/app/domain/credential/build.gradle.kts b/app/domain/credential/build.gradle.kts new file mode 100644 index 00000000..075adf43 --- /dev/null +++ b/app/domain/credential/build.gradle.kts @@ -0,0 +1,15 @@ +plugins { + id("diary.app.domain") +} + +kotlin { + sourceSets { + commonMain { + dependencies { + implementation(project(":app:domain:fetch")) + implementation(project(":app:domain:backup")) + implementation(project(":app:domain:fcm")) + } + } + } +} diff --git a/app/domain/credential/src/commonMain/kotlin/io/github/taetae98coding/diary/domain/credential/CredentialDomainModule.kt b/app/domain/credential/src/commonMain/kotlin/io/github/taetae98coding/diary/domain/credential/CredentialDomainModule.kt new file mode 100644 index 00000000..76e4c4e7 --- /dev/null +++ b/app/domain/credential/src/commonMain/kotlin/io/github/taetae98coding/diary/domain/credential/CredentialDomainModule.kt @@ -0,0 +1,8 @@ +package io.github.taetae98coding.diary.domain.credential + +import org.koin.core.annotation.ComponentScan +import org.koin.core.annotation.Module + +@Module +@ComponentScan +public class CredentialDomainModule diff --git a/app/domain/credential/src/commonMain/kotlin/io/github/taetae98coding/diary/domain/credential/repository/CredentialRepository.kt b/app/domain/credential/src/commonMain/kotlin/io/github/taetae98coding/diary/domain/credential/repository/CredentialRepository.kt new file mode 100644 index 00000000..8bfddcbe --- /dev/null +++ b/app/domain/credential/src/commonMain/kotlin/io/github/taetae98coding/diary/domain/credential/repository/CredentialRepository.kt @@ -0,0 +1,12 @@ +package io.github.taetae98coding.diary.domain.credential.repository + +import io.github.taetae98coding.diary.core.model.account.AccountToken +import kotlinx.coroutines.flow.Flow + +public interface CredentialRepository { + public suspend fun join(email: String, password: String) + public suspend fun save(email: String, token: AccountToken) + public suspend fun clear() + + public fun fetchToken(email: String, password: String): Flow +} diff --git a/app/domain/account/src/commonMain/kotlin/io/github/taetae98coding/diary/domain/account/usecase/JoinUseCase.kt b/app/domain/credential/src/commonMain/kotlin/io/github/taetae98coding/diary/domain/credential/usecase/JoinUseCase.kt similarity index 71% rename from app/domain/account/src/commonMain/kotlin/io/github/taetae98coding/diary/domain/account/usecase/JoinUseCase.kt rename to app/domain/credential/src/commonMain/kotlin/io/github/taetae98coding/diary/domain/credential/usecase/JoinUseCase.kt index d8e2acc2..2fa964aa 100644 --- a/app/domain/account/src/commonMain/kotlin/io/github/taetae98coding/diary/domain/account/usecase/JoinUseCase.kt +++ b/app/domain/credential/src/commonMain/kotlin/io/github/taetae98coding/diary/domain/credential/usecase/JoinUseCase.kt @@ -1,13 +1,13 @@ -package io.github.taetae98coding.diary.domain.account.usecase +package io.github.taetae98coding.diary.domain.credential.usecase import io.github.taetae98coding.diary.common.exception.account.InvalidEmailException -import io.github.taetae98coding.diary.domain.account.repository.AccountRepository +import io.github.taetae98coding.diary.domain.credential.repository.CredentialRepository import io.github.taetae98coding.diary.library.kotlin.regex.email import org.koin.core.annotation.Factory @Factory public class JoinUseCase internal constructor( - private val repository: AccountRepository, + private val repository: CredentialRepository, ) { public suspend operator fun invoke(email: String, password: String): Result { return runCatching { diff --git a/app/domain/credential/src/commonMain/kotlin/io/github/taetae98coding/diary/domain/credential/usecase/LoginUseCase.kt b/app/domain/credential/src/commonMain/kotlin/io/github/taetae98coding/diary/domain/credential/usecase/LoginUseCase.kt new file mode 100644 index 00000000..a09a93f9 --- /dev/null +++ b/app/domain/credential/src/commonMain/kotlin/io/github/taetae98coding/diary/domain/credential/usecase/LoginUseCase.kt @@ -0,0 +1,33 @@ +package io.github.taetae98coding.diary.domain.credential.usecase + +import io.github.taetae98coding.diary.domain.credential.repository.CredentialRepository +import io.github.taetae98coding.diary.domain.fcm.usecase.UpdateFCMTokenUseCase +import io.github.taetae98coding.diary.domain.fetch.usecase.FetchUseCase +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.launch +import org.koin.core.annotation.Factory + +@Factory +public class LoginUseCase internal constructor( + private val coroutineScope: CoroutineScope, + private val repository: CredentialRepository, + private val fetchUseCase: FetchUseCase, + private val updateFCMTokenUseCase: UpdateFCMTokenUseCase, +) { + public suspend operator fun invoke(email: String, password: String): Result { + return runCatching { + val token = repository.fetchToken(email, password).first() + repository.save(email = email, token = token) + + coroutineScope.launch { + listOf( + async { fetchUseCase() }, + async { updateFCMTokenUseCase() } + ).awaitAll() + } + } + } +} diff --git a/app/domain/credential/src/commonMain/kotlin/io/github/taetae98coding/diary/domain/credential/usecase/LogoutUseCase.kt b/app/domain/credential/src/commonMain/kotlin/io/github/taetae98coding/diary/domain/credential/usecase/LogoutUseCase.kt new file mode 100644 index 00000000..5ab2f81e --- /dev/null +++ b/app/domain/credential/src/commonMain/kotlin/io/github/taetae98coding/diary/domain/credential/usecase/LogoutUseCase.kt @@ -0,0 +1,21 @@ +package io.github.taetae98coding.diary.domain.credential.usecase + +import io.github.taetae98coding.diary.domain.backup.usecase.BackupUseCase +import io.github.taetae98coding.diary.domain.credential.repository.CredentialRepository +import io.github.taetae98coding.diary.domain.fcm.usecase.UpdateFCMTokenUseCase +import org.koin.core.annotation.Factory + +@Factory +public class LogoutUseCase internal constructor( + private val repository: CredentialRepository, + private val backupUseCase: BackupUseCase, + private val updateFCMTokenUseCase: UpdateFCMTokenUseCase, +) { + public suspend operator fun invoke(): Result { + return runCatching { + backupUseCase() + repository.clear() + updateFCMTokenUseCase() + } + } +} diff --git a/app/domain/fcm/README.md b/app/domain/fcm/README.md new file mode 100644 index 00000000..6649fbf7 --- /dev/null +++ b/app/domain/fcm/README.md @@ -0,0 +1,3 @@ +# :app:domain:fcm module +## Dependency graph +![Dependency graph](../../../docs/images/graphs/dep_graph_app_domain_fcm.svg) diff --git a/app/domain/fcm/build.gradle.kts b/app/domain/fcm/build.gradle.kts new file mode 100644 index 00000000..84e3b1c2 --- /dev/null +++ b/app/domain/fcm/build.gradle.kts @@ -0,0 +1,13 @@ +plugins { + id("diary.app.domain") +} + +kotlin { + sourceSets { + commonMain { + dependencies { + implementation(project(":app:domain:account")) + } + } + } +} diff --git a/app/domain/fcm/src/commonMain/kotlin/io/github/taetae98coding/diary/domain/fcm/FCMDomainModule.kt b/app/domain/fcm/src/commonMain/kotlin/io/github/taetae98coding/diary/domain/fcm/FCMDomainModule.kt new file mode 100644 index 00000000..a2d83583 --- /dev/null +++ b/app/domain/fcm/src/commonMain/kotlin/io/github/taetae98coding/diary/domain/fcm/FCMDomainModule.kt @@ -0,0 +1,8 @@ +package io.github.taetae98coding.diary.domain.fcm + +import org.koin.core.annotation.ComponentScan +import org.koin.core.annotation.Module + +@Module +@ComponentScan +public class FCMDomainModule diff --git a/app/domain/fcm/src/commonMain/kotlin/io/github/taetae98coding/diary/domain/fcm/repository/FCMRepository.kt b/app/domain/fcm/src/commonMain/kotlin/io/github/taetae98coding/diary/domain/fcm/repository/FCMRepository.kt new file mode 100644 index 00000000..76699848 --- /dev/null +++ b/app/domain/fcm/src/commonMain/kotlin/io/github/taetae98coding/diary/domain/fcm/repository/FCMRepository.kt @@ -0,0 +1,6 @@ +package io.github.taetae98coding.diary.domain.fcm.repository + +public interface FCMRepository { + public suspend fun upsert() + public suspend fun delete() +} diff --git a/app/domain/fcm/src/commonMain/kotlin/io/github/taetae98coding/diary/domain/fcm/usecase/UpdateFCMTokenUseCase.kt b/app/domain/fcm/src/commonMain/kotlin/io/github/taetae98coding/diary/domain/fcm/usecase/UpdateFCMTokenUseCase.kt new file mode 100644 index 00000000..ddb14d82 --- /dev/null +++ b/app/domain/fcm/src/commonMain/kotlin/io/github/taetae98coding/diary/domain/fcm/usecase/UpdateFCMTokenUseCase.kt @@ -0,0 +1,24 @@ +package io.github.taetae98coding.diary.domain.fcm.usecase + +import io.github.taetae98coding.diary.core.model.account.Account +import io.github.taetae98coding.diary.domain.account.usecase.GetAccountUseCase +import io.github.taetae98coding.diary.domain.fcm.repository.FCMRepository +import kotlinx.coroutines.flow.first +import org.koin.core.annotation.Factory + +@Factory +public class UpdateFCMTokenUseCase internal constructor( + private val getAccountUseCase: GetAccountUseCase, + private val repository: FCMRepository, +) { + public suspend operator fun invoke(): Result { + return runCatching { + val account = getAccountUseCase().first().getOrThrow() + if (account is Account.Member) { + repository.upsert() + } else { + repository.delete() + } + } + } +} diff --git a/app/domain/fetch/src/commonMain/kotlin/io/github/taetae98coding/diary/domain/fetch/usecase/FetchUseCase.kt b/app/domain/fetch/src/commonMain/kotlin/io/github/taetae98coding/diary/domain/fetch/usecase/FetchUseCase.kt index 47e67611..55acb067 100644 --- a/app/domain/fetch/src/commonMain/kotlin/io/github/taetae98coding/diary/domain/fetch/usecase/FetchUseCase.kt +++ b/app/domain/fetch/src/commonMain/kotlin/io/github/taetae98coding/diary/domain/fetch/usecase/FetchUseCase.kt @@ -3,12 +3,9 @@ package io.github.taetae98coding.diary.domain.fetch.usecase import io.github.taetae98coding.diary.core.model.account.Account import io.github.taetae98coding.diary.domain.account.usecase.GetAccountUseCase import io.github.taetae98coding.diary.domain.fetch.repository.FetchRepository -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.collectLatest -import kotlinx.coroutines.flow.mapLatest +import kotlinx.coroutines.flow.first import org.koin.core.annotation.Factory -@OptIn(ExperimentalCoroutinesApi::class) @Factory public class FetchUseCase internal constructor( private val getAccountUseCase: GetAccountUseCase, @@ -16,16 +13,10 @@ public class FetchUseCase internal constructor( ) { public suspend operator fun invoke(): Result { return runCatching { - getAccountUseCase().mapLatest { it.getOrNull() } - .collectLatest { account -> - if (account is Account.Member) { - runCatching { fetch(account.uid) } - } - } + val account = getAccountUseCase().first().getOrThrow() + if (account is Account.Member) { + repository.fetchMemo(account.uid) + } } } - - private suspend fun fetch(uid: String) { - repository.fetchMemo(uid) - } } diff --git a/app/domain/memo/build.gradle.kts b/app/domain/memo/build.gradle.kts index 84e3b1c2..7385bf5b 100644 --- a/app/domain/memo/build.gradle.kts +++ b/app/domain/memo/build.gradle.kts @@ -7,6 +7,7 @@ kotlin { commonMain { dependencies { implementation(project(":app:domain:account")) + implementation(project(":app:domain:backup")) } } } diff --git a/app/domain/memo/src/commonMain/kotlin/io/github/taetae98coding/diary/domain/memo/repository/MemoRepository.kt b/app/domain/memo/src/commonMain/kotlin/io/github/taetae98coding/diary/domain/memo/repository/MemoRepository.kt index 3a33d8bc..240a8b0b 100644 --- a/app/domain/memo/src/commonMain/kotlin/io/github/taetae98coding/diary/domain/memo/repository/MemoRepository.kt +++ b/app/domain/memo/src/commonMain/kotlin/io/github/taetae98coding/diary/domain/memo/repository/MemoRepository.kt @@ -6,10 +6,10 @@ import kotlinx.coroutines.flow.Flow import kotlinx.datetime.LocalDate public interface MemoRepository { - public suspend fun upsert(uid: String?, memo: Memo) - public suspend fun update(uid: String?, memoId: String, detail: MemoDetail) - public suspend fun updateFinish(uid: String?, memoId: String, isFinish: Boolean) - public suspend fun updateDelete(uid: String?, memoId: String, isDelete: Boolean) + public suspend fun upsert(memo: Memo) + public suspend fun update(memoId: String, detail: MemoDetail) + public suspend fun updateFinish(memoId: String, isFinish: Boolean) + public suspend fun updateDelete(memoId: String, isDelete: Boolean) public fun find(memoId: String): Flow public fun findByDateRange(owner: String?, dateRange: ClosedRange): Flow> diff --git a/app/domain/memo/src/commonMain/kotlin/io/github/taetae98coding/diary/domain/memo/usecase/AddMemoUseCase.kt b/app/domain/memo/src/commonMain/kotlin/io/github/taetae98coding/diary/domain/memo/usecase/AddMemoUseCase.kt index 7ae5cee3..8917b46b 100644 --- a/app/domain/memo/src/commonMain/kotlin/io/github/taetae98coding/diary/domain/memo/usecase/AddMemoUseCase.kt +++ b/app/domain/memo/src/commonMain/kotlin/io/github/taetae98coding/diary/domain/memo/usecase/AddMemoUseCase.kt @@ -1,13 +1,18 @@ package io.github.taetae98coding.diary.domain.memo.usecase import io.github.taetae98coding.diary.common.exception.memo.MemoTitleBlankException +import io.github.taetae98coding.diary.core.model.account.Account import io.github.taetae98coding.diary.core.model.memo.Memo import io.github.taetae98coding.diary.core.model.memo.MemoDetail import io.github.taetae98coding.diary.domain.account.usecase.GetAccountUseCase +import io.github.taetae98coding.diary.domain.backup.repository.BackupRepository +import io.github.taetae98coding.diary.domain.backup.usecase.BackupUseCase import io.github.taetae98coding.diary.domain.memo.repository.MemoRepository import kotlin.uuid.ExperimentalUuidApi import kotlin.uuid.Uuid +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.first +import kotlinx.coroutines.launch import kotlinx.datetime.Clock import org.koin.core.annotation.Factory @@ -17,6 +22,9 @@ public class AddMemoUseCase internal constructor( private val clock: Clock, private val getAccountUseCase: GetAccountUseCase, private val repository: MemoRepository, + private val coroutineScope: CoroutineScope, + private val backupRepository: BackupRepository, + private val backupUseCase: BackupUseCase, ) { public suspend operator fun invoke(detail: MemoDetail): Result { return runCatching { @@ -34,7 +42,14 @@ public class AddMemoUseCase internal constructor( updateAt = clock.now(), ) - repository.upsert(account.uid, memo) + repository.upsert(memo) + + if (account is Account.Member) { + coroutineScope.launch { + backupRepository.upsertMemoBackupQueue(account.uid, id) + backupUseCase() + } + } } } } diff --git a/app/domain/memo/src/commonMain/kotlin/io/github/taetae98coding/diary/domain/memo/usecase/DeleteMemoUseCase.kt b/app/domain/memo/src/commonMain/kotlin/io/github/taetae98coding/diary/domain/memo/usecase/DeleteMemoUseCase.kt index f223acc8..3b8cd293 100644 --- a/app/domain/memo/src/commonMain/kotlin/io/github/taetae98coding/diary/domain/memo/usecase/DeleteMemoUseCase.kt +++ b/app/domain/memo/src/commonMain/kotlin/io/github/taetae98coding/diary/domain/memo/usecase/DeleteMemoUseCase.kt @@ -1,21 +1,35 @@ package io.github.taetae98coding.diary.domain.memo.usecase +import io.github.taetae98coding.diary.core.model.account.Account import io.github.taetae98coding.diary.domain.account.usecase.GetAccountUseCase +import io.github.taetae98coding.diary.domain.backup.repository.BackupRepository +import io.github.taetae98coding.diary.domain.backup.usecase.BackupUseCase import io.github.taetae98coding.diary.domain.memo.repository.MemoRepository +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.first +import kotlinx.coroutines.launch import org.koin.core.annotation.Factory @Factory public class DeleteMemoUseCase internal constructor( private val getAccountUseCase: GetAccountUseCase, private val repository: MemoRepository, + private val coroutineScope: CoroutineScope, + private val backupRepository: BackupRepository, + private val backupUseCase: BackupUseCase, ) { public suspend operator fun invoke(memoId: String?): Result { return runCatching { if (memoId.isNullOrBlank()) return@runCatching val account = getAccountUseCase().first().getOrThrow() - repository.updateDelete(account.uid, memoId, true) + repository.updateDelete(memoId, true) + if (account is Account.Member) { + coroutineScope.launch { + backupRepository.upsertMemoBackupQueue(account.uid, memoId) + backupUseCase() + } + } } } } diff --git a/app/domain/memo/src/commonMain/kotlin/io/github/taetae98coding/diary/domain/memo/usecase/FinishMemoUseCase.kt b/app/domain/memo/src/commonMain/kotlin/io/github/taetae98coding/diary/domain/memo/usecase/FinishMemoUseCase.kt index f50e2376..dcd3d87b 100644 --- a/app/domain/memo/src/commonMain/kotlin/io/github/taetae98coding/diary/domain/memo/usecase/FinishMemoUseCase.kt +++ b/app/domain/memo/src/commonMain/kotlin/io/github/taetae98coding/diary/domain/memo/usecase/FinishMemoUseCase.kt @@ -1,21 +1,35 @@ package io.github.taetae98coding.diary.domain.memo.usecase +import io.github.taetae98coding.diary.core.model.account.Account import io.github.taetae98coding.diary.domain.account.usecase.GetAccountUseCase +import io.github.taetae98coding.diary.domain.backup.repository.BackupRepository +import io.github.taetae98coding.diary.domain.backup.usecase.BackupUseCase import io.github.taetae98coding.diary.domain.memo.repository.MemoRepository +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.first +import kotlinx.coroutines.launch import org.koin.core.annotation.Factory @Factory public class FinishMemoUseCase internal constructor( private val getAccountUseCase: GetAccountUseCase, private val repository: MemoRepository, + private val coroutineScope: CoroutineScope, + private val backupRepository: BackupRepository, + private val backupUseCase: BackupUseCase, ) { public suspend operator fun invoke(memoId: String?): Result { return runCatching { if (memoId.isNullOrBlank()) return@runCatching val account = getAccountUseCase().first().getOrThrow() - repository.updateFinish(account.uid, memoId, true) + repository.updateFinish(memoId, true) + if (account is Account.Member) { + coroutineScope.launch { + backupRepository.upsertMemoBackupQueue(account.uid, memoId) + backupUseCase() + } + } } } } diff --git a/app/domain/memo/src/commonMain/kotlin/io/github/taetae98coding/diary/domain/memo/usecase/RestartMemoUseCase.kt b/app/domain/memo/src/commonMain/kotlin/io/github/taetae98coding/diary/domain/memo/usecase/RestartMemoUseCase.kt index 66e7abdf..4bcdca0c 100644 --- a/app/domain/memo/src/commonMain/kotlin/io/github/taetae98coding/diary/domain/memo/usecase/RestartMemoUseCase.kt +++ b/app/domain/memo/src/commonMain/kotlin/io/github/taetae98coding/diary/domain/memo/usecase/RestartMemoUseCase.kt @@ -1,21 +1,36 @@ package io.github.taetae98coding.diary.domain.memo.usecase +import io.github.taetae98coding.diary.core.model.account.Account import io.github.taetae98coding.diary.domain.account.usecase.GetAccountUseCase +import io.github.taetae98coding.diary.domain.backup.repository.BackupRepository +import io.github.taetae98coding.diary.domain.backup.usecase.BackupUseCase import io.github.taetae98coding.diary.domain.memo.repository.MemoRepository +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.first +import kotlinx.coroutines.launch import org.koin.core.annotation.Factory @Factory public class RestartMemoUseCase internal constructor( private val getAccountUseCase: GetAccountUseCase, private val repository: MemoRepository, + private val coroutineScope: CoroutineScope, + private val backupRepository: BackupRepository, + private val backupUseCase: BackupUseCase, ) { public suspend operator fun invoke(memoId: String?): Result { return runCatching { if (memoId.isNullOrBlank()) return@runCatching val account = getAccountUseCase().first().getOrThrow() - repository.updateFinish(account.uid, memoId, false) + + repository.updateFinish(memoId, false) + if (account is Account.Member) { + coroutineScope.launch { + backupRepository.upsertMemoBackupQueue(account.uid, memoId) + backupUseCase() + } + } } } } diff --git a/app/domain/memo/src/commonMain/kotlin/io/github/taetae98coding/diary/domain/memo/usecase/UpdateMemoUseCase.kt b/app/domain/memo/src/commonMain/kotlin/io/github/taetae98coding/diary/domain/memo/usecase/UpdateMemoUseCase.kt index 81fa18bd..de589368 100644 --- a/app/domain/memo/src/commonMain/kotlin/io/github/taetae98coding/diary/domain/memo/usecase/UpdateMemoUseCase.kt +++ b/app/domain/memo/src/commonMain/kotlin/io/github/taetae98coding/diary/domain/memo/usecase/UpdateMemoUseCase.kt @@ -1,15 +1,23 @@ package io.github.taetae98coding.diary.domain.memo.usecase +import io.github.taetae98coding.diary.core.model.account.Account import io.github.taetae98coding.diary.core.model.memo.MemoDetail import io.github.taetae98coding.diary.domain.account.usecase.GetAccountUseCase +import io.github.taetae98coding.diary.domain.backup.repository.BackupRepository +import io.github.taetae98coding.diary.domain.backup.usecase.BackupUseCase import io.github.taetae98coding.diary.domain.memo.repository.MemoRepository +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.first +import kotlinx.coroutines.launch import org.koin.core.annotation.Factory @Factory public class UpdateMemoUseCase internal constructor( private val getAccountUseCase: GetAccountUseCase, private val repository: MemoRepository, + private val coroutineScope: CoroutineScope, + private val backupRepository: BackupRepository, + private val backupUseCase: BackupUseCase, ) { public suspend operator fun invoke(memoId: String?, detail: MemoDetail): Result { return runCatching { @@ -19,8 +27,14 @@ public class UpdateMemoUseCase internal constructor( val account = getAccountUseCase().first().getOrThrow() val validDetail = detail.copy(title = detail.title.ifBlank { memo.detail.title }) - if (memo.detail != validDetail) { - repository.update(account.uid, memoId, validDetail) + if (memo.detail == validDetail) return@runCatching + + repository.update(memoId, validDetail) + if (account is Account.Member) { + coroutineScope.launch { + backupRepository.upsertMemoBackupQueue(account.uid, memoId) + backupUseCase() + } } } } diff --git a/app/feature/account/build.gradle.kts b/app/feature/account/build.gradle.kts index 57f5f7c9..debd7606 100644 --- a/app/feature/account/build.gradle.kts +++ b/app/feature/account/build.gradle.kts @@ -7,6 +7,7 @@ kotlin { commonMain { dependencies { implementation(project(":app:domain:account")) + implementation(project(":app:domain:credential")) } } } diff --git a/app/feature/account/src/commonMain/kotlin/io/github/taetae98coding/diary/feature/account/join/JoinViewModel.kt b/app/feature/account/src/commonMain/kotlin/io/github/taetae98coding/diary/feature/account/join/JoinViewModel.kt index 202de718..fd13bd4d 100644 --- a/app/feature/account/src/commonMain/kotlin/io/github/taetae98coding/diary/feature/account/join/JoinViewModel.kt +++ b/app/feature/account/src/commonMain/kotlin/io/github/taetae98coding/diary/feature/account/join/JoinViewModel.kt @@ -4,8 +4,8 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import io.github.taetae98coding.diary.common.exception.NetworkException import io.github.taetae98coding.diary.common.exception.account.ExistEmailException -import io.github.taetae98coding.diary.domain.account.usecase.JoinUseCase -import io.github.taetae98coding.diary.domain.account.usecase.LoginUseCase +import io.github.taetae98coding.diary.domain.credential.usecase.JoinUseCase +import io.github.taetae98coding.diary.domain.credential.usecase.LoginUseCase import io.github.taetae98coding.diary.feature.account.join.state.JoinUiState import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow diff --git a/app/feature/account/src/commonMain/kotlin/io/github/taetae98coding/diary/feature/account/login/LoginViewModel.kt b/app/feature/account/src/commonMain/kotlin/io/github/taetae98coding/diary/feature/account/login/LoginViewModel.kt index 6bbbc335..47062836 100644 --- a/app/feature/account/src/commonMain/kotlin/io/github/taetae98coding/diary/feature/account/login/LoginViewModel.kt +++ b/app/feature/account/src/commonMain/kotlin/io/github/taetae98coding/diary/feature/account/login/LoginViewModel.kt @@ -4,7 +4,7 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import io.github.taetae98coding.diary.common.exception.NetworkException import io.github.taetae98coding.diary.common.exception.account.AccountNotFoundException -import io.github.taetae98coding.diary.domain.account.usecase.LoginUseCase +import io.github.taetae98coding.diary.domain.credential.usecase.LoginUseCase import io.github.taetae98coding.diary.feature.account.login.state.LoginUiState import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow diff --git a/app/feature/more/build.gradle.kts b/app/feature/more/build.gradle.kts index 278a55e8..098c2778 100644 --- a/app/feature/more/build.gradle.kts +++ b/app/feature/more/build.gradle.kts @@ -7,6 +7,7 @@ kotlin { commonMain { dependencies { implementation(project(":app:domain:account")) + implementation(project(":app:domain:credential")) } } } diff --git a/app/feature/more/src/commonMain/kotlin/io/github/taetae98coding/diary/feature/more/viewmodel/MoreAccountViewModel.kt b/app/feature/more/src/commonMain/kotlin/io/github/taetae98coding/diary/feature/more/viewmodel/MoreAccountViewModel.kt index a77ccade..c6647e30 100644 --- a/app/feature/more/src/commonMain/kotlin/io/github/taetae98coding/diary/feature/more/viewmodel/MoreAccountViewModel.kt +++ b/app/feature/more/src/commonMain/kotlin/io/github/taetae98coding/diary/feature/more/viewmodel/MoreAccountViewModel.kt @@ -4,7 +4,7 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import io.github.taetae98coding.diary.core.model.account.Account import io.github.taetae98coding.diary.domain.account.usecase.GetAccountUseCase -import io.github.taetae98coding.diary.domain.account.usecase.LogoutUseCase +import io.github.taetae98coding.diary.domain.credential.usecase.LogoutUseCase import io.github.taetae98coding.diary.feature.more.account.state.MoreAccountUiState import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow diff --git a/app/platform/android/build.gradle.kts b/app/platform/android/build.gradle.kts index 3f212970..0b164010 100644 --- a/app/platform/android/build.gradle.kts +++ b/app/platform/android/build.gradle.kts @@ -10,6 +10,7 @@ plugins { alias(libs.plugins.android.firebase.perf) alias(libs.plugins.google.services) alias(libs.plugins.dependency.guard) + alias(libs.plugins.ksp) } android { @@ -34,8 +35,8 @@ android { defaultConfig { applicationId = "io.github.taetae98coding.diary" - versionCode = 1 - versionName = "1.0.0" + versionCode = 2 + versionName = "1.1.0" } buildTypes { @@ -92,6 +93,7 @@ dependencies { implementation(project(":app:core:holiday-preferences-datastore")) implementation(project(":app:core:holiday-database-room")) implementation(project(":app:core:holiday-service")) + implementation(project(":app:domain:fcm")) implementation(libs.android.material) implementation(libs.androidx.activity.compose) @@ -101,9 +103,14 @@ dependencies { implementation(libs.android.firebase.analytics) implementation(libs.android.firebase.crashlytics) implementation(libs.android.firebase.perf) + implementation(libs.android.firebase.messaging) implementation(platform(libs.koin.bom)) implementation(libs.koin.android) + implementation(platform(libs.koin.annotations.bom)) + implementation(libs.koin.annotations) + ksp(platform(libs.koin.annotations.bom)) + ksp(libs.koin.compiler) runtimeOnly(libs.ktor.client.okhttp) diff --git a/app/platform/android/dependencies/realReleaseRuntimeClasspath.txt b/app/platform/android/dependencies/realReleaseRuntimeClasspath.txt index 47092839..2463ee65 100644 --- a/app/platform/android/dependencies/realReleaseRuntimeClasspath.txt +++ b/app/platform/android/dependencies/realReleaseRuntimeClasspath.txt @@ -10,51 +10,51 @@ androidx.arch.core:core-common:2.2.0 androidx.arch.core:core-runtime:2.2.0 androidx.autofill:autofill:1.0.0 androidx.cardview:cardview:1.0.0 -androidx.collection:collection-jvm:1.4.2 -androidx.collection:collection-ktx:1.4.2 -androidx.collection:collection:1.4.2 -androidx.compose.animation:animation-android:1.7.1 -androidx.compose.animation:animation-core-android:1.7.1 -androidx.compose.animation:animation-core:1.7.1 -androidx.compose.animation:animation:1.7.1 -androidx.compose.foundation:foundation-android:1.7.1 -androidx.compose.foundation:foundation-layout-android:1.7.1 -androidx.compose.foundation:foundation-layout:1.7.1 -androidx.compose.foundation:foundation:1.7.1 +androidx.collection:collection-jvm:1.4.4 +androidx.collection:collection-ktx:1.4.4 +androidx.collection:collection:1.4.4 +androidx.compose.animation:animation-android:1.7.5 +androidx.compose.animation:animation-core-android:1.7.5 +androidx.compose.animation:animation-core:1.7.5 +androidx.compose.animation:animation:1.7.5 +androidx.compose.foundation:foundation-android:1.7.5 +androidx.compose.foundation:foundation-layout-android:1.7.5 +androidx.compose.foundation:foundation-layout:1.7.5 +androidx.compose.foundation:foundation:1.7.5 androidx.compose.material3.adaptive:adaptive-android:1.0.0 androidx.compose.material3.adaptive:adaptive-layout-android:1.0.0 androidx.compose.material3.adaptive:adaptive-layout:1.0.0 androidx.compose.material3.adaptive:adaptive-navigation-android:1.0.0 androidx.compose.material3.adaptive:adaptive-navigation:1.0.0 androidx.compose.material3.adaptive:adaptive:1.0.0 -androidx.compose.material3:material3-adaptive-navigation-suite-android:1.3.0 -androidx.compose.material3:material3-adaptive-navigation-suite:1.3.0 -androidx.compose.material3:material3-android:1.3.0 -androidx.compose.material3:material3:1.3.0 -androidx.compose.material:material-icons-core-android:1.7.1 -androidx.compose.material:material-icons-core:1.7.1 -androidx.compose.material:material-icons-extended-android:1.7.1 -androidx.compose.material:material-icons-extended:1.7.1 -androidx.compose.material:material-ripple-android:1.7.1 -androidx.compose.material:material-ripple:1.7.1 -androidx.compose.runtime:runtime-android:1.7.1 -androidx.compose.runtime:runtime-saveable-android:1.7.1 -androidx.compose.runtime:runtime-saveable:1.7.1 -androidx.compose.runtime:runtime:1.7.1 -androidx.compose.ui:ui-android:1.7.1 -androidx.compose.ui:ui-geometry-android:1.7.1 -androidx.compose.ui:ui-geometry:1.7.1 -androidx.compose.ui:ui-graphics-android:1.7.1 -androidx.compose.ui:ui-graphics:1.7.1 -androidx.compose.ui:ui-text-android:1.7.1 -androidx.compose.ui:ui-text:1.7.1 -androidx.compose.ui:ui-tooling-preview-android:1.7.1 -androidx.compose.ui:ui-tooling-preview:1.7.1 -androidx.compose.ui:ui-unit-android:1.7.1 -androidx.compose.ui:ui-unit:1.7.1 -androidx.compose.ui:ui-util-android:1.7.1 -androidx.compose.ui:ui-util:1.7.1 -androidx.compose.ui:ui:1.7.1 +androidx.compose.material3:material3-adaptive-navigation-suite-android:1.3.1 +androidx.compose.material3:material3-adaptive-navigation-suite:1.3.1 +androidx.compose.material3:material3-android:1.3.1 +androidx.compose.material3:material3:1.3.1 +androidx.compose.material:material-icons-core-android:1.7.5 +androidx.compose.material:material-icons-core:1.7.5 +androidx.compose.material:material-icons-extended-android:1.7.5 +androidx.compose.material:material-icons-extended:1.7.5 +androidx.compose.material:material-ripple-android:1.7.5 +androidx.compose.material:material-ripple:1.7.5 +androidx.compose.runtime:runtime-android:1.7.5 +androidx.compose.runtime:runtime-saveable-android:1.7.5 +androidx.compose.runtime:runtime-saveable:1.7.5 +androidx.compose.runtime:runtime:1.7.5 +androidx.compose.ui:ui-android:1.7.5 +androidx.compose.ui:ui-geometry-android:1.7.5 +androidx.compose.ui:ui-geometry:1.7.5 +androidx.compose.ui:ui-graphics-android:1.7.5 +androidx.compose.ui:ui-graphics:1.7.5 +androidx.compose.ui:ui-text-android:1.7.5 +androidx.compose.ui:ui-text:1.7.5 +androidx.compose.ui:ui-tooling-preview-android:1.7.5 +androidx.compose.ui:ui-tooling-preview:1.7.5 +androidx.compose.ui:ui-unit-android:1.7.5 +androidx.compose.ui:ui-unit:1.7.5 +androidx.compose.ui:ui-util-android:1.7.5 +androidx.compose.ui:ui-util:1.7.5 +androidx.compose.ui:ui:1.7.5 androidx.concurrent:concurrent-futures:1.1.0 androidx.constraintlayout:constraintlayout-solver:2.0.1 androidx.constraintlayout:constraintlayout:2.0.1 @@ -152,6 +152,7 @@ com.google.android.datatransport:transport-runtime:3.3.0 com.google.android.gms:play-services-ads-identifier:18.0.0 com.google.android.gms:play-services-base:18.5.0 com.google.android.gms:play-services-basement:18.4.0 +com.google.android.gms:play-services-cloud-messaging:17.2.0 com.google.android.gms:play-services-measurement-api:22.1.2 com.google.android.gms:play-services-measurement-base:22.1.2 com.google.android.gms:play-services-measurement-impl:22.1.2 @@ -167,7 +168,7 @@ com.google.errorprone:error_prone_annotations:2.26.0 com.google.firebase:firebase-abt:21.1.1 com.google.firebase:firebase-analytics:22.1.2 com.google.firebase:firebase-annotations:16.2.0 -com.google.firebase:firebase-bom:33.5.1 +com.google.firebase:firebase-bom:33.6.0 com.google.firebase:firebase-common-ktx:21.0.0 com.google.firebase:firebase-common:21.0.0 com.google.firebase:firebase-components:18.0.0 @@ -178,9 +179,11 @@ com.google.firebase:firebase-datatransport:19.0.0 com.google.firebase:firebase-encoders-json:18.0.1 com.google.firebase:firebase-encoders-proto:16.0.0 com.google.firebase:firebase-encoders:17.0.0 +com.google.firebase:firebase-iid-interop:17.1.0 com.google.firebase:firebase-installations-interop:17.2.0 com.google.firebase:firebase-installations:18.0.0 com.google.firebase:firebase-measurement-connector:20.0.1 +com.google.firebase:firebase-messaging:24.1.0 com.google.firebase:firebase-perf:21.0.2 com.google.firebase:firebase-sessions:2.0.6 com.google.firebase:protolite-well-known-types:18.0.0 @@ -244,42 +247,42 @@ javax.inject:javax.inject:1 org.checkerframework:checker-qual:3.12.0 org.jetbrains.androidx.core:core-bundle-android:1.0.1 org.jetbrains.androidx.core:core-bundle:1.0.1 -org.jetbrains.androidx.lifecycle:lifecycle-common:2.8.3 -org.jetbrains.androidx.lifecycle:lifecycle-runtime-compose:2.8.3 -org.jetbrains.androidx.lifecycle:lifecycle-runtime:2.8.3 +org.jetbrains.androidx.lifecycle:lifecycle-common:2.8.4 +org.jetbrains.androidx.lifecycle:lifecycle-runtime-compose:2.8.4 +org.jetbrains.androidx.lifecycle:lifecycle-runtime:2.8.4 org.jetbrains.androidx.lifecycle:lifecycle-viewmodel-compose:2.8.2 org.jetbrains.androidx.lifecycle:lifecycle-viewmodel-savedstate:2.8.2 -org.jetbrains.androidx.lifecycle:lifecycle-viewmodel:2.8.3 +org.jetbrains.androidx.lifecycle:lifecycle-viewmodel:2.8.4 org.jetbrains.androidx.navigation:navigation-common:2.8.0-alpha10 org.jetbrains.androidx.navigation:navigation-compose:2.8.0-alpha10 org.jetbrains.androidx.navigation:navigation-runtime:2.8.0-alpha10 org.jetbrains.androidx.savedstate:savedstate:1.2.2 -org.jetbrains.androidx.window:window-core:1.3.0 -org.jetbrains.compose.animation:animation-core:1.7.0 -org.jetbrains.compose.animation:animation:1.7.0 -org.jetbrains.compose.annotation-internal:annotation:1.7.0 -org.jetbrains.compose.collection-internal:collection:1.7.0 -org.jetbrains.compose.components:components-resources-android:1.7.0 -org.jetbrains.compose.components:components-resources:1.7.0 -org.jetbrains.compose.foundation:foundation-layout:1.7.0 -org.jetbrains.compose.foundation:foundation:1.7.0 -org.jetbrains.compose.material3.adaptive:adaptive-layout:1.0.0 -org.jetbrains.compose.material3.adaptive:adaptive-navigation:1.0.0 -org.jetbrains.compose.material3.adaptive:adaptive:1.0.0 -org.jetbrains.compose.material3:material3-adaptive-navigation-suite:1.7.0 -org.jetbrains.compose.material3:material3:1.7.0 -org.jetbrains.compose.material:material-icons-core:1.7.0 -org.jetbrains.compose.material:material-icons-extended:1.7.0 -org.jetbrains.compose.material:material-ripple:1.7.0 -org.jetbrains.compose.runtime:runtime-saveable:1.7.0 -org.jetbrains.compose.runtime:runtime:1.7.0 -org.jetbrains.compose.ui:ui-geometry:1.7.0 -org.jetbrains.compose.ui:ui-graphics:1.7.0 -org.jetbrains.compose.ui:ui-text:1.7.0 -org.jetbrains.compose.ui:ui-tooling-preview:1.7.0 -org.jetbrains.compose.ui:ui-unit:1.7.0 -org.jetbrains.compose.ui:ui-util:1.7.0 -org.jetbrains.compose.ui:ui:1.7.0 +org.jetbrains.androidx.window:window-core:1.3.1 +org.jetbrains.compose.animation:animation-core:1.7.1 +org.jetbrains.compose.animation:animation:1.7.1 +org.jetbrains.compose.annotation-internal:annotation:1.7.1 +org.jetbrains.compose.collection-internal:collection:1.7.1 +org.jetbrains.compose.components:components-resources-android:1.7.1 +org.jetbrains.compose.components:components-resources:1.7.1 +org.jetbrains.compose.foundation:foundation-layout:1.7.1 +org.jetbrains.compose.foundation:foundation:1.7.1 +org.jetbrains.compose.material3.adaptive:adaptive-layout:1.0.1 +org.jetbrains.compose.material3.adaptive:adaptive-navigation:1.0.1 +org.jetbrains.compose.material3.adaptive:adaptive:1.0.1 +org.jetbrains.compose.material3:material3-adaptive-navigation-suite:1.7.1 +org.jetbrains.compose.material3:material3:1.7.1 +org.jetbrains.compose.material:material-icons-core:1.7.1 +org.jetbrains.compose.material:material-icons-extended:1.7.1 +org.jetbrains.compose.material:material-ripple:1.7.1 +org.jetbrains.compose.runtime:runtime-saveable:1.7.1 +org.jetbrains.compose.runtime:runtime:1.7.1 +org.jetbrains.compose.ui:ui-geometry:1.7.1 +org.jetbrains.compose.ui:ui-graphics:1.7.1 +org.jetbrains.compose.ui:ui-text:1.7.1 +org.jetbrains.compose.ui:ui-tooling-preview:1.7.1 +org.jetbrains.compose.ui:ui-unit:1.7.1 +org.jetbrains.compose.ui:ui-util:1.7.1 +org.jetbrains.compose.ui:ui:1.7.1 org.jetbrains.kotlin:kotlin-android-extensions-runtime:1.9.22 org.jetbrains.kotlin:kotlin-bom:1.8.22 org.jetbrains.kotlin:kotlin-parcelize-runtime:1.9.22 diff --git a/app/platform/android/src/main/AndroidManifest.xml b/app/platform/android/src/main/AndroidManifest.xml index b76fae4c..76ff8a94 100644 --- a/app/platform/android/src/main/AndroidManifest.xml +++ b/app/platform/android/src/main/AndroidManifest.xml @@ -3,13 +3,14 @@ xmlns:tools="http://schemas.android.com/tools"> + + + + + + + - + + + + + + diff --git a/app/platform/android/src/main/kotlin/io/github/taetae98coding/diary/DiaryActivity.kt b/app/platform/android/src/main/kotlin/io/github/taetae98coding/diary/DiaryActivity.kt index 06f2728f..6662c3bc 100644 --- a/app/platform/android/src/main/kotlin/io/github/taetae98coding/diary/DiaryActivity.kt +++ b/app/platform/android/src/main/kotlin/io/github/taetae98coding/diary/DiaryActivity.kt @@ -1,17 +1,26 @@ package io.github.taetae98coding.diary +import android.Manifest import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge +import androidx.activity.result.contract.ActivityResultContracts import io.github.taetae98coding.diary.app.App public class DiaryActivity : ComponentActivity() { + private val notificationPermissionLauncer = registerForActivityResult( + contract = ActivityResultContracts.RequestPermission(), + callback = {} + ) + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) enableEdgeToEdge() setContent { App() } + + notificationPermissionLauncer.launch(Manifest.permission.POST_NOTIFICATIONS) } } diff --git a/app/platform/android/src/main/kotlin/io/github/taetae98coding/diary/DiaryApplication.kt b/app/platform/android/src/main/kotlin/io/github/taetae98coding/diary/DiaryApplication.kt index 2d2af98d..9de0ba24 100644 --- a/app/platform/android/src/main/kotlin/io/github/taetae98coding/diary/DiaryApplication.kt +++ b/app/platform/android/src/main/kotlin/io/github/taetae98coding/diary/DiaryApplication.kt @@ -1,29 +1,5 @@ package io.github.taetae98coding.diary import android.app.Application -import androidx.lifecycle.LifecycleOwner -import io.github.taetae98coding.diary.app.manager.BackupManager -import io.github.taetae98coding.diary.app.manager.FetchManager -import org.koin.android.ext.android.get -public class DiaryApplication : Application() { - override fun onCreate() { - super.onCreate() - initBackupManager() - initFetchManager() - } - - private fun initBackupManager() { - val appLifecycleOwner = get() - val backupManager = get() - - backupManager.attach(appLifecycleOwner) - } - - private fun initFetchManager() { - val appLifecycleOwner = get() - val fetchManager = get() - - fetchManager.attach(appLifecycleOwner) - } -} +public class DiaryApplication : Application() diff --git a/app/platform/android/src/main/kotlin/io/github/taetae98coding/diary/KoinAndroidModule.kt b/app/platform/android/src/main/kotlin/io/github/taetae98coding/diary/KoinAndroidModule.kt new file mode 100644 index 00000000..42b6268e --- /dev/null +++ b/app/platform/android/src/main/kotlin/io/github/taetae98coding/diary/KoinAndroidModule.kt @@ -0,0 +1,8 @@ +package io.github.taetae98coding.diary + +import org.koin.core.annotation.ComponentScan +import org.koin.core.annotation.Module + +@Module +@ComponentScan +internal class KoinAndroidModule diff --git a/app/platform/android/src/main/kotlin/io/github/taetae98coding/diary/initializer/BackupManagerInitializer.kt b/app/platform/android/src/main/kotlin/io/github/taetae98coding/diary/initializer/BackupManagerInitializer.kt new file mode 100644 index 00000000..cfead32a --- /dev/null +++ b/app/platform/android/src/main/kotlin/io/github/taetae98coding/diary/initializer/BackupManagerInitializer.kt @@ -0,0 +1,23 @@ +package io.github.taetae98coding.diary.initializer + +import android.content.Context +import androidx.lifecycle.LifecycleOwner +import androidx.startup.Initializer +import io.github.taetae98coding.diary.app.manager.BackupManager +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject + +public class BackupManagerInitializer : Initializer, KoinComponent { + private val manager by inject() + private val appLifecycleOwner by inject() + + override fun create(context: Context): BackupManager { + manager.attach(appLifecycleOwner) + + return manager + } + + override fun dependencies(): MutableList>> { + return mutableListOf(KoinInitializer::class.java) + } +} diff --git a/app/platform/android/src/main/kotlin/io/github/taetae98coding/diary/initializer/FCMManagerInitializer.kt b/app/platform/android/src/main/kotlin/io/github/taetae98coding/diary/initializer/FCMManagerInitializer.kt new file mode 100644 index 00000000..aad4df4a --- /dev/null +++ b/app/platform/android/src/main/kotlin/io/github/taetae98coding/diary/initializer/FCMManagerInitializer.kt @@ -0,0 +1,23 @@ +package io.github.taetae98coding.diary.initializer + +import android.content.Context +import androidx.lifecycle.LifecycleOwner +import androidx.startup.Initializer +import io.github.taetae98coding.diary.app.manager.FCMManager +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject + +public class FCMManagerInitializer : Initializer, KoinComponent { + private val manager by inject() + private val appLifecycleOwner by inject() + + override fun create(context: Context): FCMManager { + manager.attach(appLifecycleOwner) + + return manager + } + + override fun dependencies(): MutableList>> { + return mutableListOf(KoinInitializer::class.java) + } +} diff --git a/app/platform/android/src/main/kotlin/io/github/taetae98coding/diary/initializer/FetchManagerInitializer.kt b/app/platform/android/src/main/kotlin/io/github/taetae98coding/diary/initializer/FetchManagerInitializer.kt new file mode 100644 index 00000000..b85bffb1 --- /dev/null +++ b/app/platform/android/src/main/kotlin/io/github/taetae98coding/diary/initializer/FetchManagerInitializer.kt @@ -0,0 +1,23 @@ +package io.github.taetae98coding.diary.initializer + +import android.content.Context +import androidx.lifecycle.LifecycleOwner +import androidx.startup.Initializer +import io.github.taetae98coding.diary.app.manager.FetchManager +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject + +public class FetchManagerInitializer : Initializer, KoinComponent { + private val manager by inject() + private val appLifecycleOwner by inject() + + override fun create(context: Context): FetchManager { + manager.attach(appLifecycleOwner) + + return manager + } + + override fun dependencies(): MutableList>> { + return mutableListOf(KoinInitializer::class.java) + } +} diff --git a/app/platform/android/src/main/kotlin/io/github/taetae98coding/diary/initializer/KoinInitializer.kt b/app/platform/android/src/main/kotlin/io/github/taetae98coding/diary/initializer/KoinInitializer.kt index 749e953e..6e6f32cc 100644 --- a/app/platform/android/src/main/kotlin/io/github/taetae98coding/diary/initializer/KoinInitializer.kt +++ b/app/platform/android/src/main/kotlin/io/github/taetae98coding/diary/initializer/KoinInitializer.kt @@ -3,6 +3,7 @@ package io.github.taetae98coding.diary.initializer import android.content.Context import androidx.startup.Initializer import io.github.taetae98coding.diary.BuildConfig +import io.github.taetae98coding.diary.KoinAndroidModule import io.github.taetae98coding.diary.app.AppModule import io.github.taetae98coding.diary.core.account.preferences.datastore.AccountDataStorePreferencesModule import io.github.taetae98coding.diary.core.diary.database.room.DiaryRoomDatabaseModule @@ -24,6 +25,7 @@ public class KoinInitializer : Initializer { androidContext(context) modules( + KoinAndroidModule().module, AppModule().module, diaryServiceModule(), AccountDataStorePreferencesModule().module, diff --git a/app/platform/android/src/main/kotlin/io/github/taetae98coding/diary/notification/DefaultNotificationManager.kt b/app/platform/android/src/main/kotlin/io/github/taetae98coding/diary/notification/DefaultNotificationManager.kt new file mode 100644 index 00000000..040cc419 --- /dev/null +++ b/app/platform/android/src/main/kotlin/io/github/taetae98coding/diary/notification/DefaultNotificationManager.kt @@ -0,0 +1,41 @@ +package io.github.taetae98coding.diary.notification + +import android.Manifest +import android.content.Context +import android.content.pm.PackageManager +import androidx.core.app.NotificationChannelCompat +import androidx.core.app.NotificationCompat +import androidx.core.app.NotificationManagerCompat +import io.github.taetae98coding.diary.R +import kotlin.math.abs +import org.koin.core.annotation.Factory + +@Factory +internal class DefaultNotificationManager( + private val context: Context, +) { + fun notify(title: String, description: String?) { + if (context.checkSelfPermission(Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) return + + val notification = NotificationCompat.Builder(context, CHANNEL_ID) + .setSmallIcon(R.drawable.ic_android_black_24dp) + .setContentTitle(title) + .setContentText(description) + .setPriority(NotificationCompat.PRIORITY_DEFAULT) + .build() + + val channel = NotificationChannelCompat.Builder(CHANNEL_ID, NotificationManagerCompat.IMPORTANCE_LOW) + .setName(context.getString(R.string.default_channel_name)) + .setDescription(context.getString(R.string.default_channel_description)) + .build() + + val manager = NotificationManagerCompat.from(context) + + manager.createNotificationChannel(channel) + manager.notify(abs(System.currentTimeMillis().toInt()), notification) + } + + companion object { + const val CHANNEL_ID = "Diary" + } +} diff --git a/app/platform/android/src/main/kotlin/io/github/taetae98coding/diary/service/DiaryFirebaseMessagingService.kt b/app/platform/android/src/main/kotlin/io/github/taetae98coding/diary/service/DiaryFirebaseMessagingService.kt new file mode 100644 index 00000000..55db41b7 --- /dev/null +++ b/app/platform/android/src/main/kotlin/io/github/taetae98coding/diary/service/DiaryFirebaseMessagingService.kt @@ -0,0 +1,45 @@ +package io.github.taetae98coding.diary.service + +import com.google.firebase.messaging.FirebaseMessagingService +import com.google.firebase.messaging.RemoteMessage +import io.github.taetae98coding.diary.domain.fcm.usecase.UpdateFCMTokenUseCase +import io.github.taetae98coding.diary.notification.DefaultNotificationManager +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.cancel +import kotlinx.coroutines.launch +import org.koin.android.ext.android.inject + +public class DiaryFirebaseMessagingService : FirebaseMessagingService() { + private val serviceScope = CoroutineScope(Dispatchers.Main.immediate + SupervisorJob()) + + private val defaultNotificationManager by inject() + private val updateFCMTokenUseCase by inject() + + override fun onNewToken(token: String) { + super.onNewToken(token) + serviceScope.launch { updateFCMTokenUseCase() } + } + + override fun onMessageReceived(message: RemoteMessage) { + super.onMessageReceived(message) + when (message.data[TYPE]) { + else -> { + defaultNotificationManager.notify( + title = message.notification?.title.orEmpty(), + description = message.notification?.body, + ) + } + } + } + + override fun onDestroy() { + super.onDestroy() + serviceScope.cancel() + } + + public companion object { + private const val TYPE = "type" + } +} diff --git a/app/platform/android/src/main/res/drawable/ic_android_black_24dp.xml b/app/platform/android/src/main/res/drawable/ic_android_black_24dp.xml new file mode 100644 index 00000000..171b3901 --- /dev/null +++ b/app/platform/android/src/main/res/drawable/ic_android_black_24dp.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/platform/android/src/main/res/values-ko/strings.xml b/app/platform/android/src/main/res/values-ko/strings.xml new file mode 100644 index 00000000..f04fc6fe --- /dev/null +++ b/app/platform/android/src/main/res/values-ko/strings.xml @@ -0,0 +1,5 @@ + + + 다이어리 + 다이어리 알림. + \ No newline at end of file diff --git a/app/platform/android/src/main/res/values/strings.xml b/app/platform/android/src/main/res/values/strings.xml new file mode 100644 index 00000000..d8941a94 --- /dev/null +++ b/app/platform/android/src/main/res/values/strings.xml @@ -0,0 +1,5 @@ + + + Diary + Diary notification. + \ No newline at end of file diff --git a/app/platform/common/build.gradle.kts b/app/platform/common/build.gradle.kts index a1ea8f56..209b0741 100644 --- a/app/platform/common/build.gradle.kts +++ b/app/platform/common/build.gradle.kts @@ -8,15 +8,19 @@ kotlin { dependencies { implementation(project(":app:data:memo")) implementation(project(":app:data:account")) + implementation(project(":app:data:credential")) implementation(project(":app:data:holiday")) implementation(project(":app:data:backup")) implementation(project(":app:data:fetch")) + implementation(project(":app:data:fcm")) implementation(project(":app:domain:memo")) implementation(project(":app:domain:account")) + implementation(project(":app:domain:credential")) implementation(project(":app:domain:holiday")) implementation(project(":app:domain:backup")) implementation(project(":app:domain:fetch")) + implementation(project(":app:domain:fcm")) implementation(project(":app:core:coroutines")) implementation(project(":app:core:diary-service")) @@ -28,6 +32,7 @@ kotlin { implementation(project(":app:feature:account")) implementation(project(":library:datetime")) + implementation(project(":library:firebase-messaging")) implementation(compose.material3AdaptiveNavigationSuite) implementation(libs.compose.material3.adaptive) diff --git a/app/platform/common/src/commonMain/kotlin/io/github/taetae98coding/diary/app/AppModule.kt b/app/platform/common/src/commonMain/kotlin/io/github/taetae98coding/diary/app/AppModule.kt index a7e70294..bb6b99bd 100644 --- a/app/platform/common/src/commonMain/kotlin/io/github/taetae98coding/diary/app/AppModule.kt +++ b/app/platform/common/src/commonMain/kotlin/io/github/taetae98coding/diary/app/AppModule.kt @@ -5,11 +5,15 @@ import io.github.taetae98coding.diary.core.diary.service.DiaryServiceModule import io.github.taetae98coding.diary.core.holiday.service.HolidayServiceModule import io.github.taetae98coding.diary.data.account.AccountDataModule import io.github.taetae98coding.diary.data.backup.BackupDataModule +import io.github.taetae98coding.diary.data.credential.CredentialDataModule +import io.github.taetae98coding.diary.data.fcm.FCMDataModule import io.github.taetae98coding.diary.data.fetch.FetchDataModule import io.github.taetae98coding.diary.data.holiday.HolidayDataModule import io.github.taetae98coding.diary.data.memo.MemoDataModule import io.github.taetae98coding.diary.domain.account.AccountDomainModule import io.github.taetae98coding.diary.domain.backup.BackupDomainModule +import io.github.taetae98coding.diary.domain.credential.CredentialDomainModule +import io.github.taetae98coding.diary.domain.fcm.FCMDomainModule import io.github.taetae98coding.diary.domain.fetch.FetchDomainModule import io.github.taetae98coding.diary.domain.holiday.HolidayDomainModule import io.github.taetae98coding.diary.domain.memo.MemoDomainModule @@ -17,6 +21,9 @@ import io.github.taetae98coding.diary.feature.account.AccountFeatureModule import io.github.taetae98coding.diary.feature.calendar.CalendarFeatureModule import io.github.taetae98coding.diary.feature.memo.MemoFeatureModule import io.github.taetae98coding.diary.feature.more.MoreFeatureModule +import io.github.taetae98coding.diary.library.firebase.KFirebase +import io.github.taetae98coding.diary.library.firebase.messaging.KFirebaseMessaging +import io.github.taetae98coding.diary.library.firebase.messaging.messaging import kotlinx.datetime.Clock import org.koin.core.annotation.ComponentScan import org.koin.core.annotation.Module @@ -32,11 +39,15 @@ import org.koin.core.annotation.Singleton HolidayDataModule::class, BackupDataModule::class, FetchDataModule::class, + FCMDataModule::class, + CredentialDataModule::class, MemoDomainModule::class, AccountDomainModule::class, HolidayDomainModule::class, BackupDomainModule::class, FetchDomainModule::class, + FCMDomainModule::class, + CredentialDomainModule::class, MemoFeatureModule::class, CalendarFeatureModule::class, MoreFeatureModule::class, @@ -49,4 +60,9 @@ public class AppModule { internal fun providesClock(): Clock { return Clock.System } + + @Singleton + internal fun providesFirebaseMessaging(): KFirebaseMessaging { + return KFirebase.messaging + } } diff --git a/app/platform/common/src/commonMain/kotlin/io/github/taetae98coding/diary/app/manager/FCMManager.kt b/app/platform/common/src/commonMain/kotlin/io/github/taetae98coding/diary/app/manager/FCMManager.kt new file mode 100644 index 00000000..34095ce0 --- /dev/null +++ b/app/platform/common/src/commonMain/kotlin/io/github/taetae98coding/diary/app/manager/FCMManager.kt @@ -0,0 +1,22 @@ +package io.github.taetae98coding.diary.app.manager + +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle +import io.github.taetae98coding.diary.domain.fcm.usecase.UpdateFCMTokenUseCase +import kotlinx.coroutines.launch +import org.koin.core.annotation.Singleton + +@Singleton +public class FCMManager internal constructor( + private val updateFCMTokenUseCase: UpdateFCMTokenUseCase, +) { + public fun attach(lifecycleOwner: LifecycleOwner) { + lifecycleOwner.lifecycleScope.launch { + lifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) { + updateFCMTokenUseCase() + } + } + } +} diff --git a/app/platform/ios/src/iosMain/kotlin/io/github/taetae98coding/diary/initializer/InitFirebaseMessagingManaver.kt b/app/platform/ios/src/iosMain/kotlin/io/github/taetae98coding/diary/initializer/InitFirebaseMessagingManaver.kt new file mode 100644 index 00000000..07e533b6 --- /dev/null +++ b/app/platform/ios/src/iosMain/kotlin/io/github/taetae98coding/diary/initializer/InitFirebaseMessagingManaver.kt @@ -0,0 +1,18 @@ +package io.github.taetae98coding.diary.initializer + +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.lifecycleScope +import io.github.taetae98coding.diary.app.manager.FCMManager +import kotlinx.coroutines.launch +import org.koin.core.KoinApplication + +internal fun initFirebaseMessagingManager( + koinApplication: KoinApplication, +) { + val appLifecycleOwner = koinApplication.koin.get() + val fcmManager = koinApplication.koin.get() + + appLifecycleOwner.lifecycleScope.launch { + fcmManager.attach(appLifecycleOwner) + } +} diff --git a/app/platform/ios/src/iosMain/kotlin/io/github/taetae98coding/diary/initializer/IosInitializer.kt b/app/platform/ios/src/iosMain/kotlin/io/github/taetae98coding/diary/initializer/IosInitializer.kt index 4211f419..29ebdbc5 100644 --- a/app/platform/ios/src/iosMain/kotlin/io/github/taetae98coding/diary/initializer/IosInitializer.kt +++ b/app/platform/ios/src/iosMain/kotlin/io/github/taetae98coding/diary/initializer/IosInitializer.kt @@ -6,4 +6,5 @@ public fun init() { initBackupManager(koinApplication) initFetchManager(koinApplication) + initFirebaseMessagingManager(koinApplication) } diff --git a/build-logic/build.gradle.kts b/build-logic/build.gradle.kts index 132fd605..17ec2a26 100644 --- a/build-logic/build.gradle.kts +++ b/build-logic/build.gradle.kts @@ -88,6 +88,11 @@ gradlePlugin { implementationClass = "plugin.compose.ComposePlugin" } + register("diary.cocoapods") { + id = "diary.cocoapods" + implementationClass = "plugin.cocoapods.CocoapodsPlugin" + } + register("diary.app.data") { id = "diary.app.data" implementationClass = "plugin.convention.AppDataPlugin" diff --git a/build-logic/src/main/kotlin/ext/KotlinMultiplatformExt.kt b/build-logic/src/main/kotlin/ext/KotlinMultiplatformExt.kt index bcfbbf98..8e117072 100644 --- a/build-logic/src/main/kotlin/ext/KotlinMultiplatformExt.kt +++ b/build-logic/src/main/kotlin/ext/KotlinMultiplatformExt.kt @@ -6,6 +6,7 @@ import org.gradle.api.plugins.ExtensionAware import org.jetbrains.compose.ComposePlugin import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet +import org.jetbrains.kotlin.gradle.plugin.cocoapods.CocoapodsExtension internal fun KotlinMultiplatformExtension.sourceSets( configure: Action>, @@ -15,3 +16,7 @@ internal fun KotlinMultiplatformExtension.sourceSets( internal val KotlinMultiplatformExtension.compose: ComposePlugin.Dependencies get() = (this as ExtensionAware).extensions.getByName("compose") as ComposePlugin.Dependencies + +public fun KotlinMultiplatformExtension.cocoapods(configure: Action) { + (this as ExtensionAware).extensions.configure("cocoapods", configure) +} diff --git a/build-logic/src/main/kotlin/plugin/cocoapods/CocoapodsPlugin.kt b/build-logic/src/main/kotlin/plugin/cocoapods/CocoapodsPlugin.kt new file mode 100644 index 00000000..901c9873 --- /dev/null +++ b/build-logic/src/main/kotlin/plugin/cocoapods/CocoapodsPlugin.kt @@ -0,0 +1,25 @@ +package plugin.cocoapods + +import ext.cocoapods +import ext.withKotlinMultiplatform +import ext.withPlugin +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.jetbrains.kotlin.gradle.plugin.mpp.NativeBuildType + +internal class CocoapodsPlugin : Plugin { + override fun apply(target: Project) { + target.withPlugin { + apply("org.jetbrains.kotlin.native.cocoapods") + } + + target.withKotlinMultiplatform { + cocoapods { + xcodeConfigurationToNativeBuildType["DevDebug"] = NativeBuildType.DEBUG + xcodeConfigurationToNativeBuildType["DevRelease"] = NativeBuildType.RELEASE + xcodeConfigurationToNativeBuildType["RealDebug"] = NativeBuildType.DEBUG + xcodeConfigurationToNativeBuildType["RealRelease"] = NativeBuildType.RELEASE + } + } + } +} diff --git a/build-logic/src/main/kotlin/plugin/compose/ComposePlugin.kt b/build-logic/src/main/kotlin/plugin/compose/ComposePlugin.kt index b2e1e846..52a65aec 100644 --- a/build-logic/src/main/kotlin/plugin/compose/ComposePlugin.kt +++ b/build-logic/src/main/kotlin/plugin/compose/ComposePlugin.kt @@ -5,6 +5,7 @@ import ext.withComposeCompiler import ext.withPlugin import org.gradle.api.Plugin import org.gradle.api.Project +import org.gradle.api.file.RegularFile import org.gradle.kotlin.dsl.assign import org.jetbrains.kotlin.compose.compiler.gradle.ComposeFeatureFlag @@ -25,6 +26,7 @@ internal class ComposePlugin : Plugin { featureFlags.add(ComposeFeatureFlag.OptimizeNonSkippingGroups) // featureFlags.add(ComposeFeatureFlag.PausableComposition) + stabilityConfigurationFile.set(RegularFile { target.rootProject.file("compose-stability-configuration-file.txt") }) // stabilityConfigurationFiles.add(RegularFile { target.rootProject.file("compose-stability-configuration-file.txt") }) metricsDestination.assign(target.rootProject.file("build/compose/metrics")) diff --git a/build.gradle.kts b/build.gradle.kts index e408b5c7..6255b12e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -3,6 +3,7 @@ plugins { alias(libs.plugins.kotlin.android).apply(false) alias(libs.plugins.kotlin.jvm).apply(false) + alias(libs.plugins.kotlin.cocoapods).apply(false) alias(libs.plugins.kotlin.serialization).apply(false) alias(libs.plugins.ksp).apply(false) @@ -53,6 +54,16 @@ subprojects { } } +subprojects { + afterEvaluate { + if (pluginManager.findPlugin("org.jetbrains.kotlin.native.cocoapods") != null) { + tasks.register("buildCocoapods") { + dependsOn(tasks.getByName("build")) + } + } + } +} + fun Project.isMultiplatformProject(): Boolean { return plugins.findPlugin("org.jetbrains.kotlin.multiplatform") != null } diff --git a/common/model/src/commonMain/kotlin/io/github/taetae98coding/diary/common/model/request/fcm/DeleteFCMRequest.kt b/common/model/src/commonMain/kotlin/io/github/taetae98coding/diary/common/model/request/fcm/DeleteFCMRequest.kt new file mode 100644 index 00000000..750d8f6b --- /dev/null +++ b/common/model/src/commonMain/kotlin/io/github/taetae98coding/diary/common/model/request/fcm/DeleteFCMRequest.kt @@ -0,0 +1,10 @@ +package io.github.taetae98coding.diary.common.model.request.fcm + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +public data class DeleteFCMRequest( + @SerialName("token") + val token: String, +) diff --git a/common/model/src/commonMain/kotlin/io/github/taetae98coding/diary/common/model/request/fcm/UpsertFCMRequest.kt b/common/model/src/commonMain/kotlin/io/github/taetae98coding/diary/common/model/request/fcm/UpsertFCMRequest.kt new file mode 100644 index 00000000..de8f6260 --- /dev/null +++ b/common/model/src/commonMain/kotlin/io/github/taetae98coding/diary/common/model/request/fcm/UpsertFCMRequest.kt @@ -0,0 +1,10 @@ +package io.github.taetae98coding.diary.common.model.request.fcm + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +public data class UpsertFCMRequest( + @SerialName("token") + val token: String, +) diff --git a/docs/images/graphs/dep_graph_app_data_account.svg b/docs/images/graphs/dep_graph_app_data_account.svg index 47240bdd..2a8edad1 100644 --- a/docs/images/graphs/dep_graph_app_data_account.svg +++ b/docs/images/graphs/dep_graph_app_data_account.svg @@ -1,101 +1,73 @@ - + - + - - :app:data:account + + :app:data:account :library:coroutines - - + + :library:datetime - - + + - - :app:core:account-preferences + + :app:core:account-preferences - - + + - - :app:core:diary-service + + :app:domain:account - - - - - - :app:domain:account + + - - + + - - + + - - :app:core:model + + :app:core:model - - + + - - :common:exception - - - - - - - - :common:model - - - - - - - - - - - - - - - - + + :common:exception - - + + - - :library:kotlin + + :library:kotlin - - + + diff --git a/docs/images/graphs/dep_graph_app_data_credential.svg b/docs/images/graphs/dep_graph_app_data_credential.svg new file mode 100644 index 00000000..eb52a792 --- /dev/null +++ b/docs/images/graphs/dep_graph_app_data_credential.svg @@ -0,0 +1,221 @@ + + + + + + :app:data:credential + + + + :library:coroutines + + + + + + + + :library:datetime + + + + + + + + :app:core:account-preferences + + + + + + + + :app:core:diary-service + + + + + + + + :app:domain:credential + + + + + + + + + + + + :app:core:model + + + + + + + + :common:exception + + + + + + + + :common:model + + + + + + + + + + + + + + + + + + + + + + + + :library:kotlin + + + + + + + + :app:domain:fetch + + + + + + + + :app:domain:backup + + + + + + + + :app:domain:fcm + + + + + + + + + + + + + + + + + + + + + + + + + + + + :app:domain:account + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/images/graphs/dep_graph_app_data_fcm.svg b/docs/images/graphs/dep_graph_app_data_fcm.svg new file mode 100644 index 00000000..7bae6781 --- /dev/null +++ b/docs/images/graphs/dep_graph_app_data_fcm.svg @@ -0,0 +1,141 @@ + + + + + + :app:data:fcm + + + + :library:coroutines + + + + + + + + :library:datetime + + + + + + + + :app:core:diary-service + + + + + + + + :app:domain:fcm + + + + + + + + :library:firebase-messaging + + + + + + + + :app:core:model + + + + + + + + :common:exception + + + + + + + + :app:core:account-preferences + + + + + + + + :common:model + + + + + + + + + + + + + + + + + + + + + + + + :library:kotlin + + + + + + + + :app:domain:account + + + + + + + + :library:firebase-common + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/images/graphs/dep_graph_app_data_memo.svg b/docs/images/graphs/dep_graph_app_data_memo.svg index c052978c..20207a19 100644 --- a/docs/images/graphs/dep_graph_app_data_memo.svg +++ b/docs/images/graphs/dep_graph_app_data_memo.svg @@ -1,105 +1,137 @@ - + - - - - :app:data:memo + + + + :app:data:memo - - - :library:coroutines + + + :library:coroutines - - + + - - - :library:datetime + + + :library:datetime - - + + - - - :app:core:diary-database + + + :app:core:diary-database - - + + - - - :app:domain:memo + + + :app:domain:memo - - + + - - - :app:core:model + + + :app:core:model - - + + - - + + - - + + - - + + - - - :common:exception + + + :common:exception - - + + - - - :library:kotlin + + + :library:kotlin - - + + - - - :app:domain:account + + + :app:domain:account + + + + + + + + :app:domain:backup - - + + - - + + - - + + - - + + + + + + + + + + - - + + - - + + + + + + + + + + + + + + + + + + diff --git a/docs/images/graphs/dep_graph_app_domain_credential.svg b/docs/images/graphs/dep_graph_app_domain_credential.svg new file mode 100644 index 00000000..f46dd985 --- /dev/null +++ b/docs/images/graphs/dep_graph_app_domain_credential.svg @@ -0,0 +1,169 @@ + + + + + + :app:domain:credential + + + + :app:core:model + + + + + + + + :common:exception + + + + + + + + :library:coroutines + + + + + + + + :library:datetime + + + + + + + + :library:kotlin + + + + + + + + :app:domain:fetch + + + + + + + + :app:domain:backup + + + + + + + + :app:domain:fcm + + + + + + + + + + + + + + + + + + + + + + + + + + + + :app:domain:account + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/images/graphs/dep_graph_app_domain_fcm.svg b/docs/images/graphs/dep_graph_app_domain_fcm.svg new file mode 100644 index 00000000..4abb57f6 --- /dev/null +++ b/docs/images/graphs/dep_graph_app_domain_fcm.svg @@ -0,0 +1,77 @@ + + + + + + :app:domain:fcm + + + + :app:core:model + + + + + + + + :common:exception + + + + + + + + :library:coroutines + + + + + + + + :library:datetime + + + + + + + + :library:kotlin + + + + + + + + :app:domain:account + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/images/graphs/dep_graph_app_domain_memo.svg b/docs/images/graphs/dep_graph_app_domain_memo.svg index 8a6a5f87..e504fb4a 100644 --- a/docs/images/graphs/dep_graph_app_domain_memo.svg +++ b/docs/images/graphs/dep_graph_app_domain_memo.svg @@ -1,77 +1,109 @@ - + - - - - :app:domain:memo + + + + :app:domain:memo - - - :app:core:model + + + :app:core:model - - + + - - - :common:exception + + + :common:exception - - + + - - - :library:coroutines + + + :library:coroutines - - + + - - - :library:datetime + + + :library:datetime - - + + - - - :library:kotlin + + + :library:kotlin - - + + - - - :app:domain:account + + + :app:domain:account + + + + + + + + :app:domain:backup - - + + - - + + + + + + + + + + - - + + - - + + - - + + - - + + + + + + + + + + + + + + + + + + diff --git a/docs/images/graphs/dep_graph_app_feature_account.svg b/docs/images/graphs/dep_graph_app_feature_account.svg index 868ada75..2b02d637 100644 --- a/docs/images/graphs/dep_graph_app_feature_account.svg +++ b/docs/images/graphs/dep_graph_app_feature_account.svg @@ -1,141 +1,265 @@ - + - - - - :app:feature:account + + + + :app:feature:account - - - :app:core:design-system + + + :app:core:design-system + + + + + + + + :app:core:navigation + + + + + + + + :app:core:resources + + + + + + + + :library:color + + + + + + + + :library:kotlin + + + + + + + + :library:navigation + + + + + + + + :library:coroutines + + + + + + + + :library:datetime + + + + + + + + :library:shimmer-m3 + + + + + + + + :app:domain:account + + + + + + + + :app:domain:credential - - + + + + + + - - - :app:core:navigation + + + - - + + - - - :app:core:resources + + + - - + + - - - :library:color + + + - - + + - - - :library:kotlin + + + - - + + - - - :library:navigation + + + :app:core:model + + + + + + + + :common:exception - - + + - - - :library:coroutines + + + - - + + - - - :library:datetime + + + - - + + - - - :library:shimmer-m3 + + + + + + + :app:domain:fetch + + + + + + + + :app:domain:backup + + + + + + + + :app:domain:fcm + + + + - - + + - - - :app:domain:account + + + - - + + - - + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - - :app:core:model + + + - - + + - - - :common:exception + + + + + + + - - + + diff --git a/docs/images/graphs/dep_graph_app_feature_calendar.svg b/docs/images/graphs/dep_graph_app_feature_calendar.svg index ecde80db..ffb82911 100644 --- a/docs/images/graphs/dep_graph_app_feature_calendar.svg +++ b/docs/images/graphs/dep_graph_app_feature_calendar.svg @@ -1,221 +1,253 @@ - + - - - - :app:feature:calendar + + + + :app:feature:calendar - - - :app:core:design-system + + + :app:core:design-system - - + + - - - :app:core:navigation + + + :app:core:navigation - - + + - - - :app:core:resources + + + :app:core:resources - - + + - - - :library:color + + + :library:color - - + + - - - :library:kotlin + + + :library:kotlin - - + + - - - :library:navigation + + + :library:navigation - - + + - - - :library:coroutines + + + :library:coroutines - - + + - - - :library:datetime + + + :library:datetime - - + + - - - :library:shimmer-m3 + + + :library:shimmer-m3 - - + + - - - :app:core:calendar-compose + + + :app:core:calendar-compose + + + + + + + + :app:domain:memo - - + + - - - :app:domain:memo + + + :app:domain:holiday - - + + - - - :app:domain:holiday + + + - - + + - - - + + + - - + + - - + + - - + + - - + + - - + + - - - + + + - - + + - - + + - - + + - - + + + + + + :app:core:model - - + + + + + + :common:exception - - + + - - - :app:core:model + + + :app:domain:account - - + + - - - :common:exception + + + :app:domain:backup + + + + - - + + - - - :app:domain:account + + + - - + + - - + + - - + + - - + + - - + + - - + + + + + + - - + + - - + + - - + + - - + + - - + + + + + + + + + + diff --git a/docs/images/graphs/dep_graph_app_feature_memo.svg b/docs/images/graphs/dep_graph_app_feature_memo.svg index 83452cf4..fd49f951 100644 --- a/docs/images/graphs/dep_graph_app_feature_memo.svg +++ b/docs/images/graphs/dep_graph_app_feature_memo.svg @@ -1,169 +1,201 @@ - + - - - - :app:feature:memo + + + + :app:feature:memo - - - :app:core:design-system + + + :app:core:design-system - - + + - - - :app:core:navigation + + + :app:core:navigation - - + + - - - :app:core:resources + + + :app:core:resources - - + + - - - :library:color + + + :library:color - - + + - - - :library:kotlin + + + :library:kotlin - - + + - - - :library:navigation + + + :library:navigation - - + + - - - :library:coroutines + + + :library:coroutines - - + + - - - :library:datetime + + + :library:datetime - - + + - - - :library:shimmer-m3 + + + :library:shimmer-m3 - - + + - - - :app:domain:memo + + + :app:domain:memo - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - - :app:core:model + + + :app:core:model - - + + - - - :common:exception + + + :common:exception - - + + - - - :app:domain:account + + + :app:domain:account + + + + + + + + :app:domain:backup - - + + - - + + - - + + - - + + - - + + + + + + + + + + - - + + + + + + + + + + + + + + + + + + diff --git a/docs/images/graphs/dep_graph_app_feature_more.svg b/docs/images/graphs/dep_graph_app_feature_more.svg index a44a8adf..db462397 100644 --- a/docs/images/graphs/dep_graph_app_feature_more.svg +++ b/docs/images/graphs/dep_graph_app_feature_more.svg @@ -1,141 +1,265 @@ - + - - - - :app:feature:more + + + + :app:feature:more - - - :app:core:design-system + + + :app:core:design-system + + + + + + + + :app:core:navigation + + + + + + + + :app:core:resources + + + + + + + + :library:color + + + + + + + + :library:kotlin + + + + + + + + :library:navigation + + + + + + + + :library:coroutines + + + + + + + + :library:datetime + + + + + + + + :library:shimmer-m3 + + + + + + + + :app:domain:account + + + + + + + + :app:domain:credential - - + + + + + + - - - :app:core:navigation + + + - - + + - - - :app:core:resources + + + - - + + - - - :library:color + + + - - + + - - - :library:kotlin + + + - - + + - - - :library:navigation + + + :app:core:model + + + + + + + + :common:exception - - + + - - - :library:coroutines + + + - - + + - - - :library:datetime + + + - - + + - - - :library:shimmer-m3 + + + + + + + :app:domain:fetch + + + + + + + + :app:domain:backup + + + + + + + + :app:domain:fcm + + + + - - + + - - - :app:domain:account + + + - - + + - - + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - - :app:core:model + + + - - + + - - - :common:exception + + + + + + + - - + + diff --git a/docs/images/graphs/dep_graph_app_platform_android.svg b/docs/images/graphs/dep_graph_app_platform_android.svg index f37bed28..91807a93 100644 --- a/docs/images/graphs/dep_graph_app_platform_android.svg +++ b/docs/images/graphs/dep_graph_app_platform_android.svg @@ -1,813 +1,969 @@ - + - - - - :app:platform:android + + + + :app:platform:android - - - :app:platform:common + + + :app:platform:common - - + + - - - :app:core:diary-database-room + + + :app:core:diary-database-room - - + + - - - :app:core:diary-service + + + :app:core:diary-service - - + + - - - :app:core:account-preferences-datastore + + + :app:core:account-preferences-datastore - - + + - - - :app:core:holiday-preferences-datastore + + + :app:core:holiday-preferences-datastore - - + + - - - :app:core:holiday-database-room + + + :app:core:holiday-database-room - - + + - - - :app:core:holiday-service + + + :app:core:holiday-service - - + + + + + + :app:domain:fcm + + + + + + + + - - + + - - + + - - - :app:core:design-system + + + :app:core:design-system - - + + - - - :app:core:navigation + + + :app:core:navigation - - + + - - - :app:core:resources + + + :app:core:resources - - + + - - - :library:color + + + :library:color - - + + - - - :library:kotlin + + + :library:kotlin - - + + - - - :library:navigation + + + :library:navigation - - + + - - - :library:coroutines + + + :library:coroutines - - + + - - - :library:datetime + + + :library:datetime - - + + - - - :library:shimmer-m3 + + + :library:shimmer-m3 - - + + - - - :app:data:memo + + + :app:data:memo - - + + + + + + :app:data:account + + + + + + + + :app:data:credential - - - :app:data:account + + + + + + + :app:data:holiday + + + + + + + + :app:data:backup - - + + - - - :app:data:holiday + + + :app:data:fetch - - + + - - - :app:data:backup + + + :app:data:fcm - - + + - - - :app:data:fetch + + + :app:domain:memo - - + + - - - :app:domain:memo + + + :app:domain:account - - + + - - - :app:domain:account + + + :app:domain:credential - - + + - - - :app:domain:holiday + + + :app:domain:holiday - - + + - - - :app:domain:backup + + + :app:domain:backup - - + + - - - :app:domain:fetch + + + :app:domain:fetch - - + + - - - :app:core:coroutines + + + :app:core:coroutines - - + + - - - :app:feature:memo + + + :app:feature:memo - - + + - - - :app:feature:calendar + + + :app:feature:calendar - - + + - - - :app:feature:more + + + :app:feature:more - - + + + + + + :app:feature:account + + + + + + + + :library:firebase-messaging - - - :app:feature:account + + + - - + + + + + + :library:koin-room - - + + - - - :library:koin-room + + + :app:core:diary-database - - + + - - - :app:core:diary-database + + + :library:room - - + + - - - :library:room + + + :app:core:model - - + + - - - :app:core:model + + + :common:exception - - + + - - - :common:exception + + + :app:core:account-preferences - - + + - - - :app:core:account-preferences + + + :common:model - - + + - - - :common:model + + + + + + + :library:koin-datastore - - + + - - + + - - - :library:koin-datastore + + + + + + + :app:core:holiday-preferences + + + + - - + + - - + + - - + + + + + + :app:core:holiday-database - - - :app:core:holiday-preferences + + + - - + + - - + + - - + + - - + + - - - :app:core:holiday-database + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + + + + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - - :app:core:calendar-compose + + + :app:core:calendar-compose - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + + + + + + + + + + + + + + + + + :library:firebase-common - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + diff --git a/docs/images/graphs/dep_graph_app_platform_common.svg b/docs/images/graphs/dep_graph_app_platform_common.svg index c07ceaf7..38c6255f 100644 --- a/docs/images/graphs/dep_graph_app_platform_common.svg +++ b/docs/images/graphs/dep_graph_app_platform_common.svg @@ -1,697 +1,849 @@ - + - - - - :app:platform:common + + + + :app:platform:common - - - :app:core:design-system + + + :app:core:design-system - - + + - - - :app:core:navigation + + + :app:core:navigation - - + + - - - :app:core:resources + + + :app:core:resources - - + + - - - :library:color + + + :library:color - - + + - - - :library:kotlin + + + :library:kotlin - - + + - - - :library:navigation + + + :library:navigation - - + + - - - :library:coroutines + + + :library:coroutines - - + + - - - :library:datetime + + + :library:datetime - - + + - - - :library:shimmer-m3 + + + :library:shimmer-m3 - - + + - - - :app:data:memo + + + :app:data:memo - - + + - - - :app:data:account + + + :app:data:account - - + + - - - :app:data:holiday + + + :app:data:credential - - + + - - - :app:data:backup + + + :app:data:holiday - - + + - - - :app:data:fetch + + + :app:data:backup - - + + - - - :app:domain:memo + + + :app:data:fetch - - + + - - - :app:domain:account + + + :app:data:fcm - - + + - - - :app:domain:holiday + + + :app:domain:memo - - + + - - - :app:domain:backup + + + :app:domain:account - - + + - - - :app:domain:fetch + + + :app:domain:credential - - + + - - - :app:core:coroutines + + + :app:domain:holiday - - + + - - - :app:core:diary-service + + + :app:domain:backup - - + + - - - :app:core:holiday-service + + + :app:domain:fetch - - + + - - - :app:feature:memo + + + :app:domain:fcm - - + + - - - :app:feature:calendar + + + :app:core:coroutines - - + + - - - :app:feature:more + + + :app:core:diary-service - - + + - - - :app:feature:account + + + :app:core:holiday-service - - + + + + + + :app:feature:memo + + + + + + + + :app:feature:calendar + + + + + + + + :app:feature:more + + + + + + + + :app:feature:account - - + + + + + + :library:firebase-messaging - - + + - - + + - - + + - - + + - - + + - - + + - - + + + + + + + + + + - - + + - - - :app:core:diary-database + + + :app:core:diary-database - - + + - - + + - - + + - - + + + + + + :app:core:account-preferences - - + + - - - :app:core:account-preferences + + + - - + + - - + + - - + + - - + + - - + + - - - :app:core:holiday-preferences + + + - - + + - - - :app:core:holiday-database + + + + + + + :app:core:holiday-preferences + + + + + + + + :app:core:holiday-database + + + + + + + + + + + + + + + + + + + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - - :app:core:model + + + :app:core:model - - + + - - - :common:exception + + + :common:exception - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + + + + + + + + + + + + + + + + + + + + + - - + + - - + + - - + + - - + + - - - :common:model + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + + + + + :common:model - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - - :app:core:calendar-compose + + + - - + + - - + + - - + + - - + + - - + + + + + + + + + + + + + + :app:core:calendar-compose - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + :library:firebase-common - - + + - - + + - - + + - - + + - - + + - - + + - - + + diff --git a/docs/images/graphs/dep_graph_app_platform_ios.svg b/docs/images/graphs/dep_graph_app_platform_ios.svg index 43da2d8f..be51ee81 100644 --- a/docs/images/graphs/dep_graph_app_platform_ios.svg +++ b/docs/images/graphs/dep_graph_app_platform_ios.svg @@ -1,817 +1,969 @@ - + - - - - :app:platform:ios + + + + :app:platform:ios - - - :app:platform:common + + + :app:platform:common - - + + - - - :app:core:coroutines + + + :app:core:coroutines - - + + - - - :app:core:diary-database-room + + + :app:core:diary-database-room - - + + - - - :app:core:diary-service + + + :app:core:diary-service - - + + - - - :app:core:account-preferences-datastore + + + :app:core:account-preferences-datastore - - + + - - - :app:core:holiday-preferences-datastore + + + :app:core:holiday-preferences-datastore - - + + - - - :app:core:holiday-database-room + + + :app:core:holiday-database-room - - + + - + :app:core:holiday-service - - + + - - + + - - + + - - + + - - - :app:core:design-system + + + :app:core:design-system - - + + - - - :app:core:navigation + + + :app:core:navigation - - + + - - - :app:core:resources + + + :app:core:resources - - + + - - - :library:color + + + :library:color - - + + - - - :library:kotlin + + + :library:kotlin - - + + - - - :library:navigation + + + :library:navigation - - + + - - - :library:coroutines + + + :library:coroutines - - + + - - - :library:datetime + + + :library:datetime - - + + - - - :library:shimmer-m3 + + + :library:shimmer-m3 - - + + - - - :app:data:memo + + + :app:data:memo - - + + - - - :app:data:account + + + :app:data:account - - + + - - - :app:data:holiday + + + :app:data:credential - - + + - - - :app:data:backup + + + :app:data:holiday - - + + - - - :app:data:fetch + + + :app:data:backup - - + + - - - :app:domain:memo + + + :app:data:fetch - - + + - - - :app:domain:account + + + :app:data:fcm - - + + - - - :app:domain:holiday + + + :app:domain:memo - - + + - - - :app:domain:backup + + + :app:domain:account - - + + - - - :app:domain:fetch + + + :app:domain:credential - - + + - - - :app:feature:memo + + + :app:domain:holiday - - + + - - - :app:feature:calendar + + + :app:domain:backup - - + + - - - :app:feature:more + + + :app:domain:fetch - - + + - - - :app:feature:account + + + :app:domain:fcm - - + + + + + + :app:feature:memo + + + + + + + + :app:feature:calendar + + + + + + + + :app:feature:more + + + + + + + + :app:feature:account - - + + + + + + :library:firebase-messaging - - - :library:koin-room + + + - - + + - - - :app:core:diary-database + + + :library:koin-room - - + + - - - :library:room + + + :app:core:diary-database - - + + - - - :app:core:model + + + :library:room - - + + - - - :common:exception + + + :app:core:model - - + + - - - :app:core:account-preferences + + + :common:exception - - + + - - - :common:model + + + :app:core:account-preferences - - + + + + + + :common:model - - + + - - - :library:koin-datastore + + + + + + + :library:koin-datastore - - + + - - + + - - + + - - - :app:core:holiday-preferences + + + :app:core:holiday-preferences - - + + - - + + - - + + - - + + - - - :app:core:holiday-database + + + :app:core:holiday-database - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + + + + + + + + + + + + + + + + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - - :app:core:calendar-compose + + + - - + + - - + + - - + + - - + + - - + + + + + + + + + + + + + + + + + + :app:core:calendar-compose - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + :library:firebase-common - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + diff --git a/docs/images/graphs/dep_graph_app_platform_jvm.svg b/docs/images/graphs/dep_graph_app_platform_jvm.svg index 4f83a8a4..81c5faac 100644 --- a/docs/images/graphs/dep_graph_app_platform_jvm.svg +++ b/docs/images/graphs/dep_graph_app_platform_jvm.svg @@ -1,825 +1,977 @@ - + - - - - :app:platform:jvm + + + + :app:platform:jvm - - - :app:platform:common + + + :app:platform:common - - + + - - - :app:core:coroutines + + + :app:core:coroutines - - + + - - - :app:core:diary-database-room + + + :app:core:diary-database-room - - + + - - - :app:core:diary-service + + + :app:core:diary-service - - + + - - - :app:core:account-preferences-datastore + + + :app:core:account-preferences-datastore - - + + - - - :app:core:holiday-preferences-datastore + + + :app:core:holiday-preferences-datastore - - + + - - - :app:core:holiday-database-room + + + :app:core:holiday-database-room - - + + - - - :app:core:holiday-service + + + :app:core:holiday-service - - + + - - - :library:koin-room + + + :library:koin-room - - + + - - - :library:koin-datastore + + + :library:koin-datastore - - + + - - + + - - + + - - + + - - - :app:core:design-system + + + :app:core:design-system - - + + - - - :app:core:navigation + + + :app:core:navigation - - + + - - - :app:core:resources + + + :app:core:resources - - + + - - - :library:color + + + :library:color - - + + - - - :library:kotlin + + + :library:kotlin - - + + - - - :library:navigation + + + :library:navigation - - + + - - - :library:coroutines + + + :library:coroutines - - + + - - - :library:datetime + + + :library:datetime - - + + - - - :library:shimmer-m3 + + + :library:shimmer-m3 - - + + - - - :app:data:memo + + + :app:data:memo - - + + - - - :app:data:account + + + :app:data:account - - + + - - - :app:data:holiday + + + :app:data:credential - - + + - - - :app:data:backup + + + :app:data:holiday - - + + - - - :app:data:fetch + + + :app:data:backup - - + + - - - :app:domain:memo + + + :app:data:fetch - - + + - - - :app:domain:account + + + :app:data:fcm - - + + - - - :app:domain:holiday + + + :app:domain:memo - - + + - - - :app:domain:backup + + + :app:domain:account - - + + - - - :app:domain:fetch + + + :app:domain:credential - - + + - - - :app:feature:memo + + + :app:domain:holiday - - + + - - - :app:feature:calendar + + + :app:domain:backup - - + + - - - :app:feature:more + + + :app:domain:fetch - - + + - - - :app:feature:account + + + :app:domain:fcm - - + + + + + + :app:feature:memo + + + + + + + + :app:feature:calendar + + + + + + + + :app:feature:more + + + + + + + + :app:feature:account - - + + + + + + :library:firebase-messaging - - + + - - - :app:core:diary-database + + + - - + + - - - :library:room + + + :app:core:diary-database - - + + - - - :app:core:model + + + :library:room - - + + - - - :common:exception + + + :app:core:model - - + + - - - :app:core:account-preferences + + + :common:exception - - + + - - - :common:model + + + :app:core:account-preferences - - + + + + + + :common:model - - + + - - + + - - + + - - + + - - - :app:core:holiday-preferences + + + + + + + :app:core:holiday-preferences - - + + - - + + - - + + - - + + - - - :app:core:holiday-database + + + :app:core:holiday-database - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + + + + + + + + + + + + + + + + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - - :app:core:calendar-compose + + + - - + + - - + + - - + + - - + + - - + + + + + + + + + + + + + + + + + + :app:core:calendar-compose - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + :library:firebase-common - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + diff --git a/docs/images/graphs/dep_graph_app_platform_wasm.svg b/docs/images/graphs/dep_graph_app_platform_wasm.svg index 20ad3df3..d2bbad42 100644 --- a/docs/images/graphs/dep_graph_app_platform_wasm.svg +++ b/docs/images/graphs/dep_graph_app_platform_wasm.svg @@ -1,773 +1,925 @@ - + - - - - :app:platform:wasm + + + + :app:platform:wasm - - - :app:platform:common + + + :app:platform:common - - + + - - - :app:core:coroutines + + + :app:core:coroutines - - + + - - - :app:core:diary-database-memory + + + :app:core:diary-database-memory - - + + - - - :app:core:diary-service + + + :app:core:diary-service - - + + - - - :app:core:account-preferences-memory + + + :app:core:account-preferences-memory - - + + - - - :app:core:holiday-preferences-memory + + + :app:core:holiday-preferences-memory - - + + - - - :app:core:holiday-database-memory + + + :app:core:holiday-database-memory - - + + - - - :app:core:holiday-service + + + :app:core:holiday-service - - + + - - + + - - + + - - + + - - - :app:core:design-system + + + :app:core:design-system - - + + - - - :app:core:navigation + + + :app:core:navigation - - + + - - - :app:core:resources + + + :app:core:resources - - + + - - - :library:color + + + :library:color - - + + - - - :library:kotlin + + + :library:kotlin - - + + - - - :library:navigation + + + :library:navigation - - + + - - - :library:coroutines + + + :library:coroutines - - + + - - - :library:datetime + + + :library:datetime - - + + - - - :library:shimmer-m3 + + + :library:shimmer-m3 - - + + - - - :app:data:memo + + + :app:data:memo - - + + - - - :app:data:account + + + :app:data:account - - + + - - - :app:data:holiday + + + :app:data:credential - - + + - - - :app:data:backup + + + :app:data:holiday - - + + - - - :app:data:fetch + + + :app:data:backup - - + + - - - :app:domain:memo + + + :app:data:fetch - - + + - - - :app:domain:account + + + :app:data:fcm - - + + - - - :app:domain:holiday + + + :app:domain:memo - - + + - - - :app:domain:backup + + + :app:domain:account - - + + - - - :app:domain:fetch + + + :app:domain:credential - - + + - - - :app:feature:memo + + + :app:domain:holiday - - + + - - - :app:feature:calendar + + + :app:domain:backup - - + + - - - :app:feature:more + + + :app:domain:fetch - - + + - - - :app:feature:account + + + :app:domain:fcm - - + + + + + + :app:feature:memo + + + + + + + + :app:feature:calendar + + + + + + + + :app:feature:more + + + + + + + + :app:feature:account - - + + + + + + :library:firebase-messaging - - + + - - - :app:core:diary-database + + + - - + + - - - :app:core:model + + + :app:core:diary-database - - + + - - - :common:exception + + + :app:core:model - - + + - - - :app:core:account-preferences + + + :common:exception - - + + - - - :common:model + + + :app:core:account-preferences - - + + + + + + :common:model - - + + - - - :app:core:holiday-preferences + + + + + + + :app:core:holiday-preferences - - + + - - - :app:core:holiday-database + + + :app:core:holiday-database - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + + + + + + + + + + + + + + + + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - - :app:core:calendar-compose + + + - - + + - - + + - - + + - - + + - - + + + + + + + + + + + + + + + + + + :app:core:calendar-compose - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + :library:firebase-common - - + + - - + + - - + + - - + + - - + + - - + + - - + + diff --git a/docs/images/graphs/dep_graph_library_firebase_common.svg b/docs/images/graphs/dep_graph_library_firebase_common.svg new file mode 100644 index 00000000..78fcd833 --- /dev/null +++ b/docs/images/graphs/dep_graph_library_firebase_common.svg @@ -0,0 +1,9 @@ + + + + + + :library:firebase-common + + + diff --git a/docs/images/graphs/dep_graph_library_firebase_messaging.svg b/docs/images/graphs/dep_graph_library_firebase_messaging.svg new file mode 100644 index 00000000..65cd3e1b --- /dev/null +++ b/docs/images/graphs/dep_graph_library_firebase_messaging.svg @@ -0,0 +1,17 @@ + + + + + + :library:firebase-messaging + + + + :library:firebase-common + + + + + + + diff --git a/docs/images/graphs/dep_graph_server_app.svg b/docs/images/graphs/dep_graph_server_app.svg index e6f6e563..99884d1d 100644 --- a/docs/images/graphs/dep_graph_server_app.svg +++ b/docs/images/graphs/dep_graph_server_app.svg @@ -1,189 +1,253 @@ - + - + - - :server:app + + :server:app - - :server:core:database + + :server:core:database - - + + - - :server:data:account + + :server:data:account - - + + - - :server:data:memo + + :server:data:memo - - + + - - :server:domain:account + + :server:data:fcm - - + + - - :server:domain:memo + + :server:domain:account - - + + - - :server:feature:home + + :server:domain:memo - - + + - - :server:feature:account + + :server:domain:fcm - - + + - - :server:feature:memo + + :server:feature:home - - + + - - :common:model + + :server:feature:account - - + + - - :server:core:model + + :server:feature:memo + + + + + + + + :server:feature:fcm + + + + + + + + :common:model + + + + + + + + :server:core:model - - + + - - + + + + + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + + + + + - - :common:exception + + :common:exception - - + + - - :library:kotlin + + :library:kotlin + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + diff --git a/docs/images/graphs/dep_graph_server_data_fcm.svg b/docs/images/graphs/dep_graph_server_data_fcm.svg new file mode 100644 index 00000000..0ccbfb26 --- /dev/null +++ b/docs/images/graphs/dep_graph_server_data_fcm.svg @@ -0,0 +1,57 @@ + + + + + + :server:data:fcm + + + + :server:core:model + + + + + + + + :server:core:database + + + + + + + + :server:domain:fcm + + + + + + + + + + + + + + + + :common:exception + + + + + + + + :library:kotlin + + + + + + + diff --git a/docs/images/graphs/dep_graph_server_domain_fcm.svg b/docs/images/graphs/dep_graph_server_domain_fcm.svg new file mode 100644 index 00000000..559a4e63 --- /dev/null +++ b/docs/images/graphs/dep_graph_server_domain_fcm.svg @@ -0,0 +1,33 @@ + + + + + + :server:domain:fcm + + + + :server:core:model + + + + + + + + :common:exception + + + + + + + + :library:kotlin + + + + + + + diff --git a/docs/images/graphs/dep_graph_server_feature_fcm.svg b/docs/images/graphs/dep_graph_server_feature_fcm.svg new file mode 100644 index 00000000..012aef53 --- /dev/null +++ b/docs/images/graphs/dep_graph_server_feature_fcm.svg @@ -0,0 +1,57 @@ + + + + + + :server:feature:fcm + + + + :server:core:model + + + + + + + + :common:model + + + + + + + + :common:exception + + + + + + + + :server:domain:fcm + + + + + + + + + + + + + + + + :library:kotlin + + + + + + + diff --git a/gradle.properties b/gradle.properties index be568185..20447bae 100644 --- a/gradle.properties +++ b/gradle.properties @@ -9,4 +9,6 @@ kotlin.native.ignoreDisabledTargets=true android.useAndroidX=true android.nonTransitiveRClass=true -buildkonfig.flavor=dev \ No newline at end of file +buildkonfig.flavor=dev + +firebase.cocoapods.version=11.5.0 \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 3498f555..291ce091 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -3,17 +3,17 @@ kotlin = "2.0.21" # https://github.com/jetbrains/kotlin/releases agp = "8.6.1" # https://developer.android.com/build/releases/gradle-plugin?hl=en ktor = "3.0.1" # https://github.com/ktorio/ktor/releases -ksp = "2.0.21-1.0.26" # https://github.com/google/ksp/releases +ksp = "2.0.21-1.0.28" # https://github.com/google/ksp/releases kotlinx-serialization = "1.7.3" # https://github.com/Kotlin/kotlinx.serialization/releases kotlinx-coroutines = "1.9.0" # https://github.com/Kotlin/kotlinx.coroutines/releases kotlinx-datetime = "0.6.1" # https://github.com/Kotlin/kotlinx-datetime/releases ### multiplatform -compose = "1.7.0" # https://github.com/JetBrains/compose-multiplatform/releases -compose-material3-adaptive = "1.0.0" +compose = "1.7.1" # https://github.com/JetBrains/compose-multiplatform/releases +compose-material3-adaptive = "1.0.1" navigation = "2.8.0-alpha10" -lifecycle = "2.8.3" +lifecycle = "2.8.4" androidx-lifecycle = "2.8.5" compose-markdown = "0.27.0" # https://github.com/mikepenz/multiplatform-markdown-renderer/releases @@ -29,7 +29,7 @@ android-material = "1.12.0" # https://github.com/material-compon androidx-activity = "1.9.3" # https://developer.android.com/jetpack/androidx/releases/activity?hl=en androidx-startup = "1.2.0" # https://developer.android.com/jetpack/androidx/releases/startup?hl=en -android-firebase-bom = "33.5.1" # https://firebase.google.com/support/release-notes/android +android-firebase-bom = "33.6.0" # https://firebase.google.com/support/release-notes/android android-firebase-crashlytics-plugin = "3.0.2" android-firebase-perf-plugin = "1.4.2" google-services = "4.4.2" @@ -39,6 +39,7 @@ leakcanary = "2.14" # https://github.com/square/leakcana ### server exposed = "0.56.0" # https://github.com/JetBrains/Exposed/releases mysql = "8.0.33" +server-firebase-admin = "9.4.1" # https://github.com/firebase/firebase-admin-java/releases logback = "1.5.12" # https://github.com/qos-ch/logback/tags @@ -103,6 +104,7 @@ android-firebase-bom = { group = "com.google.firebase", name = "firebase-bom", v android-firebase-analytics = { group = "com.google.firebase", name = "firebase-analytics" } android-firebase-crashlytics = { group = "com.google.firebase", name = "firebase-crashlytics" } android-firebase-perf = { group = "com.google.firebase", name = "firebase-perf" } +android-firebase-messaging = { group = "com.google.firebase", name = "firebase-messaging" } leakcanary = { group = "com.squareup.leakcanary", name = "leakcanary-android", version.ref = "leakcanary" } @@ -128,6 +130,8 @@ exposed-datetime = { group = "org.jetbrains.exposed", name = "exposed-kotlin-dat exposed-jdbc = { group = "org.jetbrains.exposed", name = "exposed-jdbc" } mysql-connector = { group = "mysql", name = "mysql-connector-java", version.ref = "mysql" } +server-firebase-admin = { group = "com.google.firebase", name = "firebase-admin", version.ref = "server-firebase-admin" } + logback-classic = { module = "ch.qos.logback:logback-classic", version.ref = "logback" } [plugins] @@ -136,6 +140,7 @@ kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } +kotlin-cocoapods = { id = "org.jetbrains.kotlin.native.cocoapods", version.ref = "kotlin" } kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 3eadd003..3602eebc 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Sat Oct 26 22:47:54 KST 2024 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.11-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/library/firebase-common/README.md b/library/firebase-common/README.md new file mode 100644 index 00000000..d8beb8da --- /dev/null +++ b/library/firebase-common/README.md @@ -0,0 +1,3 @@ +# :library:firebase-common module +## Dependency graph +![Dependency graph](../../docs/images/graphs/dep_graph_library_firebase_common.svg) diff --git a/library/firebase-common/build.gradle.kts b/library/firebase-common/build.gradle.kts new file mode 100644 index 00000000..9992a217 --- /dev/null +++ b/library/firebase-common/build.gradle.kts @@ -0,0 +1,8 @@ +plugins { + id("diary.android.library") + id("diary.kotlin.multiplatform.all") +} + +android { + namespace = "${Build.NAMESPACE}.library.firebase.common" +} diff --git a/library/firebase-common/src/commonMain/kotlin/io/github/taetae98coding/diary/library/firebase/KFirebase.kt b/library/firebase-common/src/commonMain/kotlin/io/github/taetae98coding/diary/library/firebase/KFirebase.kt new file mode 100644 index 00000000..c8831124 --- /dev/null +++ b/library/firebase-common/src/commonMain/kotlin/io/github/taetae98coding/diary/library/firebase/KFirebase.kt @@ -0,0 +1,3 @@ +package io.github.taetae98coding.diary.library.firebase + +public data object KFirebase diff --git a/library/firebase-messaging/README.md b/library/firebase-messaging/README.md new file mode 100644 index 00000000..7e9fcadf --- /dev/null +++ b/library/firebase-messaging/README.md @@ -0,0 +1,3 @@ +# :library:firebase-messaging module +## Dependency graph +![Dependency graph](../../docs/images/graphs/dep_graph_library_firebase_messaging.svg) diff --git a/library/firebase-messaging/build.gradle.kts b/library/firebase-messaging/build.gradle.kts new file mode 100644 index 00000000..daad9238 --- /dev/null +++ b/library/firebase-messaging/build.gradle.kts @@ -0,0 +1,48 @@ +plugins { + id("diary.android.library") + id("diary.kotlin.multiplatform.all") + id("diary.cocoapods") +} + +kotlin { + cocoapods { + ios.deploymentTarget = "18.0" + + noPodspec() + pod( + name = "FirebaseMessaging", + version = property("firebase.cocoapods.version") as String, + ) + } + + sourceSets { + commonMain { + dependencies { + api(project(":library:firebase-common")) + } + } + + androidMain { + dependencies { + implementation(project.dependencies.platform(libs.android.firebase.bom)) + implementation(libs.android.firebase.messaging) + } + } + + iosMain { + dependencies { + implementation(libs.kotlinx.coroutines.core) + } + } + + val nonSupportMain = create("nonSupportMain") + + nonSupportMain.dependsOn(commonMain.get()) + jvmMain.get().dependsOn(nonSupportMain) + wasmJsMain.get().dependsOn(nonSupportMain) + } +} + +android { + namespace = "${Build.NAMESPACE}.library.firebase.messaging" +} diff --git a/library/firebase-messaging/src/androidMain/kotlin/io/github/taetae98coding/diary/library/firebase/messaging/KFirebaseMessagingExt.android.kt b/library/firebase-messaging/src/androidMain/kotlin/io/github/taetae98coding/diary/library/firebase/messaging/KFirebaseMessagingExt.android.kt new file mode 100644 index 00000000..27942c24 --- /dev/null +++ b/library/firebase-messaging/src/androidMain/kotlin/io/github/taetae98coding/diary/library/firebase/messaging/KFirebaseMessagingExt.android.kt @@ -0,0 +1,6 @@ +package io.github.taetae98coding.diary.library.firebase.messaging + +import io.github.taetae98coding.diary.library.firebase.KFirebase + +public actual val KFirebase.messaging: KFirebaseMessaging + get() = KFirebaseMessagingImpl() diff --git a/library/firebase-messaging/src/androidMain/kotlin/io/github/taetae98coding/diary/library/firebase/messaging/KFirebaseMessagingImpl.kt b/library/firebase-messaging/src/androidMain/kotlin/io/github/taetae98coding/diary/library/firebase/messaging/KFirebaseMessagingImpl.kt new file mode 100644 index 00000000..b1ea0049 --- /dev/null +++ b/library/firebase-messaging/src/androidMain/kotlin/io/github/taetae98coding/diary/library/firebase/messaging/KFirebaseMessagingImpl.kt @@ -0,0 +1,11 @@ +package io.github.taetae98coding.diary.library.firebase.messaging + +import com.google.firebase.Firebase +import com.google.firebase.messaging.messaging +import kotlinx.coroutines.tasks.await + +internal class KFirebaseMessagingImpl : KFirebaseMessaging { + override suspend fun getToken(): String { + return Firebase.messaging.token.await() + } +} diff --git a/library/firebase-messaging/src/commonMain/kotlin/io/github/taetae98coding/diary/library/firebase/messaging/KFirebaseMessaging.kt b/library/firebase-messaging/src/commonMain/kotlin/io/github/taetae98coding/diary/library/firebase/messaging/KFirebaseMessaging.kt new file mode 100644 index 00000000..e879128d --- /dev/null +++ b/library/firebase-messaging/src/commonMain/kotlin/io/github/taetae98coding/diary/library/firebase/messaging/KFirebaseMessaging.kt @@ -0,0 +1,5 @@ +package io.github.taetae98coding.diary.library.firebase.messaging + +public interface KFirebaseMessaging { + public suspend fun getToken(): String +} diff --git a/library/firebase-messaging/src/commonMain/kotlin/io/github/taetae98coding/diary/library/firebase/messaging/KFirebaseMessagingExt.kt b/library/firebase-messaging/src/commonMain/kotlin/io/github/taetae98coding/diary/library/firebase/messaging/KFirebaseMessagingExt.kt new file mode 100644 index 00000000..35ed1fe6 --- /dev/null +++ b/library/firebase-messaging/src/commonMain/kotlin/io/github/taetae98coding/diary/library/firebase/messaging/KFirebaseMessagingExt.kt @@ -0,0 +1,5 @@ +package io.github.taetae98coding.diary.library.firebase.messaging + +import io.github.taetae98coding.diary.library.firebase.KFirebase + +public expect val KFirebase.messaging: KFirebaseMessaging diff --git a/library/firebase-messaging/src/iosMain/kotlin/io/github/taetae98coding/diary/library/firebase/messaging/KFirebaseMessagingExt.ios.kt b/library/firebase-messaging/src/iosMain/kotlin/io/github/taetae98coding/diary/library/firebase/messaging/KFirebaseMessagingExt.ios.kt new file mode 100644 index 00000000..27942c24 --- /dev/null +++ b/library/firebase-messaging/src/iosMain/kotlin/io/github/taetae98coding/diary/library/firebase/messaging/KFirebaseMessagingExt.ios.kt @@ -0,0 +1,6 @@ +package io.github.taetae98coding.diary.library.firebase.messaging + +import io.github.taetae98coding.diary.library.firebase.KFirebase + +public actual val KFirebase.messaging: KFirebaseMessaging + get() = KFirebaseMessagingImpl() diff --git a/library/firebase-messaging/src/iosMain/kotlin/io/github/taetae98coding/diary/library/firebase/messaging/KFirebaseMessagingImpl.kt b/library/firebase-messaging/src/iosMain/kotlin/io/github/taetae98coding/diary/library/firebase/messaging/KFirebaseMessagingImpl.kt new file mode 100644 index 00000000..d9c365fa --- /dev/null +++ b/library/firebase-messaging/src/iosMain/kotlin/io/github/taetae98coding/diary/library/firebase/messaging/KFirebaseMessagingImpl.kt @@ -0,0 +1,24 @@ +package io.github.taetae98coding.diary.library.firebase.messaging + +import cocoapods.FirebaseMessaging.FIRMessaging +import kotlin.coroutines.resume +import kotlin.coroutines.resumeWithException +import kotlinx.cinterop.ExperimentalForeignApi +import kotlinx.coroutines.suspendCancellableCoroutine + +@OptIn(ExperimentalForeignApi::class) +internal class KFirebaseMessagingImpl : KFirebaseMessaging { + override suspend fun getToken(): String { + return suspendCancellableCoroutine { continuation -> + FIRMessaging.messaging().tokenWithCompletion { token, error -> + if (error != null) { + continuation.resumeWithException(Exception(error.toString())) + } else if (token.isNullOrBlank()) { + continuation.resumeWithException(Exception("token is null or blank")) + } else { + continuation.resume(token) + } + } + } + } +} diff --git a/library/firebase-messaging/src/nonSupportMain/kotlin/io/github/taetae98coding/diary/library/firebase/messaging/KFirebaseMessagingExt.nonSupport.kt b/library/firebase-messaging/src/nonSupportMain/kotlin/io/github/taetae98coding/diary/library/firebase/messaging/KFirebaseMessagingExt.nonSupport.kt new file mode 100644 index 00000000..27942c24 --- /dev/null +++ b/library/firebase-messaging/src/nonSupportMain/kotlin/io/github/taetae98coding/diary/library/firebase/messaging/KFirebaseMessagingExt.nonSupport.kt @@ -0,0 +1,6 @@ +package io.github.taetae98coding.diary.library.firebase.messaging + +import io.github.taetae98coding.diary.library.firebase.KFirebase + +public actual val KFirebase.messaging: KFirebaseMessaging + get() = KFirebaseMessagingImpl() diff --git a/library/firebase-messaging/src/nonSupportMain/kotlin/io/github/taetae98coding/diary/library/firebase/messaging/KFirebaseMessagingImpl.kt b/library/firebase-messaging/src/nonSupportMain/kotlin/io/github/taetae98coding/diary/library/firebase/messaging/KFirebaseMessagingImpl.kt new file mode 100644 index 00000000..6f25db4f --- /dev/null +++ b/library/firebase-messaging/src/nonSupportMain/kotlin/io/github/taetae98coding/diary/library/firebase/messaging/KFirebaseMessagingImpl.kt @@ -0,0 +1,7 @@ +package io.github.taetae98coding.diary.library.firebase.messaging + +internal class KFirebaseMessagingImpl : KFirebaseMessaging { + override suspend fun getToken(): String { + error("Not Support") + } +} diff --git a/server/app/build.gradle.kts b/server/app/build.gradle.kts index 9f2cc389..7876ac9e 100644 --- a/server/app/build.gradle.kts +++ b/server/app/build.gradle.kts @@ -13,13 +13,16 @@ dependencies { implementation(project(":server:data:account")) implementation(project(":server:data:memo")) + implementation(project(":server:data:fcm")) implementation(project(":server:domain:account")) implementation(project(":server:domain:memo")) + implementation(project(":server:domain:fcm")) implementation(project(":server:feature:home")) implementation(project(":server:feature:account")) implementation(project(":server:feature:memo")) + implementation(project(":server:feature:fcm")) implementation(project(":common:model")) @@ -32,6 +35,8 @@ dependencies { implementation(platform(libs.exposed.bom)) implementation(libs.exposed.jdbc) implementation(libs.mysql.connector) + + implementation(libs.server.firebase.admin) } dependencyGuard { diff --git a/server/app/dependencies/runtimeClasspath.txt b/server/app/dependencies/runtimeClasspath.txt index 2bb54f1e..b0f2bb2d 100644 --- a/server/app/dependencies/runtimeClasspath.txt +++ b/server/app/dependencies/runtimeClasspath.txt @@ -1,6 +1,5 @@ ch.qos.logback:logback-classic:1.5.12 ch.qos.logback:logback-core:1.5.12 -ch.randelshofer:fastdoubleparser:0.8.0 co.touchlab:stately-concurrency-jvm:2.1.0 co.touchlab:stately-concurrency:2.1.0 co.touchlab:stately-concurrent-collections-jvm:2.1.0 @@ -9,18 +8,77 @@ co.touchlab:stately-strict-jvm:2.1.0 co.touchlab:stately-strict:2.1.0 com.auth0:java-jwt:4.4.0 com.auth0:jwks-rsa:0.22.1 -com.fasterxml.jackson.core:jackson-annotations:2.15.0 -com.fasterxml.jackson.core:jackson-core:2.15.0 -com.fasterxml.jackson.core:jackson-databind:2.15.0 -com.fasterxml.jackson:jackson-bom:2.15.0 +com.fasterxml.jackson.core:jackson-annotations:2.17.2 +com.fasterxml.jackson.core:jackson-core:2.17.2 +com.fasterxml.jackson.core:jackson-databind:2.17.2 +com.fasterxml.jackson:jackson-bom:2.17.2 +com.google.android:annotations:4.1.1.4 +com.google.api-client:google-api-client-gson:2.7.0 +com.google.api-client:google-api-client:2.7.0 +com.google.api.grpc:gapic-google-cloud-storage-v2:2.43.1-beta +com.google.api.grpc:grpc-google-cloud-storage-v2:2.43.1-beta +com.google.api.grpc:proto-google-cloud-firestore-v1:3.26.5 +com.google.api.grpc:proto-google-cloud-monitoring-v3:3.31.0 +com.google.api.grpc:proto-google-cloud-storage-v2:2.43.1-beta +com.google.api.grpc:proto-google-common-protos:2.45.1 +com.google.api.grpc:proto-google-iam-v1:1.40.1 +com.google.api:api-common:2.37.1 +com.google.api:gax-grpc:2.54.1 +com.google.api:gax-httpjson:2.54.1 +com.google.api:gax:2.54.1 +com.google.apis:google-api-services-storage:v1-rev20240819-2.0.0 +com.google.auth:google-auth-library-credentials:1.27.0 +com.google.auth:google-auth-library-oauth2-http:1.27.0 +com.google.auto.value:auto-value-annotations:1.11.0 +com.google.cloud.opentelemetry:detector-resources-support:0.32.0 +com.google.cloud.opentelemetry:exporter-metrics:0.31.0 +com.google.cloud.opentelemetry:shared-resourcemapping:0.32.0 +com.google.cloud:google-cloud-core-grpc:2.44.1 +com.google.cloud:google-cloud-core-http:2.44.1 +com.google.cloud:google-cloud-core:2.44.1 +com.google.cloud:google-cloud-firestore:3.26.5 +com.google.cloud:google-cloud-monitoring:3.31.0 +com.google.cloud:google-cloud-storage:2.43.1 +com.google.cloud:libraries-bom:26.26.0 +com.google.cloud:proto-google-cloud-firestore-bundle-v1:3.26.5 com.google.code.findbugs:jsr305:3.0.2 -com.google.errorprone:error_prone_annotations:2.18.0 -com.google.guava:failureaccess:1.0.1 -com.google.guava:guava-parent:32.1.1-jre -com.google.guava:guava:32.1.1-jre -com.google.protobuf:protobuf-java:3.21.9 +com.google.code.gson:gson:2.11.0 +com.google.errorprone:error_prone_annotations:2.32.0 +com.google.firebase:firebase-admin:9.4.1 +com.google.guava:failureaccess:1.0.2 +com.google.guava:guava:33.3.0-jre +com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava +com.google.http-client:google-http-client-apache-v2:1.45.0 +com.google.http-client:google-http-client-appengine:1.45.0 +com.google.http-client:google-http-client-gson:1.45.0 +com.google.http-client:google-http-client-jackson2:1.45.0 +com.google.http-client:google-http-client:1.45.0 +com.google.j2objc:j2objc-annotations:3.0.0 +com.google.oauth-client:google-oauth-client:1.36.0 +com.google.protobuf:protobuf-java-util:3.25.5 +com.google.protobuf:protobuf-java:3.25.5 +com.google.re2j:re2j:1.7 com.mysql:mysql-connector-j:8.0.33 com.typesafe:config:1.4.3 +commons-codec:commons-codec:1.17.1 +commons-logging:commons-logging:1.2 +io.grpc:grpc-alts:1.68.0 +io.grpc:grpc-api:1.68.0 +io.grpc:grpc-auth:1.68.0 +io.grpc:grpc-context:1.68.0 +io.grpc:grpc-core:1.68.0 +io.grpc:grpc-googleapis:1.68.0 +io.grpc:grpc-grpclb:1.68.0 +io.grpc:grpc-inprocess:1.68.0 +io.grpc:grpc-netty-shaded:1.68.0 +io.grpc:grpc-opentelemetry:1.68.0 +io.grpc:grpc-protobuf-lite:1.68.0 +io.grpc:grpc-protobuf:1.68.0 +io.grpc:grpc-rls:1.68.0 +io.grpc:grpc-services:1.68.0 +io.grpc:grpc-stub:1.68.0 +io.grpc:grpc-util:1.68.0 +io.grpc:grpc-xds:1.68.0 io.insert-koin:koin-annotations-bom:2.0.0-Beta1 io.insert-koin:koin-annotations-jvm:2.0.0-Beta1 io.insert-koin:koin-annotations:2.0.0-Beta1 @@ -84,10 +142,38 @@ io.netty:netty-transport-native-epoll:4.1.114.Final io.netty:netty-transport-native-kqueue:4.1.114.Final io.netty:netty-transport-native-unix-common:4.1.114.Final io.netty:netty-transport:4.1.114.Final +io.opencensus:opencensus-api:0.31.1 +io.opencensus:opencensus-contrib-http-util:0.31.1 +io.opencensus:opencensus-proto:0.2.0 +io.opentelemetry.contrib:opentelemetry-gcp-resources:1.37.0-alpha +io.opentelemetry.instrumentation:opentelemetry-grpc-1.6:2.1.0-alpha +io.opentelemetry.instrumentation:opentelemetry-instrumentation-api-incubator:2.1.0-alpha +io.opentelemetry.instrumentation:opentelemetry-instrumentation-api:2.1.0 +io.opentelemetry.semconv:opentelemetry-semconv:1.25.0-alpha +io.opentelemetry:opentelemetry-api-incubator:1.42.1-alpha +io.opentelemetry:opentelemetry-api:1.42.1 +io.opentelemetry:opentelemetry-bom:1.33.0 +io.opentelemetry:opentelemetry-context:1.42.1 +io.opentelemetry:opentelemetry-extension-incubator:1.35.0-alpha +io.opentelemetry:opentelemetry-sdk-common:1.42.1 +io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi:1.42.1 +io.opentelemetry:opentelemetry-sdk-logs:1.42.1 +io.opentelemetry:opentelemetry-sdk-metrics:1.42.1 +io.opentelemetry:opentelemetry-sdk-trace:1.42.1 +io.opentelemetry:opentelemetry-sdk:1.42.1 +io.perfmark:perfmark-api:0.27.0 +javax.annotation:javax.annotation-api:1.3.2 mysql:mysql-connector-java:8.0.33 net.mamoe.yamlkt:yamlkt-jvm:0.13.0 net.mamoe.yamlkt:yamlkt:0.13.0 -org.checkerframework:checker-qual:3.33.0 +org.apache.httpcomponents.client5:httpclient5:5.3.1 +org.apache.httpcomponents.core5:httpcore5-h2:5.2.4 +org.apache.httpcomponents.core5:httpcore5:5.2.4 +org.apache.httpcomponents:httpclient:4.5.14 +org.apache.httpcomponents:httpcore:4.4.16 +org.checkerframework:checker-qual:3.47.0 +org.codehaus.mojo:animal-sniffer-annotations:1.24 +org.conscrypt:conscrypt-openjdk-uber:2.5.2 org.eclipse.jetty.alpn:alpn-api:1.1.3.v20160715 org.fusesource.jansi:jansi:2.4.1 org.jetbrains.exposed:exposed-bom:0.56.0 @@ -117,3 +203,4 @@ org.jetbrains.kotlinx:kotlinx-serialization-json-jvm:1.7.3 org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3 org.jetbrains:annotations:23.0.0 org.slf4j:slf4j-api:2.0.16 +org.threeten:threetenbp:1.7.0 diff --git a/server/app/src/main/kotlin/io/github/taetae98coding/diary/Application.kt b/server/app/src/main/kotlin/io/github/taetae98coding/diary/Application.kt index 3a4e228e..7d05b76c 100644 --- a/server/app/src/main/kotlin/io/github/taetae98coding/diary/Application.kt +++ b/server/app/src/main/kotlin/io/github/taetae98coding/diary/Application.kt @@ -4,6 +4,7 @@ import io.github.taetae98coding.diary.plugin.installAuth import io.github.taetae98coding.diary.plugin.installCORS import io.github.taetae98coding.diary.plugin.installContentNegotiation import io.github.taetae98coding.diary.plugin.installDatabase +import io.github.taetae98coding.diary.plugin.installFirebase import io.github.taetae98coding.diary.plugin.installKoin import io.github.taetae98coding.diary.plugin.installRouting import io.ktor.server.application.Application @@ -19,5 +20,6 @@ public fun Application.module() { installKoin() installAuth() installContentNegotiation() + installFirebase() installRouting() } diff --git a/server/app/src/main/kotlin/io/github/taetae98coding/diary/plugin/DatabasePlugin.kt b/server/app/src/main/kotlin/io/github/taetae98coding/diary/plugin/DatabasePlugin.kt index 56fa0def..1dff6c0b 100644 --- a/server/app/src/main/kotlin/io/github/taetae98coding/diary/plugin/DatabasePlugin.kt +++ b/server/app/src/main/kotlin/io/github/taetae98coding/diary/plugin/DatabasePlugin.kt @@ -1,6 +1,7 @@ package io.github.taetae98coding.diary.plugin import io.github.taetae98coding.diary.core.database.AccountTable +import io.github.taetae98coding.diary.core.database.FCMTokenTable import io.github.taetae98coding.diary.core.database.MemoTable import io.ktor.server.application.Application import org.jetbrains.exposed.sql.Database @@ -17,6 +18,6 @@ internal fun Application.installDatabase() { ) transaction(database) { - SchemaUtils.createMissingTablesAndColumns(AccountTable, MemoTable) + SchemaUtils.createMissingTablesAndColumns(AccountTable, MemoTable, FCMTokenTable) } } diff --git a/server/app/src/main/kotlin/io/github/taetae98coding/diary/plugin/FirebasePlugin.kt b/server/app/src/main/kotlin/io/github/taetae98coding/diary/plugin/FirebasePlugin.kt new file mode 100644 index 00000000..049c926d --- /dev/null +++ b/server/app/src/main/kotlin/io/github/taetae98coding/diary/plugin/FirebasePlugin.kt @@ -0,0 +1,19 @@ +package io.github.taetae98coding.diary.plugin + +import com.google.auth.oauth2.GoogleCredentials +import com.google.firebase.FirebaseApp +import com.google.firebase.FirebaseOptions +import io.ktor.server.application.Application +import java.io.FileInputStream + +internal fun Application.installFirebase() { + val keyPath = environment.config.property("firebase.key").getString() + val key = FileInputStream(keyPath) + val options = + FirebaseOptions + .builder() + .setCredentials(GoogleCredentials.fromStream(key)) + .build() + + FirebaseApp.initializeApp(options) +} diff --git a/server/app/src/main/kotlin/io/github/taetae98coding/diary/plugin/KoinPlugin.kt b/server/app/src/main/kotlin/io/github/taetae98coding/diary/plugin/KoinPlugin.kt index 24f8a0d5..a2ef0776 100644 --- a/server/app/src/main/kotlin/io/github/taetae98coding/diary/plugin/KoinPlugin.kt +++ b/server/app/src/main/kotlin/io/github/taetae98coding/diary/plugin/KoinPlugin.kt @@ -1,8 +1,10 @@ package io.github.taetae98coding.diary.plugin import io.github.taetae98coding.diary.data.account.AccountDataModule +import io.github.taetae98coding.diary.data.fcm.FCMDataModule import io.github.taetae98coding.diary.data.memo.MemoDataModule import io.github.taetae98coding.diary.domain.account.AccountDomainModule +import io.github.taetae98coding.diary.domain.fcm.FCMDomainModule import io.github.taetae98coding.diary.domain.memo.MemoDomainModule import io.ktor.server.application.Application import io.ktor.server.application.install @@ -14,8 +16,10 @@ internal fun Application.installKoin() { modules( AccountDataModule().module, MemoDataModule().module, + FCMDataModule().module, AccountDomainModule().module, MemoDomainModule().module, + FCMDomainModule().module, ) } } diff --git a/server/app/src/main/kotlin/io/github/taetae98coding/diary/plugin/RoutingPlugin.kt b/server/app/src/main/kotlin/io/github/taetae98coding/diary/plugin/RoutingPlugin.kt index ad0d347a..30bfa969 100644 --- a/server/app/src/main/kotlin/io/github/taetae98coding/diary/plugin/RoutingPlugin.kt +++ b/server/app/src/main/kotlin/io/github/taetae98coding/diary/plugin/RoutingPlugin.kt @@ -1,6 +1,7 @@ package io.github.taetae98coding.diary.plugin import io.github.taetae98coding.diary.feature.account.accountRouting +import io.github.taetae98coding.diary.feature.fcm.fcmRouting import io.github.taetae98coding.diary.feature.home.homeRouting import io.github.taetae98coding.diary.feature.memo.memoRouting import io.ktor.server.application.Application @@ -11,5 +12,6 @@ internal fun Application.installRouting() { homeRouting() accountRouting() memoRouting() + fcmRouting() } } diff --git a/server/app/src/main/resources/application.yaml b/server/app/src/main/resources/application.yaml index af5854df..a23c4743 100644 --- a/server/app/src/main/resources/application.yaml +++ b/server/app/src/main/resources/application.yaml @@ -8,4 +8,7 @@ ktor: database: url: $DIARY_DATABASE_URL user: $DIARY_DATABASE_USER - password: $DIARY_DATABASE_PASSWORD \ No newline at end of file + password: $DIARY_DATABASE_PASSWORD + +firebase: + key: $DIARY_FIREBASE_ADMIN_KEY \ No newline at end of file diff --git a/server/core/database/src/main/kotlin/io/github/taetae98coding/diary/core/database/FCMTokenTable.kt b/server/core/database/src/main/kotlin/io/github/taetae98coding/diary/core/database/FCMTokenTable.kt new file mode 100644 index 00000000..77d9fb79 --- /dev/null +++ b/server/core/database/src/main/kotlin/io/github/taetae98coding/diary/core/database/FCMTokenTable.kt @@ -0,0 +1,41 @@ +package io.github.taetae98coding.diary.core.database + +import kotlinx.datetime.Clock +import org.jetbrains.exposed.sql.ReferenceOption +import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq +import org.jetbrains.exposed.sql.Table +import org.jetbrains.exposed.sql.deleteWhere +import org.jetbrains.exposed.sql.kotlin.datetime.timestamp +import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction +import org.jetbrains.exposed.sql.upsert + +public data object FCMTokenTable : Table(name = "FCMToken") { + private val TOKEN = varchar("token", 255).default("") + private val OWNER = + reference( + name = "owner", + refColumn = AccountTable.UID, + onDelete = ReferenceOption.CASCADE, + onUpdate = ReferenceOption.CASCADE, + ).nullable() + + private val UPDATE_AT = timestamp("updateAt").default(Clock.System.now()) + + override val primaryKey: PrimaryKey = PrimaryKey(TOKEN) + + public suspend fun upsert(token: String, owner: String) { + newSuspendedTransaction { + upsert { + it[TOKEN] = token + it[OWNER] = owner + it[UPDATE_AT] = Clock.System.now() + } + } + } + + public suspend fun delete(token: String) { + newSuspendedTransaction { + deleteWhere { TOKEN eq token } + } + } +} diff --git a/server/data/fcm/README.md b/server/data/fcm/README.md new file mode 100644 index 00000000..bb0a78b0 --- /dev/null +++ b/server/data/fcm/README.md @@ -0,0 +1,3 @@ +# :server:data:fcm module +## Dependency graph +![Dependency graph](../../../docs/images/graphs/dep_graph_server_data_fcm.svg) diff --git a/server/data/fcm/build.gradle.kts b/server/data/fcm/build.gradle.kts new file mode 100644 index 00000000..d4b84fbe --- /dev/null +++ b/server/data/fcm/build.gradle.kts @@ -0,0 +1,11 @@ +plugins { + id("diary.server.data") +} + +dependencies { + implementation(project(":server:core:database")) + implementation(project(":server:domain:fcm")) + + implementation(platform(libs.exposed.bom)) + implementation(libs.exposed.core) +} diff --git a/server/data/fcm/src/main/kotlin/io/github/taetae98coding/diary/data/fcm/FCMDataModule.kt b/server/data/fcm/src/main/kotlin/io/github/taetae98coding/diary/data/fcm/FCMDataModule.kt new file mode 100644 index 00000000..e9adca4d --- /dev/null +++ b/server/data/fcm/src/main/kotlin/io/github/taetae98coding/diary/data/fcm/FCMDataModule.kt @@ -0,0 +1,8 @@ +package io.github.taetae98coding.diary.data.fcm + +import org.koin.core.annotation.ComponentScan +import org.koin.core.annotation.Module + +@Module +@ComponentScan +public class FCMDataModule diff --git a/server/data/fcm/src/main/kotlin/io/github/taetae98coding/diary/data/fcm/repository/FCMRepositoryImpl.kt b/server/data/fcm/src/main/kotlin/io/github/taetae98coding/diary/data/fcm/repository/FCMRepositoryImpl.kt new file mode 100644 index 00000000..9b4433fa --- /dev/null +++ b/server/data/fcm/src/main/kotlin/io/github/taetae98coding/diary/data/fcm/repository/FCMRepositoryImpl.kt @@ -0,0 +1,16 @@ +package io.github.taetae98coding.diary.data.fcm.repository + +import io.github.taetae98coding.diary.core.database.FCMTokenTable +import io.github.taetae98coding.diary.domain.fcm.repository.FCMRepository +import org.koin.core.annotation.Factory + +@Factory +internal class FCMRepositoryImpl : FCMRepository { + override suspend fun upsert(token: String, owner: String) { + FCMTokenTable.upsert(token, owner) + } + + override suspend fun delete(token: String) { + FCMTokenTable.delete(token) + } +} diff --git a/server/domain/fcm/README.md b/server/domain/fcm/README.md new file mode 100644 index 00000000..b5897e01 --- /dev/null +++ b/server/domain/fcm/README.md @@ -0,0 +1,3 @@ +# :server:domain:fcm module +## Dependency graph +![Dependency graph](../../../docs/images/graphs/dep_graph_server_domain_fcm.svg) diff --git a/server/domain/fcm/build.gradle.kts b/server/domain/fcm/build.gradle.kts new file mode 100644 index 00000000..e10cade2 --- /dev/null +++ b/server/domain/fcm/build.gradle.kts @@ -0,0 +1,3 @@ +plugins { + id("diary.server.domain") +} diff --git a/server/domain/fcm/src/main/kotlin/io/github/taetae98coding/diary/domain/fcm/FCMDomainModule.kt b/server/domain/fcm/src/main/kotlin/io/github/taetae98coding/diary/domain/fcm/FCMDomainModule.kt new file mode 100644 index 00000000..a2d83583 --- /dev/null +++ b/server/domain/fcm/src/main/kotlin/io/github/taetae98coding/diary/domain/fcm/FCMDomainModule.kt @@ -0,0 +1,8 @@ +package io.github.taetae98coding.diary.domain.fcm + +import org.koin.core.annotation.ComponentScan +import org.koin.core.annotation.Module + +@Module +@ComponentScan +public class FCMDomainModule diff --git a/server/domain/fcm/src/main/kotlin/io/github/taetae98coding/diary/domain/fcm/repository/FCMRepository.kt b/server/domain/fcm/src/main/kotlin/io/github/taetae98coding/diary/domain/fcm/repository/FCMRepository.kt new file mode 100644 index 00000000..566d8863 --- /dev/null +++ b/server/domain/fcm/src/main/kotlin/io/github/taetae98coding/diary/domain/fcm/repository/FCMRepository.kt @@ -0,0 +1,7 @@ +package io.github.taetae98coding.diary.domain.fcm.repository + +public interface FCMRepository { + public suspend fun upsert(token: String, owner: String) + + public suspend fun delete(token: String) +} diff --git a/server/domain/fcm/src/main/kotlin/io/github/taetae98coding/diary/domain/fcm/usecase/DeleteFCMTokenUseCase.kt b/server/domain/fcm/src/main/kotlin/io/github/taetae98coding/diary/domain/fcm/usecase/DeleteFCMTokenUseCase.kt new file mode 100644 index 00000000..90f77c96 --- /dev/null +++ b/server/domain/fcm/src/main/kotlin/io/github/taetae98coding/diary/domain/fcm/usecase/DeleteFCMTokenUseCase.kt @@ -0,0 +1,11 @@ +package io.github.taetae98coding.diary.domain.fcm.usecase + +import io.github.taetae98coding.diary.domain.fcm.repository.FCMRepository +import org.koin.core.annotation.Factory + +@Factory +public class DeleteFCMTokenUseCase internal constructor( + private val repository: FCMRepository, +) { + public suspend operator fun invoke(token: String): Result = runCatching { repository.delete(token) } +} diff --git a/server/domain/fcm/src/main/kotlin/io/github/taetae98coding/diary/domain/fcm/usecase/UpsertFCMTokenUseCase.kt b/server/domain/fcm/src/main/kotlin/io/github/taetae98coding/diary/domain/fcm/usecase/UpsertFCMTokenUseCase.kt new file mode 100644 index 00000000..4589d1f7 --- /dev/null +++ b/server/domain/fcm/src/main/kotlin/io/github/taetae98coding/diary/domain/fcm/usecase/UpsertFCMTokenUseCase.kt @@ -0,0 +1,11 @@ +package io.github.taetae98coding.diary.domain.fcm.usecase + +import io.github.taetae98coding.diary.domain.fcm.repository.FCMRepository +import org.koin.core.annotation.Factory + +@Factory +public class UpsertFCMTokenUseCase internal constructor( + private val repository: FCMRepository, +) { + public suspend operator fun invoke(token: String, owner: String): Result = runCatching { repository.upsert(token, owner) } +} diff --git a/server/feature/fcm/README.md b/server/feature/fcm/README.md new file mode 100644 index 00000000..a32947cf --- /dev/null +++ b/server/feature/fcm/README.md @@ -0,0 +1,3 @@ +# :server:feature:fcm module +## Dependency graph +![Dependency graph](../../../docs/images/graphs/dep_graph_server_feature_fcm.svg) diff --git a/server/feature/fcm/build.gradle.kts b/server/feature/fcm/build.gradle.kts new file mode 100644 index 00000000..9dacd93b --- /dev/null +++ b/server/feature/fcm/build.gradle.kts @@ -0,0 +1,7 @@ +plugins { + id("diary.server.feature") +} + +dependencies { + implementation(project(":server:domain:fcm")) +} diff --git a/server/feature/fcm/src/main/kotlin/io/github/taetae98coding/diary/feature/fcm/FCMRouting.kt b/server/feature/fcm/src/main/kotlin/io/github/taetae98coding/diary/feature/fcm/FCMRouting.kt new file mode 100644 index 00000000..399fa7f1 --- /dev/null +++ b/server/feature/fcm/src/main/kotlin/io/github/taetae98coding/diary/feature/fcm/FCMRouting.kt @@ -0,0 +1,46 @@ +package io.github.taetae98coding.diary.feature.fcm + +import io.github.taetae98coding.diary.common.model.request.fcm.DeleteFCMRequest +import io.github.taetae98coding.diary.common.model.request.fcm.UpsertFCMRequest +import io.github.taetae98coding.diary.common.model.response.DiaryResponse +import io.github.taetae98coding.diary.domain.fcm.usecase.DeleteFCMTokenUseCase +import io.github.taetae98coding.diary.domain.fcm.usecase.UpsertFCMTokenUseCase +import io.ktor.http.HttpStatusCode +import io.ktor.server.auth.authenticate +import io.ktor.server.auth.jwt.JWTPrincipal +import io.ktor.server.auth.principal +import io.ktor.server.response.respond +import io.ktor.server.routing.Route +import io.ktor.server.routing.post +import io.ktor.server.routing.route +import org.koin.ktor.plugin.scope + +public fun Route.fcmRouting() { + route("/fcm") { + post("/delete") { request -> + val useCase = call.scope.get() + + useCase(token = request.token) + .onSuccess { call.respond(DiaryResponse.Success) } + } + + authenticate("account") { + post("/upsert") { request -> + val principal = call.principal() + if (principal == null) { + call.respond(HttpStatusCode.Unauthorized, DiaryResponse.Unauthorized) + return@post + } + + val useCase = call.scope.get() + + useCase( + token = request.token, + owner = principal.payload.getClaim("uid").asString(), + ).onSuccess { + call.respond(DiaryResponse.Success) + } + } + } + } +} diff --git a/server/feature/memo/src/main/kotlin/io/github/taetae98coding/diary/feature/memo/MemoRouting.kt b/server/feature/memo/src/main/kotlin/io/github/taetae98coding/diary/feature/memo/MemoRouting.kt index 3e5bd5b5..3ed30933 100644 --- a/server/feature/memo/src/main/kotlin/io/github/taetae98coding/diary/feature/memo/MemoRouting.kt +++ b/server/feature/memo/src/main/kotlin/io/github/taetae98coding/diary/feature/memo/MemoRouting.kt @@ -50,15 +50,6 @@ public fun Route.memoRouting() { .onSuccess { call.respond(DiaryResponse.success(it.map(Memo::toEntity))) } } } - - post>("/migrate") { request -> - val useCase = call.scope.get() - val memoList = request.map(MemoEntity::toMemo) - - useCase(memoList) - .onSuccess { call.respond(DiaryResponse.Success) } - .onFailure { call.respond(HttpStatusCode.InternalServerError, it.toString()) } - } } } diff --git a/settings.gradle.kts b/settings.gradle.kts index 146c4db6..0031627f 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -65,12 +65,16 @@ include(":app:data:account") include(":app:data:holiday") include(":app:data:backup") include(":app:data:fetch") +include(":app:data:fcm") +include(":app:data:credential") include(":app:domain:memo") include(":app:domain:account") include(":app:domain:holiday") include(":app:domain:backup") include(":app:domain:fetch") +include(":app:domain:fcm") +include(":app:domain:credential") include(":app:feature:memo") include(":app:feature:calendar") @@ -82,14 +86,17 @@ include(":server:core:model") include(":server:data:account") include(":server:data:memo") +include(":server:data:fcm") include(":server:domain:account") include(":server:domain:memo") +include(":server:domain:fcm") include(":server:app") include(":server:feature:home") include(":server:feature:account") include(":server:feature:memo") +include(":server:feature:fcm") include(":common:exception") include(":common:model") @@ -103,3 +110,6 @@ include(":library:kotlin") include(":library:navigation") include(":library:room") include(":library:shimmer-m3") + +include(":library:firebase-common") +include(":library:firebase-messaging")