From c8914f2e9f6f3f727ea0f0ddda1c02669fbb1035 Mon Sep 17 00:00:00 2001 From: changs97 Date: Tue, 12 Aug 2025 01:44:30 +0900 Subject: [PATCH 01/27] =?UTF-8?q?core:=20firebase=20=EC=9D=98=EC=A1=B4?= =?UTF-8?q?=EC=84=B1=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 6 ++++++ build.gradle.kts | 1 + gradle/libs.versions.toml | 11 +++++++++++ 3 files changed, 18 insertions(+) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 6b0007d..ec2ffb0 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -2,6 +2,7 @@ import com.android.build.gradle.internal.cxx.configure.gradleLocalProperties plugins { id("runcombi.android.application") + alias(libs.plugins.google.services) } android { @@ -63,6 +64,11 @@ dependencies { implementation(libs.hilt.android) implementation(libs.v2.user) + // Firebase + implementation(platform(libs.firebase.bom)) + implementation(libs.firebase.analytics) + implementation(libs.firebase.crashlytics) + implementation(project(":feature:main")) implementation(project(":feature:login")) implementation(project(":feature:history")) diff --git a/build.gradle.kts b/build.gradle.kts index e2fedcc..8f918ba 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -9,4 +9,5 @@ plugins { alias(libs.plugins.kotlin.plugin.serialization) apply false alias(libs.plugins.android.library) apply false alias(libs.plugins.jetbrains.kotlin.jvm) apply false + alias(libs.plugins.google.services) apply false } \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c98ed89..ec3ac16 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -51,6 +51,9 @@ lottie-compose = "6.5.0" # Markdown compose-markdown = "0.3.6" +# Firebase +firebase-bom = "32.7.4" + # Test junit = "4.13.2" junitVersion = "1.2.1" @@ -137,6 +140,11 @@ v2-user = { module = "com.kakao.sdk:v2-user", version.ref = "v2User" } maps-compose = { module = "com.google.maps.android:maps-compose", version.ref = "mapsCompose" } play-services-location = { group = "com.google.android.gms", name = "play-services-location", version.ref = "playServicesLocation" } +# Firebase +firebase-bom = { group = "com.google.firebase", name = "firebase-bom", version.ref = "firebase-bom" } +firebase-analytics = { group = "com.google.firebase", name = "firebase-analytics-ktx" } +firebase-crashlytics = { group = "com.google.firebase", name = "firebase-crashlytics-ktx" } + [plugins] # Android android-application = { id = "com.android.application", version.ref = "agp" } @@ -155,6 +163,9 @@ kotlin-ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } # Hilt hilt-android = { id = "com.google.dagger.hilt.android", version.ref = "hilt" } +# Firebase +google-services = { id = "com.google.gms.google-services", version = "4.4.3" } + [bundles] coroutines = ["coroutines-core", "coroutines-android"] test = ["junit","coroutines-test","kotlin-test"] From 7527c4c44f34c6bdd8ce91a2e5a46d1b69bd15fe Mon Sep 17 00:00:00 2001 From: changs97 Date: Tue, 12 Aug 2025 01:59:31 +0900 Subject: [PATCH 02/27] =?UTF-8?q?feat:=20ci/cd=20=EC=8A=A4=ED=81=AC?= =?UTF-8?q?=EB=A6=BD=ED=8A=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/android.yml | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index f40df71..2f63006 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -1,8 +1,6 @@ name: Android CI on: - push: - branches: [ "master" ] pull_request: branches: [ "master" ] @@ -51,12 +49,6 @@ jobs: run: | echo $KEYSTORE_BASE64 | base64 -d > ./app/runcombi-keystore.jks - - name: Build with Gradle - run: ./gradlew build - - - name: Build Release APK - run: ./gradlew assembleRelease - - name: Extract App Version id: app_version run: | @@ -64,6 +56,13 @@ jobs: VERSION_CODE=$(grep 'versionCode' app/build.gradle.kts | sed 's/.*versionCode = \([0-9]*\)/\1/') echo "version_name=$VERSION_NAME" >> $GITHUB_OUTPUT echo "version_code=$VERSION_CODE" >> $GITHUB_OUTPUT + echo "Extracted version: $VERSION_NAME ($VERSION_CODE)" + + - name: Build with Gradle + run: ./gradlew build + + - name: Build Release APK + run: ./gradlew assembleRelease - name: Upload Release Build to Artifacts uses: actions/upload-artifact@v4 @@ -124,7 +123,13 @@ jobs: SLACK_TITLE: 'RunCombi Android PR Check Success ✅' MSG_MINIMAL: true SLACK_USERNAME: RunCombi Android - SLACK_MESSAGE: 'RunCombi Android PR 체크 성공 🎉 (v${{ steps.app_version.outputs.version_name }} - ${{ steps.app_version.outputs.version_code }})%0A%0A**PR 제목:** ${{ github.event.pull_request.title }}%0A**PR 설명:** ${{ github.event.pull_request.body }}%0A**작성자:** @${{ github.event.pull_request.user.login }}%0A**브랜치:** ${{ github.event.pull_request.head.ref }} → ${{ github.event.pull_request.base.ref }}' + SLACK_MESSAGE: | + RunCombi Android PR 체크 성공 🎉 + Version: v${{ steps.app_version.outputs.version_name }} (${{ steps.app_version.outputs.version_code }}) + + PR 제목: ${{ github.event.pull_request.title }} + 작성자: @${{ github.event.pull_request.user.login }} + 브랜치: ${{ github.event.pull_request.head.ref }} → ${{ github.event.pull_request.base.ref }} - name: If Fail, Send notification on Slack if: ${{failure()}} @@ -135,4 +140,10 @@ jobs: SLACK_TITLE: 'RunCombi Android PR Check Failed ❌' MSG_MINIMAL: true SLACK_USERNAME: RunCombi Android - SLACK_MESSAGE: 'RunCombi Android PR 체크 실패 - 확인이 필요합니다 🔍 (v${{ steps.app_version.outputs.version_name }} - ${{ steps.app_version.outputs.version_code }})%0A%0A**PR 제목:** ${{ github.event.pull_request.title }}%0A**PR 설명:** ${{ github.event.pull_request.body }}%0A**작성자:** @${{ github.event.pull_request.user.login }}%0A**브랜치:** ${{ github.event.pull_request.head.ref }} → ${{ github.event.pull_request.base.ref }}' + SLACK_MESSAGE: | + RunCombi Android PR 체크 실패 - 확인이 필요합니다 🔍 + Version: v${{ steps.app_version.outputs.version_name }} (${{ steps.app_version.outputs.version_code }}) + + PR 제목: ${{ github.event.pull_request.title }} + 작성자: @${{ github.event.pull_request.user.login }} + 브랜치: ${{ github.event.pull_request.head.ref }} → ${{ github.event.pull_request.base.ref }} From 6904ab9777194f16672b9408b1000a9b73fd4c44 Mon Sep 17 00:00:00 2001 From: changs97 Date: Tue, 12 Aug 2025 01:59:57 +0900 Subject: [PATCH 03/27] =?UTF-8?q?fix:=20=ED=8C=8C=EC=9D=B4=EC=96=B4?= =?UTF-8?q?=EB=B2=A0=EC=9D=B4=EC=8A=A4=20=EC=9D=98=EC=A1=B4=EC=84=B1=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 10 +++++++++- build.gradle.kts | 1 + gradle/libs.versions.toml | 1 + 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index ec2ffb0..f1c84a1 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -3,6 +3,7 @@ import com.android.build.gradle.internal.cxx.configure.gradleLocalProperties plugins { id("runcombi.android.application") alias(libs.plugins.google.services) + alias(libs.plugins.firebase.crashlytics) } android { @@ -44,10 +45,17 @@ android { getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro" ) - isDebuggable = false } + debug { + isDebuggable = true + } } + + buildFeatures { + buildConfig = true + } + flavorDimensions += "mode" productFlavors { create("mock") { diff --git a/build.gradle.kts b/build.gradle.kts index 8f918ba..3a18517 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -10,4 +10,5 @@ plugins { alias(libs.plugins.android.library) apply false alias(libs.plugins.jetbrains.kotlin.jvm) apply false alias(libs.plugins.google.services) apply false + alias(libs.plugins.firebase.crashlytics) apply false } \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ec3ac16..78f2902 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -165,6 +165,7 @@ hilt-android = { id = "com.google.dagger.hilt.android", version.ref = "hilt" } # Firebase google-services = { id = "com.google.gms.google-services", version = "4.4.3" } +firebase-crashlytics = { id = "com.google.firebase.crashlytics", version = "3.0.5" } [bundles] coroutines = ["coroutines-core", "coroutines-android"] From bca4b6145fb16104876600f0485c852fb7bc9528 Mon Sep 17 00:00:00 2001 From: changs97 Date: Tue, 12 Aug 2025 02:26:54 +0900 Subject: [PATCH 04/27] =?UTF-8?q?fix:=20firebase.crashlytics=20=EB=8B=A4?= =?UTF-8?q?=EC=9A=B4=EA=B7=B8=EB=A0=88=EC=9D=B4=EB=93=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 78f2902..6beb001 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -165,7 +165,7 @@ hilt-android = { id = "com.google.dagger.hilt.android", version.ref = "hilt" } # Firebase google-services = { id = "com.google.gms.google-services", version = "4.4.3" } -firebase-crashlytics = { id = "com.google.firebase.crashlytics", version = "3.0.5" } +firebase-crashlytics = { id = "com.google.firebase.crashlytics", version = "2.9.9" } [bundles] coroutines = ["coroutines-core", "coroutines-android"] From 654175543d64f57bd551de4ae304b5ad172fac1a Mon Sep 17 00:00:00 2001 From: changs97 Date: Tue, 12 Aug 2025 02:31:32 +0900 Subject: [PATCH 05/27] fix: ci/cd update --- .github/workflows/android.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index 2f63006..f7371f6 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -11,10 +11,10 @@ jobs: steps: - uses: actions/checkout@v4 - - name: set up JDK 11 + - name: set up JDK 17 uses: actions/setup-java@v4 with: - java-version: '11' + java-version: '17' distribution: 'temurin' cache: gradle From 7a1604824e78afad6157757b79c3efe36465a108 Mon Sep 17 00:00:00 2001 From: changs97 Date: Tue, 12 Aug 2025 02:32:00 +0900 Subject: [PATCH 06/27] fix: ci/cd update --- .github/workflows/android.yml | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index f7371f6..34c32f2 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -130,20 +130,3 @@ jobs: PR 제목: ${{ github.event.pull_request.title }} 작성자: @${{ github.event.pull_request.user.login }} 브랜치: ${{ github.event.pull_request.head.ref }} → ${{ github.event.pull_request.base.ref }} - - - name: If Fail, Send notification on Slack - if: ${{failure()}} - uses: rtCamp/action-slack-notify@v2 - env: - SLACK_COLOR: '#ff0000' - SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }} - SLACK_TITLE: 'RunCombi Android PR Check Failed ❌' - MSG_MINIMAL: true - SLACK_USERNAME: RunCombi Android - SLACK_MESSAGE: | - RunCombi Android PR 체크 실패 - 확인이 필요합니다 🔍 - Version: v${{ steps.app_version.outputs.version_name }} (${{ steps.app_version.outputs.version_code }}) - - PR 제목: ${{ github.event.pull_request.title }} - 작성자: @${{ github.event.pull_request.user.login }} - 브랜치: ${{ github.event.pull_request.head.ref }} → ${{ github.event.pull_request.base.ref }} From dd85d8003ca6b50ac5d11e43704b90b1e5832f9d Mon Sep 17 00:00:00 2001 From: changs97 Date: Tue, 12 Aug 2025 02:32:47 +0900 Subject: [PATCH 07/27] =?UTF-8?q?fix:=20=EB=B9=8C=EB=93=9C=20=EA=B2=BD?= =?UTF-8?q?=EA=B3=A0=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build-logic/settings.gradle.kts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/build-logic/settings.gradle.kts b/build-logic/settings.gradle.kts index 2490982..15c63d7 100644 --- a/build-logic/settings.gradle.kts +++ b/build-logic/settings.gradle.kts @@ -1,3 +1,5 @@ +rootProject.name = "build-logic" + enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") @Suppress("UnstableApiUsage") dependencyResolutionManagement { From f81fbd10fbb1eb0738857af7d96af86438a53ee6 Mon Sep 17 00:00:00 2001 From: changs97 Date: Tue, 12 Aug 2025 02:40:35 +0900 Subject: [PATCH 08/27] fix: ci/cd update --- .github/workflows/android.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index 34c32f2..1b4fcd3 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -40,8 +40,10 @@ jobs: - name: Get Google Services JSON env: GOOGLE_SERVICES_JSON: ${{secrets.GOOGLE_SERVICES_JSON}} - run: - echo '$GOOGLE_SERVICES_JSON' > ./app/google-services.json + run: | + echo '${{ secrets.GOOGLE_SERVICES_JSON }}' > ./app/google-services.json + echo "Google Services JSON file created successfully" + ls -la ./app/google-services.json - name: Create Keystore File env: From f09410011a2b4903ccd072cd54353b38ddd2ca60 Mon Sep 17 00:00:00 2001 From: changs97 Date: Tue, 12 Aug 2025 02:52:49 +0900 Subject: [PATCH 09/27] fix: get baseurl --- core/network/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/network/build.gradle.kts b/core/network/build.gradle.kts index a45f417..bf5ccd5 100644 --- a/core/network/build.gradle.kts +++ b/core/network/build.gradle.kts @@ -59,5 +59,5 @@ dependencies { } fun getBaseUrl(): String { - return gradleLocalProperties(rootDir, providers).getProperty("BASE_URL") ?: "" + return gradleLocalProperties(rootDir, providers).getProperty("BASE_URL") ?: "\"\"" } \ No newline at end of file From 9f664cc11cb253d5a5b0f606ebd38aeaeb991efc Mon Sep 17 00:00:00 2001 From: changs97 Date: Tue, 12 Aug 2025 03:02:07 +0900 Subject: [PATCH 10/27] fix: get baseurl --- core/network/build.gradle.kts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/core/network/build.gradle.kts b/core/network/build.gradle.kts index bf5ccd5..a986abd 100644 --- a/core/network/build.gradle.kts +++ b/core/network/build.gradle.kts @@ -21,11 +21,11 @@ android { buildTypes { debug { - buildConfigField("String", "BASE_URL", getBaseUrl()) + buildConfigField("String", "BASE_URL", "\"${getBaseUrl()}\"") } release { - buildConfigField("String", "BASE_URL", getBaseUrl()) + buildConfigField("String", "BASE_URL", "\"${getBaseUrl()}\"") isMinifyEnabled = false proguardFiles( getDefaultProguardFile("proguard-android-optimize.txt"), @@ -59,5 +59,6 @@ dependencies { } fun getBaseUrl(): String { - return gradleLocalProperties(rootDir, providers).getProperty("BASE_URL") ?: "\"\"" + val baseUrl = gradleLocalProperties(rootDir, providers).getProperty("BASE_URL") + return if (baseUrl.isNullOrBlank()) "http://api.runcombi.site/" else baseUrl } \ No newline at end of file From 1160106d2d0bb5708c3af20915b3721917b3e119 Mon Sep 17 00:00:00 2001 From: changs97 Date: Tue, 12 Aug 2025 03:13:56 +0900 Subject: [PATCH 11/27] fix: get baseurl --- core/network/build.gradle.kts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/core/network/build.gradle.kts b/core/network/build.gradle.kts index a986abd..ab7abf9 100644 --- a/core/network/build.gradle.kts +++ b/core/network/build.gradle.kts @@ -21,11 +21,11 @@ android { buildTypes { debug { - buildConfigField("String", "BASE_URL", "\"${getBaseUrl()}\"") + buildConfigField("String", "BASE_URL", getBaseUrl()) } release { - buildConfigField("String", "BASE_URL", "\"${getBaseUrl()}\"") + buildConfigField("String", "BASE_URL", getBaseUrl()) isMinifyEnabled = false proguardFiles( getDefaultProguardFile("proguard-android-optimize.txt"), @@ -60,5 +60,9 @@ dependencies { fun getBaseUrl(): String { val baseUrl = gradleLocalProperties(rootDir, providers).getProperty("BASE_URL") - return if (baseUrl.isNullOrBlank()) "http://api.runcombi.site/" else baseUrl + return if (baseUrl.isNullOrBlank()) { + "http://api.runcombi.site/" + } else { + baseUrl + } } \ No newline at end of file From a7f4b626e90c75efb04e5f9723f450b01d59167a Mon Sep 17 00:00:00 2001 From: changs97 Date: Tue, 12 Aug 2025 03:18:13 +0900 Subject: [PATCH 12/27] fix: ci/cd update --- .github/workflows/android.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index 1b4fcd3..847ebfc 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -21,6 +21,12 @@ jobs: - name: Grant execute permission for gradlew run: chmod +x gradlew + - name: Set Gradle Memory Settings + run: | + echo "org.gradle.jvmargs=-Xmx4096m -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8" >> gradle.properties + echo "Gradle memory settings updated:" + cat gradle.properties | grep "org.gradle.jvmargs" + - name: Add Local Properties env: BASE_URL: ${{secrets.BASE_URL}} From 51e67b9b17173b0867d3947172e2649d31450d3d Mon Sep 17 00:00:00 2001 From: changs97 Date: Tue, 12 Aug 2025 03:22:45 +0900 Subject: [PATCH 13/27] fix: ci/cd update --- .github/workflows/android.yml | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index 847ebfc..8953e47 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -23,9 +23,33 @@ jobs: - name: Set Gradle Memory Settings run: | - echo "org.gradle.jvmargs=-Xmx4096m -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8" >> gradle.properties - echo "Gradle memory settings updated:" - cat gradle.properties | grep "org.gradle.jvmargs" + cat > gradle.properties << 'EOF' + # Project-wide Gradle settings. + # IDE (e.g. Android Studio) users: + # Gradle settings configured through the IDE *will override* + # any settings specified in this file. + # For more details on how to configure your build environment visit + # http://www.gradle.org/docs/current/userguide/build_environment.html + # Specifies the JVM arguments used for the daemon process. + # The setting is particularly useful for tweaking memory settings. + org.gradle.jvmargs=-Xmx4096m -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 + # When configured, Gradle will run in incubating parallel mode. + # This option should only be used with decoupled projects. For more details, visit + # https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects + # org.gradle.parallel=true + # AndroidX package structure to make it clearer which packages are bundled with the + # Android operating system, and which are packaged with your app's APK + # https://developer.android.com/topic/libraries/support-library/androidx-rn + android.useAndroidX=true + # Kotlin code style for this project: "official" or "obsolete": + kotlin.code.style=official + # Enables namespacing of each library's R class so that its R class includes only the + # resources declared in the library itself and none from the library's dependencies, + # thereby reducing the size of the R class for that library + android.nonTransitiveRClass=true + EOF + echo "Gradle properties file created with memory settings:" + cat gradle.properties - name: Add Local Properties env: From a9d387435428f0d06e04cfe1b55a6869adc83c87 Mon Sep 17 00:00:00 2001 From: changs97 Date: Tue, 12 Aug 2025 03:33:00 +0900 Subject: [PATCH 14/27] fix: getBaseUrl --- core/network/build.gradle.kts | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/core/network/build.gradle.kts b/core/network/build.gradle.kts index ab7abf9..65426d1 100644 --- a/core/network/build.gradle.kts +++ b/core/network/build.gradle.kts @@ -33,7 +33,7 @@ android { ) } } - + buildFeatures { buildConfig = true } @@ -59,10 +59,5 @@ dependencies { } fun getBaseUrl(): String { - val baseUrl = gradleLocalProperties(rootDir, providers).getProperty("BASE_URL") - return if (baseUrl.isNullOrBlank()) { - "http://api.runcombi.site/" - } else { - baseUrl - } + return gradleLocalProperties(rootDir, providers).getProperty("BASE_URL") ?: "" } \ No newline at end of file From 44925a55e6ef84021f4c4c05a8bd32737b75c115 Mon Sep 17 00:00:00 2001 From: changs97 Date: Tue, 12 Aug 2025 03:47:45 +0900 Subject: [PATCH 15/27] fix: ci/cd update --- .github/workflows/android.yml | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index 8953e47..ccbf4ab 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -60,12 +60,16 @@ jobs: SIGNING_KEY_ALIAS: ${{secrets.SIGNING_KEY_ALIAS}} SIGNING_KEY_PASSWORD: ${{secrets.SIGNING_KEY_PASSWORD}} run: | - echo "BASE_URL=$BASE_URL" >> ./local.properties - echo "GOOGLE_API_KEY=$GOOGLE_API_KEY" >> ./local.properties - echo "KAKAO_API_KEY=$KAKAO_API_KEY" >> ./local.properties - echo "SIGNING_STORE_PASSWORD=$SIGNING_STORE_PASSWORD" >> ./local.properties - echo "SIGNING_KEY_ALIAS=$SIGNING_KEY_ALIAS" >> ./local.properties - echo "SIGNING_KEY_PASSWORD=$SIGNING_KEY_PASSWORD" >> ./local.properties + cat > ./local.properties << EOF + BASE_URL=$BASE_URL + GOOGLE_API_KEY=$GOOGLE_API_KEY + KAKAO_API_KEY=$KAKAO_API_KEY + SIGNING_STORE_PASSWORD=$SIGNING_STORE_PASSWORD + SIGNING_KEY_ALIAS=$SIGNING_KEY_ALIAS + SIGNING_KEY_PASSWORD=$SIGNING_KEY_PASSWORD + EOF + echo "Created local.properties with:" + cat ./local.properties - name: Get Google Services JSON env: From b9a120c4e0c99c4464c74343193a0a0a5120fd66 Mon Sep 17 00:00:00 2001 From: changs97 Date: Tue, 12 Aug 2025 03:55:21 +0900 Subject: [PATCH 16/27] fix: ci/cd update --- .github/workflows/android.yml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index ccbf4ab..aae08f1 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -60,7 +60,8 @@ jobs: SIGNING_KEY_ALIAS: ${{secrets.SIGNING_KEY_ALIAS}} SIGNING_KEY_PASSWORD: ${{secrets.SIGNING_KEY_PASSWORD}} run: | - cat > ./local.properties << EOF + echo "DEBUG: BASE_URL from secrets = '$BASE_URL'" + cat > local.properties << EOF BASE_URL=$BASE_URL GOOGLE_API_KEY=$GOOGLE_API_KEY KAKAO_API_KEY=$KAKAO_API_KEY @@ -68,8 +69,9 @@ jobs: SIGNING_KEY_ALIAS=$SIGNING_KEY_ALIAS SIGNING_KEY_PASSWORD=$SIGNING_KEY_PASSWORD EOF - echo "Created local.properties with:" - cat ./local.properties + echo "Created local.properties at project root:" + ls -la local.properties + cat local.properties - name: Get Google Services JSON env: From 4685289982e7b9b6c49c215a23849cec4727c256 Mon Sep 17 00:00:00 2001 From: changs97 Date: Tue, 12 Aug 2025 04:05:45 +0900 Subject: [PATCH 17/27] =?UTF-8?q?fix:=20test=20=EC=9D=98=EC=A1=B4=EC=84=B1?= =?UTF-8?q?=20=EB=88=84=EB=9D=BD=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index f1c84a1..b3307b6 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -91,4 +91,10 @@ dependencies { implementation(project(":core:data:walk")) implementation(project(":core:data:history")) implementation(project(":core:data:setting")) + + // Test dependencies + testImplementation(libs.junit) + testImplementation(libs.kotlin.test) + androidTestImplementation(libs.androidx.junit) + androidTestImplementation(libs.androidx.espresso.core) } \ No newline at end of file From 166caf7f569a6b2b44360998b47c1bbe58875b37 Mon Sep 17 00:00:00 2001 From: changs97 Date: Tue, 12 Aug 2025 04:09:41 +0900 Subject: [PATCH 18/27] =?UTF-8?q?fix:=20test=20=EC=9D=98=EC=A1=B4=EC=84=B1?= =?UTF-8?q?=20=EB=88=84=EB=9D=BD=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/combo/auth/ExampleInstrumentedTest.kt | 23 ------------------- .../history/ExampleInstrumentedTest.kt | 23 ------------------- .../setting/ExampleInstrumentedTest.kt | 23 ------------------- .../runcombi/walk/ExampleInstrumentedTest.kt | 22 ------------------ .../datastore/ExampleInstrumentedTest.kt | 22 ------------------ 5 files changed, 113 deletions(-) diff --git a/core/data/auth/src/androidTest/java/com/combo/auth/ExampleInstrumentedTest.kt b/core/data/auth/src/androidTest/java/com/combo/auth/ExampleInstrumentedTest.kt index 296d24d..5c686a0 100644 --- a/core/data/auth/src/androidTest/java/com/combo/auth/ExampleInstrumentedTest.kt +++ b/core/data/auth/src/androidTest/java/com/combo/auth/ExampleInstrumentedTest.kt @@ -1,24 +1 @@ package com.combo.auth - -import androidx.test.platform.app.InstrumentationRegistry -import androidx.test.ext.junit.runners.AndroidJUnit4 - -import org.junit.Test -import org.junit.runner.RunWith - -import org.junit.Assert.* - -/** - * Instrumented test, which will execute on an Android device. - * - * See [testing documentation](http://d.android.com/tools/testing). - */ -@RunWith(AndroidJUnit4::class) -class ExampleInstrumentedTest { - @Test - fun useAppContext() { - // Context of the app under test. - val appContext = InstrumentationRegistry.getInstrumentation().targetContext - assertEquals("com.combo.auth.test", appContext.packageName) - } -} \ No newline at end of file diff --git a/core/data/history/src/androidTest/java/com/combo/runcombi/history/ExampleInstrumentedTest.kt b/core/data/history/src/androidTest/java/com/combo/runcombi/history/ExampleInstrumentedTest.kt index cc26751..715fbf8 100644 --- a/core/data/history/src/androidTest/java/com/combo/runcombi/history/ExampleInstrumentedTest.kt +++ b/core/data/history/src/androidTest/java/com/combo/runcombi/history/ExampleInstrumentedTest.kt @@ -1,24 +1 @@ package com.combo.runcombi.history - -import androidx.test.platform.app.InstrumentationRegistry -import androidx.test.ext.junit.runners.AndroidJUnit4 - -import org.junit.Test -import org.junit.runner.RunWith - -import org.junit.Assert.* - -/** - * Instrumented test, which will execute on an Android device. - * - * See [testing documentation](http://d.android.com/tools/testing). - */ -@RunWith(AndroidJUnit4::class) -class ExampleInstrumentedTest { - @Test - fun useAppContext() { - // Context of the app under test. - val appContext = InstrumentationRegistry.getInstrumentation().targetContext - assertEquals("com.combo.runcombi.history.test", appContext.packageName) - } -} \ No newline at end of file diff --git a/core/data/setting/src/androidTest/java/com/combo/runcombi/setting/ExampleInstrumentedTest.kt b/core/data/setting/src/androidTest/java/com/combo/runcombi/setting/ExampleInstrumentedTest.kt index 8c340ed..1d39ec4 100644 --- a/core/data/setting/src/androidTest/java/com/combo/runcombi/setting/ExampleInstrumentedTest.kt +++ b/core/data/setting/src/androidTest/java/com/combo/runcombi/setting/ExampleInstrumentedTest.kt @@ -1,24 +1 @@ package com.combo.runcombi.setting - -import androidx.test.platform.app.InstrumentationRegistry -import androidx.test.ext.junit.runners.AndroidJUnit4 - -import org.junit.Test -import org.junit.runner.RunWith - -import org.junit.Assert.* - -/** - * Instrumented test, which will execute on an Android device. - * - * See [testing documentation](http://d.android.com/tools/testing). - */ -@RunWith(AndroidJUnit4::class) -class ExampleInstrumentedTest { - @Test - fun useAppContext() { - // Context of the app under test. - val appContext = InstrumentationRegistry.getInstrumentation().targetContext - assertEquals("com.combo.runcombi.setting.test", appContext.packageName) - } -} \ No newline at end of file diff --git a/core/data/walk/src/androidTest/java/com/combo/runcombi/walk/ExampleInstrumentedTest.kt b/core/data/walk/src/androidTest/java/com/combo/runcombi/walk/ExampleInstrumentedTest.kt index 13bcc53..21c437d 100644 --- a/core/data/walk/src/androidTest/java/com/combo/runcombi/walk/ExampleInstrumentedTest.kt +++ b/core/data/walk/src/androidTest/java/com/combo/runcombi/walk/ExampleInstrumentedTest.kt @@ -1,24 +1,2 @@ package com.combo.runcombi.walk -import androidx.test.platform.app.InstrumentationRegistry -import androidx.test.ext.junit.runners.AndroidJUnit4 - -import org.junit.Test -import org.junit.runner.RunWith - -import org.junit.Assert.* - -/** - * Instrumented test, which will execute on an Android device. - * - * See [testing documentation](http://d.android.com/tools/testing). - */ -@RunWith(AndroidJUnit4::class) -class ExampleInstrumentedTest { - @Test - fun useAppContext() { - // Context of the app under test. - val appContext = InstrumentationRegistry.getInstrumentation().targetContext - assertEquals("com.combo.runcombi.walk.test", appContext.packageName) - } -} \ No newline at end of file diff --git a/core/datastore/src/androidTest/java/com/combo/runcombi/datastore/ExampleInstrumentedTest.kt b/core/datastore/src/androidTest/java/com/combo/runcombi/datastore/ExampleInstrumentedTest.kt index be2072f..2472014 100644 --- a/core/datastore/src/androidTest/java/com/combo/runcombi/datastore/ExampleInstrumentedTest.kt +++ b/core/datastore/src/androidTest/java/com/combo/runcombi/datastore/ExampleInstrumentedTest.kt @@ -1,24 +1,2 @@ package com.combo.runcombi.datastore -import androidx.test.platform.app.InstrumentationRegistry -import androidx.test.ext.junit.runners.AndroidJUnit4 - -import org.junit.Test -import org.junit.runner.RunWith - -import org.junit.Assert.* - -/** - * Instrumented test, which will execute on an Android device. - * - * See [testing documentation](http://d.android.com/tools/testing). - */ -@RunWith(AndroidJUnit4::class) -class ExampleInstrumentedTest { - @Test - fun useAppContext() { - // Context of the app under test. - val appContext = InstrumentationRegistry.getInstrumentation().targetContext - assertEquals("com.combo.runcombi.datastore.test", appContext.packageName) - } -} \ No newline at end of file From 1d8d1ac2349bc16f3ae79b15a1d4261d59445ff3 Mon Sep 17 00:00:00 2001 From: changs97 Date: Tue, 12 Aug 2025 08:31:01 +0900 Subject: [PATCH 19/27] fix: ci/cd --- .github/workflows/android.yml | 46 ++++++++++++++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index aae08f1..1a49881 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -4,6 +4,10 @@ on: pull_request: branches: [ "master" ] +# 전체 워크플로우에 대한 권한 설정 +permissions: + contents: write + jobs: build: @@ -87,6 +91,23 @@ jobs: run: | echo $KEYSTORE_BASE64 | base64 -d > ./app/runcombi-keystore.jks + - name: Check if runcombi-keystore.jks exists + run: | + if [ -f "./app/runcombi-keystore.jks" ]; then + echo "✅ runcombi-keystore.jks file exists in the app directory." + ls -la ./app/runcombi-keystore.jks + else + echo "❌ Error: runcombi-keystore.jks file is missing in the app directory." >&2 + exit 1 + fi + + - name: List keys in runcombi-keystore.jks + env: + SIGNING_STORE_PASSWORD: ${{secrets.SIGNING_STORE_PASSWORD}} + run: | + echo "Checking keystore contents..." + keytool -list -v -keystore ./app/runcombi-keystore.jks -storepass "$SIGNING_STORE_PASSWORD" || echo "Failed to list keystore contents" + - name: Extract App Version id: app_version run: | @@ -100,7 +121,30 @@ jobs: run: ./gradlew build - name: Build Release APK - run: ./gradlew assembleRelease + run: | + echo "Starting assembleRelease..." + ./gradlew assembleRelease --info --stacktrace + echo "assembleRelease completed with exit code: $?" + echo "Checking if APK was generated..." + if [ -f "app/build/outputs/apk/release/app-release.apk" ]; then + echo "✅ APK file found!" + ls -la app/build/outputs/apk/release/ + else + echo "❌ APK file not found!" + echo "Checking build directory..." + ls -la app/build/outputs/ || echo "Outputs directory not found" + echo "Checking for any APK files..." + find app/build/outputs/ -name "*.apk" -type f || echo "No APK files found" + fi + + - name: Check APK output + run: | + echo "Checking APK output directory..." + ls -la app/build/outputs/apk/release/ || echo "Release directory not found" + echo "Checking all APK outputs..." + find app/build/outputs/ -name "*.apk" -type f || echo "No APK files found" + echo "Checking build directory structure..." + ls -la app/build/outputs/ || echo "Outputs directory not found" - name: Upload Release Build to Artifacts uses: actions/upload-artifact@v4 From 6f6edcb2c2e19b889a2768ee3832d995559952b6 Mon Sep 17 00:00:00 2001 From: changs97 Date: Tue, 12 Aug 2025 09:11:01 +0900 Subject: [PATCH 20/27] fix: ci/cd --- .github/workflows/android.yml | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index 1a49881..306b42e 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -150,7 +150,9 @@ jobs: uses: actions/upload-artifact@v4 with: name: release-artifacts - path: app/build/outputs/apk/release/ + path: | + app/build/outputs/apk/mock/release/ + app/build/outputs/apk/prod/release/ if-no-files-found: error - name: Create Github Release @@ -160,7 +162,8 @@ jobs: release_name: RunCombi Android v${{ steps.app_version.outputs.version_name }} generate_release_notes: true files: | - app/build/outputs/apk/release/app-release.apk + app/build/outputs/apk/mock/release/app-mock-release.apk + app/build/outputs/apk/prod/release/app-prod-release.apk body: | ## RunCombi Android v${{ steps.app_version.outputs.version_name }} @@ -171,6 +174,10 @@ jobs: - PR: ${{ github.event.pull_request.title }} - Author: @${{ github.event.pull_request.user.login }} - Branch: ${{ github.event.pull_request.head.ref }} → ${{ github.event.pull_request.base.ref }} + + ### APK Files + - **Mock Release**: app-mock-release.apk + - **Prod Release**: app-prod-release.apk - name: Upload artifact to Firebase App Distribution uses: wzieba/Firebase-Distribution-Github-Action@v1 @@ -178,13 +185,15 @@ jobs: appId: ${{secrets.FIREBASE_APP_ID}} serviceCredentialsFileContent: ${{ secrets.CREDENTIAL_FILE_CONTENT }} groups: testers - file: app/build/outputs/apk/release/app-release.apk + file: app/build/outputs/apk/prod/release/app-prod-release.apk releaseNotes: | RunCombi Android v${{ steps.app_version.outputs.version_name }} PR: ${{ github.event.pull_request.title }} Author: @${{ github.event.pull_request.user.login }} Branch: ${{ github.event.pull_request.head.ref }} → ${{ github.event.pull_request.base.ref }} + + APK: Prod Release Version - name: Upload APK to Slack if: ${{success()}} @@ -194,7 +203,7 @@ jobs: webhook_url: ${{ secrets.SLACK_WEBHOOK_URL }} channel: '#general' text: 'RunCombi Android APK 빌드 완료! 🎉' - file: app/build/outputs/apk/release/app-release.apk + file: app/build/outputs/apk/prod/release/app-prod-release.apk - name: If Success, Send notification on Slack if: ${{success()}} From d370b3e75a08267a1f3c9bbbeae1ce09fa8e166c Mon Sep 17 00:00:00 2001 From: changs97 Date: Tue, 12 Aug 2025 20:43:02 +0900 Subject: [PATCH 21/27] =?UTF-8?q?fix:=20=EA=B3=B5=EC=A7=80=20=ED=83=80?= =?UTF-8?q?=EC=9E=85=EC=9D=BC=20=EA=B2=BD=EC=9A=B0=20=EA=B8=B0=EA=B0=84?= =?UTF-8?q?=EC=9D=B4=20=EC=95=84=EB=8B=8C=20=EB=93=B1=EB=A1=9D=20=EB=82=A0?= =?UTF-8?q?=EC=A7=9C=20=ED=91=9C=EC=8B=9C=ED=95=98=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../screen/AnnouncementDetailScreen.kt | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/feature/setting/src/main/java/com/combo/runcombi/setting/screen/AnnouncementDetailScreen.kt b/feature/setting/src/main/java/com/combo/runcombi/setting/screen/AnnouncementDetailScreen.kt index b24d392..4aaef99 100644 --- a/feature/setting/src/main/java/com/combo/runcombi/setting/screen/AnnouncementDetailScreen.kt +++ b/feature/setting/src/main/java/com/combo/runcombi/setting/screen/AnnouncementDetailScreen.kt @@ -191,7 +191,9 @@ fun AnnouncementDetailContent( TitleSection( title = detail.title, startDate = detail.startDate, - endDate = detail.endDate + endDate = detail.endDate, + announcementType = detail.announcementType, + regDate = detail.regDate ) Spacer(modifier = Modifier.height(16.dp)) @@ -222,6 +224,8 @@ fun TitleSection( title: String, startDate: String, endDate: String, + announcementType: String, + regDate: String, ) { Column { Text( @@ -232,11 +236,19 @@ fun TitleSection( Spacer(modifier = Modifier.height(8.dp)) - Text( - text = "기간: ${FormatUtils.formatDate(startDate)} ~ ${FormatUtils.formatDate(endDate)}", - style = body3, - color = Grey06 - ) + if (announcementType == "EVENT") { + Text( + text = "기간: ${FormatUtils.formatDate(startDate)} ~ ${FormatUtils.formatDate(endDate)}", + style = body3, + color = Grey06 + ) + } else { + Text( + text = "등록일: ${FormatUtils.formatDate(regDate)}", + style = body3, + color = Grey06 + ) + } } } From b1a8a9a5349bf5216c76328f3443a2530929c18a Mon Sep 17 00:00:00 2001 From: changs97 Date: Tue, 12 Aug 2025 23:35:04 +0900 Subject: [PATCH 22/27] =?UTF-8?q?fix:=20ci/cd=20=EC=8A=AC=EB=9E=99=20apk?= =?UTF-8?q?=20=EC=97=85=EB=A1=9C=EB=93=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/android.yml | 49 +++++++++++++++++++++-------------- 1 file changed, 30 insertions(+), 19 deletions(-) diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index 306b42e..96dd811 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -197,27 +197,38 @@ jobs: - name: Upload APK to Slack if: ${{success()}} - uses: 8398a7/action-slack@v3 + uses: MeilCli/slack-upload-file@v1 with: - status: success - webhook_url: ${{ secrets.SLACK_WEBHOOK_URL }} - channel: '#general' - text: 'RunCombi Android APK 빌드 완료! 🎉' - file: app/build/outputs/apk/prod/release/app-prod-release.apk + slack_token: ${{ secrets.SLACK_READ_WRITE_TOKEN }} + channels: ${{ secrets.SLACK_CHANNEL_DEPLOY }} + file_path: app/build/outputs/apk/prod/release/app-prod-release.apk + file_name: RunCombi-Android-v${{ steps.app_version.outputs.version_name }}.apk + file_type: apk + initial_comment: | + 🎉 RunCombi Android APK 빌드 완료! + + **Version:** v${{ steps.app_version.outputs.version_name }} (${{ steps.app_version.outputs.version_code }}) + **PR:** ${{ github.event.pull_request.title }} + **Author:** @${{ github.event.pull_request.user.login }} + **Branch:** ${{ github.event.pull_request.head.ref }} → ${{ github.event.pull_request.base.ref }} + + APK 파일이 성공적으로 업로드되었습니다. - name: If Success, Send notification on Slack if: ${{success()}} - uses: rtCamp/action-slack-notify@v2 - env: - SLACK_COLOR: '#60E0C5' - SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }} - SLACK_TITLE: 'RunCombi Android PR Check Success ✅' - MSG_MINIMAL: true - SLACK_USERNAME: RunCombi Android - SLACK_MESSAGE: | - RunCombi Android PR 체크 성공 🎉 - Version: v${{ steps.app_version.outputs.version_name }} (${{ steps.app_version.outputs.version_code }}) + uses: MeilCli/slack-upload-file@v1 + with: + slack_token: ${{ secrets.SLACK_TOKEN }} + channels: ${{ secrets.SLACK_CHANNEL }} + file_path: app/build/outputs/apk/prod/release/app-prod-release.apk + file_name: RunCombi-Android-v${{ steps.app_version.outputs.version_name }}-notification.apk + file_type: apk + initial_comment: | + ✅ RunCombi Android PR 체크 성공 🎉 + + **Version:** v${{ steps.app_version.outputs.version_name }} (${{ steps.app_version.outputs.version_code }}) + **PR 제목:** ${{ github.event.pull_request.title }} + **작성자:** @${{ github.event.pull_request.user.login }} + **브랜치:** ${{ github.event.pull_request.head.ref }} → ${{ github.event.pull_request.base.ref }} - PR 제목: ${{ github.event.pull_request.title }} - 작성자: @${{ github.event.pull_request.user.login }} - 브랜치: ${{ github.event.pull_request.head.ref }} → ${{ github.event.pull_request.base.ref }} + PR 체크가 성공적으로 완료되었습니다. From 0e9590ad90bf803393dded556b60774ea61bd9e0 Mon Sep 17 00:00:00 2001 From: changs97 Date: Wed, 13 Aug 2025 00:21:26 +0900 Subject: [PATCH 23/27] =?UTF-8?q?fix:=20ci/cd=20=EC=8A=AC=EB=9E=99=20apk?= =?UTF-8?q?=20=EC=97=85=EB=A1=9C=EB=93=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/android.yml | 26 +++++--------------------- 1 file changed, 5 insertions(+), 21 deletions(-) diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index 96dd811..a14e4f3 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -199,8 +199,8 @@ jobs: if: ${{success()}} uses: MeilCli/slack-upload-file@v1 with: - slack_token: ${{ secrets.SLACK_READ_WRITE_TOKEN }} - channels: ${{ secrets.SLACK_CHANNEL_DEPLOY }} + slack_token: ${{ secrets.SLACK_TOKEN }} + channels: ${{ secrets.SLACK_CHANNEL }} file_path: app/build/outputs/apk/prod/release/app-prod-release.apk file_name: RunCombi-Android-v${{ steps.app_version.outputs.version_name }}.apk file_type: apk @@ -212,23 +212,7 @@ jobs: **Author:** @${{ github.event.pull_request.user.login }} **Branch:** ${{ github.event.pull_request.head.ref }} → ${{ github.event.pull_request.base.ref }} - APK 파일이 성공적으로 업로드되었습니다. - - - name: If Success, Send notification on Slack - if: ${{success()}} - uses: MeilCli/slack-upload-file@v1 - with: - slack_token: ${{ secrets.SLACK_TOKEN }} - channels: ${{ secrets.SLACK_CHANNEL }} - file_path: app/build/outputs/apk/prod/release/app-prod-release.apk - file_name: RunCombi-Android-v${{ steps.app_version.outputs.version_name }}-notification.apk - file_type: apk - initial_comment: | - ✅ RunCombi Android PR 체크 성공 🎉 + **PR 내용:** + ${{ github.event.pull_request.body || '내용이 없습니다.' }} - **Version:** v${{ steps.app_version.outputs.version_name }} (${{ steps.app_version.outputs.version_code }}) - **PR 제목:** ${{ github.event.pull_request.title }} - **작성자:** @${{ github.event.pull_request.user.login }} - **브랜치:** ${{ github.event.pull_request.head.ref }} → ${{ github.event.pull_request.base.ref }} - - PR 체크가 성공적으로 완료되었습니다. + APK 파일이 성공적으로 업로드되었습니다. From 5df070e8616010d32acce4aea0b8130e4f9552c3 Mon Sep 17 00:00:00 2001 From: changs97 Date: Wed, 13 Aug 2025 01:08:02 +0900 Subject: [PATCH 24/27] =?UTF-8?q?fix:=20ci/cd=20=EC=8A=AC=EB=9E=99=20apk?= =?UTF-8?q?=20=EC=97=85=EB=A1=9C=EB=93=9C=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/android.yml | 65 ++++++++++++++++++++++++----------- 1 file changed, 45 insertions(+), 20 deletions(-) diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index a14e4f3..269068e 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -117,6 +117,10 @@ jobs: echo "version_code=$VERSION_CODE" >> $GITHUB_OUTPUT echo "Extracted version: $VERSION_NAME ($VERSION_CODE)" + - name: Get timestamp + id: timestamp + run: echo "timestamp=$(date +%s)" >> $GITHUB_OUTPUT + - name: Build with Gradle run: ./gradlew build @@ -195,24 +199,45 @@ jobs: APK: Prod Release Version - - name: Upload APK to Slack - if: ${{success()}} - uses: MeilCli/slack-upload-file@v1 + - name: action-slack + uses: 8398a7/action-slack@v3 with: - slack_token: ${{ secrets.SLACK_TOKEN }} - channels: ${{ secrets.SLACK_CHANNEL }} - file_path: app/build/outputs/apk/prod/release/app-prod-release.apk - file_name: RunCombi-Android-v${{ steps.app_version.outputs.version_name }}.apk - file_type: apk - initial_comment: | - 🎉 RunCombi Android APK 빌드 완료! - - **Version:** v${{ steps.app_version.outputs.version_name }} (${{ steps.app_version.outputs.version_code }}) - **PR:** ${{ github.event.pull_request.title }} - **Author:** @${{ github.event.pull_request.user.login }} - **Branch:** ${{ github.event.pull_request.head.ref }} → ${{ github.event.pull_request.base.ref }} - - **PR 내용:** - ${{ github.event.pull_request.body || '내용이 없습니다.' }} - - APK 파일이 성공적으로 업로드되었습니다. + status: ${{ job.status }} + author_name: RunCombi Android CI + fields: repo,message,commit,author,action,eventName,ref,workflow,job,took + if_mention: failure,cancelled + custom_payload: | + { + "attachments": [{ + "color": "${{ job.status == 'success' && '#36a64f' || '#ff0000' }}", + "title": "🎉 RunCombi Android 빌드 완료", + "fields": [ + { + "title": "📱 앱 버전", + "value": "v${{ steps.app_version.outputs.version_name }} (${{ steps.app_version.outputs.version_code }})", + "short": true + }, + { + "title": "🔗 PR 제목", + "value": "${{ github.event.pull_request.title }}", + "short": true + }, + { + "title": "📝 PR 내용", + "value": "${{ github.event.pull_request.body || '내용이 없습니다.' }}", + "short": false + }, + { + "title": "🚀 GitHub Release", + "value": "https://github.com/${{ github.repository }}/releases/tag/v${{ steps.app_version.outputs.version_name }}", + "short": false + } + ], + "footer": "RunCombi Android CI", + "ts": "${{ env.TIMESTAMP }}" + }] + } + env: + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} + TIMESTAMP: ${{ steps.timestamp.outputs.timestamp }} + if: always() From 0592b00f55ca250cebb956dd069084dedbbf2350 Mon Sep 17 00:00:00 2001 From: changs97 Date: Wed, 13 Aug 2025 01:23:55 +0900 Subject: [PATCH 25/27] =?UTF-8?q?feat:=20Firebase=20=EC=9D=B4=EB=B2=A4?= =?UTF-8?q?=ED=8A=B8=20=EB=A1=9C=EA=B7=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/runcombi.android.feature.gradle.kts | 1 + core/analytics/.gitignore | 1 + core/analytics/README.md | 92 ++++++++++++ core/analytics/build.gradle.kts | 45 ++++++ .../runcombi/analytics/AnalyticsEvent.kt | 44 ++++++ .../runcombi/analytics/AnalyticsExtensions.kt | 141 ++++++++++++++++++ .../runcombi/analytics/AnalyticsHelper.kt | 5 + .../runcombi/analytics/AnalyticsModule.kt | 15 ++ .../analytics/FirebaseAnalyticsHelper.kt | 32 ++++ .../runcombi/analytics/NoOpAnalyticsHelper.kt | 5 + .../runcombi/analytics/StubAnalyticsHelper.kt | 18 +++ .../runcombi/feature/login/LoginScreen.kt | 19 ++- .../runcombi/feature/login/LoginViewModel.kt | 4 +- .../screen/AnnouncementDetailScreen.kt | 6 +- .../viewmodel/AnnouncementDetailViewModel.kt | 2 + .../signup/viewmodel/CompleteViewModel.kt | 12 ++ .../runcombi/walk/screen/WalkMainScreen.kt | 5 +- .../walk/screen/WalkTrackingScreen.kt | 13 +- .../walk/viewmodel/WalkMainViewModel.kt | 2 + .../walk/viewmodel/WalkTrackingViewModel.kt | 2 + gradle/libs.versions.toml | 2 + settings.gradle.kts | 1 + 22 files changed, 460 insertions(+), 7 deletions(-) create mode 100644 core/analytics/.gitignore create mode 100644 core/analytics/README.md create mode 100644 core/analytics/build.gradle.kts create mode 100644 core/analytics/src/main/java/com/combo/runcombi/analytics/AnalyticsEvent.kt create mode 100644 core/analytics/src/main/java/com/combo/runcombi/analytics/AnalyticsExtensions.kt create mode 100644 core/analytics/src/main/java/com/combo/runcombi/analytics/AnalyticsHelper.kt create mode 100644 core/analytics/src/main/java/com/combo/runcombi/analytics/AnalyticsModule.kt create mode 100644 core/analytics/src/main/java/com/combo/runcombi/analytics/FirebaseAnalyticsHelper.kt create mode 100644 core/analytics/src/main/java/com/combo/runcombi/analytics/NoOpAnalyticsHelper.kt create mode 100644 core/analytics/src/main/java/com/combo/runcombi/analytics/StubAnalyticsHelper.kt create mode 100644 feature/signup/src/main/java/com/combo/runcombi/signup/viewmodel/CompleteViewModel.kt diff --git a/build-logic/src/main/java/runcombi.android.feature.gradle.kts b/build-logic/src/main/java/runcombi.android.feature.gradle.kts index 056eb6f..120ca2f 100644 --- a/build-logic/src/main/java/runcombi.android.feature.gradle.kts +++ b/build-logic/src/main/java/runcombi.android.feature.gradle.kts @@ -23,6 +23,7 @@ configureHiltAndroid() dependencies { implementation(project(":core:designsystem")) implementation(project(":core:navigation")) + implementation(project(":core:analytics")) implementation(project(":core:ui")) implementation(project(":core:domain:auth")) implementation(project(":core:domain:common")) diff --git a/core/analytics/.gitignore b/core/analytics/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/core/analytics/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/core/analytics/README.md b/core/analytics/README.md new file mode 100644 index 0000000..d8c3a13 --- /dev/null +++ b/core/analytics/README.md @@ -0,0 +1,92 @@ +# Analytics 모듈 + +RunCombi_Android 프로젝트의 분석 이벤트 로깅을 위한 모듈입니다. + +## 주요 구성 요소 + +### 1. AnalyticsEvent +분석 이벤트를 정의하는 데이터 클래스입니다. + +### 2. AnalyticsHelper +분석 이벤트를 로깅하는 인터페이스입니다. + +### 3. StubAnalyticsHelper +개발 및 디버그 빌드에서 사용하는 구현체로, 이벤트를 logcat에 기록합니다. + +### 4. FirebaseAnalyticsHelper +프로덕션 빌드에서 사용하는 구현체로, Firebase Analytics에 실제 이벤트를 전송합니다. + +### 5. NoOpAnalyticsHelper +테스트와 프리뷰에서 사용하는 구현체로, 아무것도 하지 않습니다. + +## 사용법 + +### 기본 사용법 + +```kotlin +@Inject +lateinit var analyticsHelper: AnalyticsHelper + +analyticsHelper.logEvent( + AnalyticsEvent( + type = "custom_event", + extras = listOf( + AnalyticsEvent.Param(key = "key", value = "value") + ) + ) +) +``` + +### 확장 함수 사용 + +```kotlin +analyticsHelper.logScreenView("LoginActivity") +analyticsHelper.logUserLogin("google") +analyticsHelper.logWalkStarted("outdoor") +analyticsHelper.logSettingChanged("theme", "dark") +analyticsHelper.logFeatureUsed("camera") +analyticsHelper.logButtonClick("login_button", "LoginScreen") +analyticsHelper.logError("network_error", "Connection failed", "MainScreen") +``` + +### Compose에서 사용 + +```kotlin +val analyticsHelper = LocalAnalyticsHelper.current + +LaunchedEffect(Unit) { + analyticsHelper.logScreenView("HomeScreen") +} +``` + +## 표준 이벤트 타입 + +- `screen_view`: 화면 보기 +- `user_login`: 사용자 로그인 +- `user_signup`: 사용자 회원가입 +- `walk_started`: 산책 시작 +- `walk_completed`: 산책 완료 +- `setting_changed`: 설정 변경 +- `feature_used`: 기능 사용 +- `button_click`: 버튼 클릭 +- `error_occurred`: 오류 발생 +- `app_started`: 앱 시작 +- `app_backgrounded`: 앱 백그라운드 +- `app_foregrounded`: 앱 포그라운드 + +## 표준 매개변수 키 + +- `screen_name`: 화면 이름 +- `login_method`: 로그인 방법 +- `signup_method`: 회원가입 방법 +- `walk_type`: 산책 타입 +- `walk_duration`: 산책 시간 +- `walk_distance`: 산책 거리 +- `setting_name`: 설정 이름 +- `setting_value`: 설정 값 +- `feature_name`: 기능 이름 +- `button_name`: 버튼 이름 +- `error_type`: 오류 타입 +- `error_message`: 오류 메시지 +- `is_new_user`: 신규 사용자 여부 +- `user_status`: 사용자 상태 diff --git a/core/analytics/build.gradle.kts b/core/analytics/build.gradle.kts new file mode 100644 index 0000000..9de162c --- /dev/null +++ b/core/analytics/build.gradle.kts @@ -0,0 +1,45 @@ +plugins { + alias(libs.plugins.android.library) + alias(libs.plugins.kotlin.android) + alias(libs.plugins.hilt.android) + alias(libs.plugins.kotlin.ksp) +} + +android { + namespace = "com.combo.runcombi.analytics" + compileSdk = 35 + + defaultConfig { + minSdk = 26 + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles("consumer-rules.pro") + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } + kotlinOptions { + jvmTarget = "11" + } +} + +dependencies { + // Hilt + implementation(libs.hilt.android) + ksp(libs.hilt.compiler) + + // Firebase + implementation(platform(libs.firebase.bom)) + implementation(libs.firebase.analytics) +} diff --git a/core/analytics/src/main/java/com/combo/runcombi/analytics/AnalyticsEvent.kt b/core/analytics/src/main/java/com/combo/runcombi/analytics/AnalyticsEvent.kt new file mode 100644 index 0000000..a53bfdf --- /dev/null +++ b/core/analytics/src/main/java/com/combo/runcombi/analytics/AnalyticsEvent.kt @@ -0,0 +1,44 @@ +package com.combo.runcombi.analytics + +data class AnalyticsEvent( + val type: String, + val extras: List = emptyList(), +) { + class Types { + companion object { + const val SCREEN_VIEW = "screen_view" + const val USER_LOGIN = "user_login" + const val USER_SIGNUP = "user_signup" + const val WALK_STARTED = "walk_started" + const val WALK_COMPLETED = "walk_completed" + const val SETTING_CHANGED = "setting_changed" + const val FEATURE_USED = "feature_used" + const val BUTTON_CLICK = "button_click" + const val ERROR_OCCURRED = "error_occurred" + const val APP_STARTED = "app_started" + const val APP_BACKGROUNDED = "app_backgrounded" + const val APP_FOREGROUNDED = "app_foregrounded" + } + } + + data class Param(val key: String, val value: String) + + class ParamKeys { + companion object { + const val SCREEN_NAME = "screen_name" + const val LOGIN_METHOD = "login_method" + const val SIGNUP_METHOD = "signup_method" + const val WALK_TYPE = "walk_type" + const val WALK_DURATION = "walk_duration" + const val WALK_DISTANCE = "walk_distance" + const val SETTING_NAME = "setting_name" + const val SETTING_VALUE = "setting_value" + const val FEATURE_NAME = "feature_name" + const val BUTTON_NAME = "button_name" + const val ERROR_TYPE = "error_type" + const val ERROR_MESSAGE = "error_message" + const val IS_NEW_USER = "is_new_user" + const val USER_STATUS = "user_status" + } + } +} diff --git a/core/analytics/src/main/java/com/combo/runcombi/analytics/AnalyticsExtensions.kt b/core/analytics/src/main/java/com/combo/runcombi/analytics/AnalyticsExtensions.kt new file mode 100644 index 0000000..66a0ffc --- /dev/null +++ b/core/analytics/src/main/java/com/combo/runcombi/analytics/AnalyticsExtensions.kt @@ -0,0 +1,141 @@ +package com.combo.runcombi.analytics + +import com.combo.runcombi.analytics.AnalyticsEvent +import com.combo.runcombi.analytics.AnalyticsEvent.Param +import com.combo.runcombi.analytics.AnalyticsHelper + +// 기본 화면 및 사용자 이벤트 +fun AnalyticsHelper.logScreenView(screenName: String) = + logEvent( + AnalyticsEvent( + type = AnalyticsEvent.Types.SCREEN_VIEW, + extras = listOf( + Param(key = AnalyticsEvent.ParamKeys.SCREEN_NAME, value = screenName), + ), + ), + ) + +fun AnalyticsHelper.logUserLogin(loginMethod: String) = + logEvent( + AnalyticsEvent( + type = AnalyticsEvent.Types.USER_LOGIN, + extras = listOf( + Param(key = AnalyticsEvent.ParamKeys.LOGIN_METHOD, value = loginMethod), + ), + ), + ) + +fun AnalyticsHelper.logUserSignup(signupMethod: String) = + logEvent( + AnalyticsEvent( + type = AnalyticsEvent.Types.USER_SIGNUP, + extras = listOf( + Param(key = AnalyticsEvent.ParamKeys.SIGNUP_METHOD, value = signupMethod), + ), + ), + ) + +// 산책 관련 이벤트 +fun AnalyticsHelper.logWalkStarted(walkType: String) = + logEvent( + AnalyticsEvent( + type = AnalyticsEvent.Types.WALK_STARTED, + extras = listOf( + Param(key = AnalyticsEvent.ParamKeys.WALK_TYPE, value = walkType), + ), + ), + ) + +fun AnalyticsHelper.logWalkCompleted(duration: String, distance: String) = + logEvent( + AnalyticsEvent( + type = AnalyticsEvent.Types.WALK_COMPLETED, + extras = listOf( + Param(key = AnalyticsEvent.ParamKeys.WALK_DURATION, value = duration), + Param(key = AnalyticsEvent.ParamKeys.WALK_DISTANCE, value = distance), + ), + ), + ) + +// 설정 및 기능 이벤트 +fun AnalyticsHelper.logSettingChanged(settingName: String, settingValue: String) = + logEvent( + AnalyticsEvent( + type = AnalyticsEvent.Types.SETTING_CHANGED, + extras = listOf( + Param(key = AnalyticsEvent.ParamKeys.SETTING_NAME, value = settingName), + Param(key = AnalyticsEvent.ParamKeys.SETTING_VALUE, value = settingValue), + ), + ), + ) + +fun AnalyticsHelper.logFeatureUsed(featureName: String) = + logEvent( + AnalyticsEvent( + type = AnalyticsEvent.Types.FEATURE_USED, + extras = listOf( + Param(key = AnalyticsEvent.ParamKeys.FEATURE_NAME, value = featureName), + ), + ), + ) + +// 앱 생명주기 이벤트 +fun AnalyticsHelper.logAppStarted(isNewUser: Boolean, userStatus: String) = + logEvent( + AnalyticsEvent( + type = AnalyticsEvent.Types.APP_STARTED, + extras = listOf( + Param(key = AnalyticsEvent.ParamKeys.IS_NEW_USER, value = isNewUser.toString()), + Param(key = AnalyticsEvent.ParamKeys.USER_STATUS, value = userStatus), + ), + ), + ) + +fun AnalyticsHelper.logAppBackgrounded() = + logEvent( + AnalyticsEvent( + type = AnalyticsEvent.Types.APP_BACKGROUNDED + ) + ) + +fun AnalyticsHelper.logAppForegrounded() = + logEvent( + AnalyticsEvent( + type = AnalyticsEvent.Types.APP_FOREGROUNDED + ) + ) + +// 사용자 상호작용 이벤트 +fun AnalyticsHelper.logButtonClick(buttonName: String, screenName: String? = null) = + logEvent( + AnalyticsEvent( + type = AnalyticsEvent.Types.BUTTON_CLICK, + extras = listOf( + Param(key = AnalyticsEvent.ParamKeys.BUTTON_NAME, value = buttonName), + ).let { params -> + if (screenName != null) { + params + Param(key = AnalyticsEvent.ParamKeys.SCREEN_NAME, value = screenName) + } else { + params + } + } + ) + ) + +// 오류 및 예외 이벤트 +fun AnalyticsHelper.logError(errorType: String, errorMessage: String, screenName: String? = null) = + logEvent( + AnalyticsEvent( + type = AnalyticsEvent.Types.ERROR_OCCURRED, + extras = listOf( + Param(key = AnalyticsEvent.ParamKeys.ERROR_TYPE, value = errorType), + Param(key = AnalyticsEvent.ParamKeys.ERROR_MESSAGE, value = errorMessage), + ).let { params -> + if (screenName != null) { + params + Param(key = AnalyticsEvent.ParamKeys.SCREEN_NAME, value = screenName) + } else { + params + } + } + ) + ) diff --git a/core/analytics/src/main/java/com/combo/runcombi/analytics/AnalyticsHelper.kt b/core/analytics/src/main/java/com/combo/runcombi/analytics/AnalyticsHelper.kt new file mode 100644 index 0000000..75dab94 --- /dev/null +++ b/core/analytics/src/main/java/com/combo/runcombi/analytics/AnalyticsHelper.kt @@ -0,0 +1,5 @@ +package com.combo.runcombi.analytics + +interface AnalyticsHelper { + fun logEvent(event: AnalyticsEvent) +} diff --git a/core/analytics/src/main/java/com/combo/runcombi/analytics/AnalyticsModule.kt b/core/analytics/src/main/java/com/combo/runcombi/analytics/AnalyticsModule.kt new file mode 100644 index 0000000..f1b4ccb --- /dev/null +++ b/core/analytics/src/main/java/com/combo/runcombi/analytics/AnalyticsModule.kt @@ -0,0 +1,15 @@ +package com.combo.runcombi.analytics + +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent + +@Module +@InstallIn(SingletonComponent::class) +internal abstract class AnalyticsModule { + + @Binds + abstract fun bindsAnalyticsHelper(analyticsHelperImpl: FirebaseAnalyticsHelper): AnalyticsHelper + +} diff --git a/core/analytics/src/main/java/com/combo/runcombi/analytics/FirebaseAnalyticsHelper.kt b/core/analytics/src/main/java/com/combo/runcombi/analytics/FirebaseAnalyticsHelper.kt new file mode 100644 index 0000000..776f683 --- /dev/null +++ b/core/analytics/src/main/java/com/combo/runcombi/analytics/FirebaseAnalyticsHelper.kt @@ -0,0 +1,32 @@ +package com.combo.runcombi.analytics + +import android.annotation.SuppressLint +import android.content.Context +import android.os.Bundle +import com.google.firebase.analytics.FirebaseAnalytics +import dagger.hilt.android.qualifiers.ApplicationContext +import javax.inject.Inject +import javax.inject.Singleton + +@SuppressLint("MissingPermission") +@Singleton +internal class FirebaseAnalyticsHelper @Inject constructor( + @ApplicationContext private val context: Context +) : AnalyticsHelper { + + private val firebaseAnalytics: FirebaseAnalytics by lazy { + FirebaseAnalytics.getInstance(context) + } + + override fun logEvent(event: AnalyticsEvent) { + val bundle = Bundle().apply { + putString(FirebaseAnalytics.Param.ITEM_ID, event.type) + + event.extras.forEach { param -> + putString(param.key, param.value) + } + } + + firebaseAnalytics.logEvent(event.type, bundle) + } +} diff --git a/core/analytics/src/main/java/com/combo/runcombi/analytics/NoOpAnalyticsHelper.kt b/core/analytics/src/main/java/com/combo/runcombi/analytics/NoOpAnalyticsHelper.kt new file mode 100644 index 0000000..9c82344 --- /dev/null +++ b/core/analytics/src/main/java/com/combo/runcombi/analytics/NoOpAnalyticsHelper.kt @@ -0,0 +1,5 @@ +package com.combo.runcombi.analytics + +class NoOpAnalyticsHelper : AnalyticsHelper { + override fun logEvent(event: AnalyticsEvent) = Unit +} diff --git a/core/analytics/src/main/java/com/combo/runcombi/analytics/StubAnalyticsHelper.kt b/core/analytics/src/main/java/com/combo/runcombi/analytics/StubAnalyticsHelper.kt new file mode 100644 index 0000000..0a9e533 --- /dev/null +++ b/core/analytics/src/main/java/com/combo/runcombi/analytics/StubAnalyticsHelper.kt @@ -0,0 +1,18 @@ +package com.combo.runcombi.analytics + +import android.util.Log +import javax.inject.Inject +import javax.inject.Singleton + +private const val TAG = "Analytics" + +@Singleton +internal class StubAnalyticsHelper @Inject constructor() : AnalyticsHelper { + override fun logEvent(event: AnalyticsEvent) { + val extrasStr = if (event.extras.isNotEmpty()) { + event.extras.joinToString(", ") { "${it.key}=${it.value}" } + } else "" + + Log.d(TAG, "[${event.type}] $extrasStr") + } +} diff --git a/feature/login/src/main/java/com/combo/runcombi/feature/login/LoginScreen.kt b/feature/login/src/main/java/com/combo/runcombi/feature/login/LoginScreen.kt index 1f805ce..64ce3c9 100644 --- a/feature/login/src/main/java/com/combo/runcombi/feature/login/LoginScreen.kt +++ b/feature/login/src/main/java/com/combo/runcombi/feature/login/LoginScreen.kt @@ -24,10 +24,12 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel +import com.combo.runcombi.analytics.logButtonClick +import com.combo.runcombi.analytics.logScreenView import com.combo.runcombi.core.designsystem.component.StableImage -import com.combo.runcombi.ui.ext.screenDefaultPadding import com.combo.runcombi.core.designsystem.theme.RunCombiTypography import com.combo.runcombi.domain.user.model.MemberStatus +import com.combo.runcombi.ui.ext.screenDefaultPadding import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch @@ -71,9 +73,16 @@ private fun rememberLoginManager(): LoginManager { @Composable fun LoginScreen( - modifier: Modifier = Modifier, onKakaoLoginClick: () -> Unit, + modifier: Modifier = Modifier, + viewModel: LoginViewModel = hiltViewModel(), ) { + val analyticsHelper = viewModel.analyticsHelper + + LaunchedEffect(Unit) { + analyticsHelper.logScreenView("LoginScreen") + } + Box( modifier = modifier .fillMaxSize() @@ -92,7 +101,11 @@ fun LoginScreen( .align(Alignment.BottomCenter) ) { Button( - onClick = onKakaoLoginClick, + onClick = { + // 카카오 로그인 버튼 클릭 이벤트 로깅 + analyticsHelper.logButtonClick("kakao_login", "LoginScreen") + onKakaoLoginClick() + }, colors = ButtonDefaults.buttonColors( containerColor = Color(0xFFFEE102), contentColor = Color.Black diff --git a/feature/login/src/main/java/com/combo/runcombi/feature/login/LoginViewModel.kt b/feature/login/src/main/java/com/combo/runcombi/feature/login/LoginViewModel.kt index 58e2d96..31fd686 100644 --- a/feature/login/src/main/java/com/combo/runcombi/feature/login/LoginViewModel.kt +++ b/feature/login/src/main/java/com/combo/runcombi/feature/login/LoginViewModel.kt @@ -2,6 +2,7 @@ package com.combo.runcombi.feature.login import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.combo.runcombi.analytics.AnalyticsHelper import com.combo.runcombi.auth.usecase.LoginUseCase import com.combo.runcombi.common.DomainResult import com.combo.runcombi.domain.user.model.MemberStatus @@ -18,7 +19,8 @@ import javax.inject.Inject @HiltViewModel class LoginViewModel @Inject constructor( private val loginUseCase: LoginUseCase, - private val getUserInfoUseCase: GetUserInfoUseCase + private val getUserInfoUseCase: GetUserInfoUseCase, + val analyticsHelper: AnalyticsHelper ) : ViewModel() { private val _eventFlow = MutableSharedFlow() val eventFlow = _eventFlow.asSharedFlow() diff --git a/feature/setting/src/main/java/com/combo/runcombi/setting/screen/AnnouncementDetailScreen.kt b/feature/setting/src/main/java/com/combo/runcombi/setting/screen/AnnouncementDetailScreen.kt index 4aaef99..41adbd2 100644 --- a/feature/setting/src/main/java/com/combo/runcombi/setting/screen/AnnouncementDetailScreen.kt +++ b/feature/setting/src/main/java/com/combo/runcombi/setting/screen/AnnouncementDetailScreen.kt @@ -40,6 +40,8 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.combo.runcombi.analytics.AnalyticsEvent +import com.combo.runcombi.analytics.logButtonClick import com.combo.runcombi.core.designsystem.component.NetworkImage import com.combo.runcombi.core.designsystem.component.RunCombiAppTopBar import com.combo.runcombi.core.designsystem.component.RunCombiButton @@ -100,7 +102,9 @@ fun AnnouncementDetailScreen( .fillMaxSize() .background(Grey01) ) { - val onApplyClick: (String) -> Unit = { url -> viewModel.openEventApplyUrl(url) } + val onApplyClick: (String) -> Unit = { url -> + viewModel.analyticsHelper.logButtonClick("onApplyClick", "AnnouncementDetailScreen") + viewModel.openEventApplyUrl(url) } Column(modifier = Modifier.fillMaxSize()) { Box( diff --git a/feature/setting/src/main/java/com/combo/runcombi/setting/viewmodel/AnnouncementDetailViewModel.kt b/feature/setting/src/main/java/com/combo/runcombi/setting/viewmodel/AnnouncementDetailViewModel.kt index 5ca0830..425ea08 100644 --- a/feature/setting/src/main/java/com/combo/runcombi/setting/viewmodel/AnnouncementDetailViewModel.kt +++ b/feature/setting/src/main/java/com/combo/runcombi/setting/viewmodel/AnnouncementDetailViewModel.kt @@ -2,6 +2,7 @@ package com.combo.runcombi.setting.viewmodel import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.combo.runcombi.analytics.AnalyticsHelper import com.combo.runcombi.common.DomainResult import com.combo.runcombi.setting.model.AnnouncementDetailEvent import com.combo.runcombi.setting.model.AnnouncementDetailUiState @@ -22,6 +23,7 @@ import javax.inject.Inject @HiltViewModel class AnnouncementDetailViewModel @Inject constructor( private val getAnnouncementDetailUseCase: GetAnnouncementDetailUseCase, + val analyticsHelper: AnalyticsHelper ) : ViewModel() { private val _uiState = MutableStateFlow(AnnouncementDetailUiState()) val uiState: StateFlow = _uiState.asStateFlow() diff --git a/feature/signup/src/main/java/com/combo/runcombi/signup/viewmodel/CompleteViewModel.kt b/feature/signup/src/main/java/com/combo/runcombi/signup/viewmodel/CompleteViewModel.kt new file mode 100644 index 0000000..23c68a5 --- /dev/null +++ b/feature/signup/src/main/java/com/combo/runcombi/signup/viewmodel/CompleteViewModel.kt @@ -0,0 +1,12 @@ +package com.combo.runcombi.signup.viewmodel + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.combo.runcombi.analytics.AnalyticsHelper +import dagger.hilt.android.lifecycle.HiltViewModel +import javax.inject.Inject + +@HiltViewModel +class CompleteViewModel @Inject constructor( + val analyticsHelper: AnalyticsHelper, +) : ViewModel() diff --git a/feature/walk/src/main/java/com/combo/runcombi/walk/screen/WalkMainScreen.kt b/feature/walk/src/main/java/com/combo/runcombi/walk/screen/WalkMainScreen.kt index c0d05fd..7b2c710 100644 --- a/feature/walk/src/main/java/com/combo/runcombi/walk/screen/WalkMainScreen.kt +++ b/feature/walk/src/main/java/com/combo/runcombi/walk/screen/WalkMainScreen.kt @@ -38,6 +38,7 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.core.net.toUri import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.combo.runcombi.analytics.logScreenView import com.combo.runcombi.core.designsystem.component.NetworkImage import com.combo.runcombi.core.designsystem.component.RunCombiBottomSheet import com.combo.runcombi.core.designsystem.component.StableImage @@ -78,7 +79,6 @@ import com.google.maps.android.compose.MapUiSettings import com.google.maps.android.compose.rememberCameraPositionState import kotlinx.coroutines.flow.collectLatest - @OptIn(ExperimentalPermissionsApi::class) @SuppressLint("MissingPermission") @Composable @@ -92,8 +92,11 @@ fun WalkMainScreen( val uiState by walkMainViewModel.uiState.collectAsStateWithLifecycle() val locationPermissionState = rememberPermissionState(Manifest.permission.ACCESS_FINE_LOCATION) var showPermissionSettingSheet by remember { mutableStateOf(false) } + val analyticsHelper = walkMainViewModel.analyticsHelper LaunchedEffect(Unit) { + analyticsHelper.logScreenView("WalkMainScreen") + if (!locationPermissionState.status.isGranted) { locationPermissionState.launchPermissionRequest() } diff --git a/feature/walk/src/main/java/com/combo/runcombi/walk/screen/WalkTrackingScreen.kt b/feature/walk/src/main/java/com/combo/runcombi/walk/screen/WalkTrackingScreen.kt index f254070..eaccf71 100644 --- a/feature/walk/src/main/java/com/combo/runcombi/walk/screen/WalkTrackingScreen.kt +++ b/feature/walk/src/main/java/com/combo/runcombi/walk/screen/WalkTrackingScreen.kt @@ -1,4 +1,5 @@ -@file:OptIn(ExperimentalFoundationApi::class, ExperimentalFoundationApi::class, +@file:OptIn( + ExperimentalFoundationApi::class, ExperimentalFoundationApi::class, ExperimentalFoundationApi::class, ExperimentalFoundationApi::class ) @@ -45,6 +46,8 @@ import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.combo.runcombi.analytics.logScreenView +import com.combo.runcombi.analytics.logWalkCompleted import com.combo.runcombi.core.designsystem.component.NetworkImage import com.combo.runcombi.core.designsystem.component.RunCombiBottomSheet import com.combo.runcombi.core.designsystem.component.StableImage @@ -91,6 +94,7 @@ fun WalkTrackingScreen( walkRecordViewModel: WalkTrackingViewModel = hiltViewModel(), ) { val context = LocalContext.current + val analyticsHelper = walkRecordViewModel.analyticsHelper val uiState by walkRecordViewModel.uiState.collectAsStateWithLifecycle() val isPaused = uiState.isPaused val fusedLocationClient = remember { LocationServices.getFusedLocationProviderClient(context) } @@ -113,6 +117,8 @@ fun WalkTrackingScreen( LaunchedEffect(isInitialized.value) { if (!isInitialized.value) { + analyticsHelper.logScreenView("WalkTrackingScreen") + walkMainViewModel.startRun() val member = walkMainViewModel.walkData.value.member @@ -169,6 +175,11 @@ fun WalkTrackingScreen( onAccept = { when (showSheet.value) { BottomSheetType.FINISH -> { + // 산책 완료 이벤트 로깅 + val duration = FormatUtils.formatTime(uiState.time) + val distance = String.format("%.2f", uiState.distance / 1000.0) + analyticsHelper.logWalkCompleted(duration, "${distance}km") + walkMainViewModel.setResultData( time = uiState.time, distance = uiState.distance, diff --git a/feature/walk/src/main/java/com/combo/runcombi/walk/viewmodel/WalkMainViewModel.kt b/feature/walk/src/main/java/com/combo/runcombi/walk/viewmodel/WalkMainViewModel.kt index d4067c8..175c0b9 100644 --- a/feature/walk/src/main/java/com/combo/runcombi/walk/viewmodel/WalkMainViewModel.kt +++ b/feature/walk/src/main/java/com/combo/runcombi/walk/viewmodel/WalkMainViewModel.kt @@ -2,6 +2,7 @@ package com.combo.runcombi.walk.viewmodel import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.combo.runcombi.analytics.AnalyticsHelper import com.combo.runcombi.common.DomainResult import com.combo.runcombi.domain.user.model.Member import com.combo.runcombi.domain.user.model.Pet @@ -30,6 +31,7 @@ import com.combo.runcombi.walk.usecase.StartRunUseCase class WalkMainViewModel @Inject constructor( private val getUserInfoUseCase: GetUserInfoUseCase, private val startRunUseCase: StartRunUseCase, + val analyticsHelper: AnalyticsHelper ) : ViewModel() { private val _uiState = MutableStateFlow(WalkMainUiState()) val uiState: StateFlow = _uiState diff --git a/feature/walk/src/main/java/com/combo/runcombi/walk/viewmodel/WalkTrackingViewModel.kt b/feature/walk/src/main/java/com/combo/runcombi/walk/viewmodel/WalkTrackingViewModel.kt index 830fada..a7bfa48 100644 --- a/feature/walk/src/main/java/com/combo/runcombi/walk/viewmodel/WalkTrackingViewModel.kt +++ b/feature/walk/src/main/java/com/combo/runcombi/walk/viewmodel/WalkTrackingViewModel.kt @@ -2,6 +2,7 @@ package com.combo.runcombi.walk.viewmodel import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.combo.runcombi.analytics.AnalyticsHelper import com.combo.runcombi.common.DomainResult import com.combo.runcombi.walk.model.BottomSheetType import com.combo.runcombi.walk.model.ExerciseType @@ -34,6 +35,7 @@ class WalkTrackingViewModel @Inject constructor( private val updateWalkRecordUseCase: UpdateWalkRecordUseCase, private val calculatePetCalorieUseCase: CalculatePetCalorieUseCase, private val calculateMemberCalorieUseCase: CalculateMemberCalorieUseCase, + val analyticsHelper: AnalyticsHelper, ) : ViewModel() { private val _uiState = MutableStateFlow(WalkUiState()) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 6beb001..ff0f000 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -61,6 +61,8 @@ espressoCore = "3.6.1" mockk = "1.13.11" v2User = "2.21.4" playServicesLocation = "21.3.0" +runtimeAndroid = "1.8.3" +playServicesMeasurementApi = "23.0.0" [libraries] # AndroidX diff --git a/settings.gradle.kts b/settings.gradle.kts index 75bcb44..48b70b1 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -50,3 +50,4 @@ include(":core:domain:history") include(":core:data:history") include(":core:domain:setting") include(":core:data:setting") +include(":core:analytics") From 9cbadb149fa226cc8222a0ca6eefd900ddcdd80b Mon Sep 17 00:00:00 2001 From: changs97 Date: Fri, 15 Aug 2025 13:26:04 +0900 Subject: [PATCH 26/27] =?UTF-8?q?feat:=20ci/cd=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/android.yml | 113 +++++++++++++++++++--------------- 1 file changed, 65 insertions(+), 48 deletions(-) diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index 269068e..32da721 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -8,19 +8,48 @@ on: permissions: contents: write +# 워크플로우 최적화 +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: build: - runs-on: ubuntu-latest + + # 빌드 캐시 설정 + env: + GRADLE_OPTS: -Dorg.gradle.daemon=false -Dorg.gradle.parallel=true -Dorg.gradle.configureondemand=true -Dorg.gradle.jvmargs=-Xmx4g -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError steps: - uses: actions/checkout@v4 - - name: set up JDK 17 + + - name: Set up JDK 17 uses: actions/setup-java@v4 with: java-version: '17' distribution: 'temurin' cache: gradle + + - name: Setup Gradle Cache + uses: actions/cache@v4 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} + restore-keys: | + ${{ runner.os }}-gradle- + + - name: Setup Android SDK Cache + uses: actions/cache@v4 + with: + path: | + ~/.android/build-cache + ~/.android/cache + key: ${{ runner.os }}-android-${{ hashFiles('**/gradle.properties', '**/local.properties') }} + restore-keys: | + ${{ runner.os }}-android- - name: Grant execute permission for gradlew run: chmod +x gradlew @@ -117,17 +146,15 @@ jobs: echo "version_code=$VERSION_CODE" >> $GITHUB_OUTPUT echo "Extracted version: $VERSION_NAME ($VERSION_CODE)" - - name: Get timestamp - id: timestamp - run: echo "timestamp=$(date +%s)" >> $GITHUB_OUTPUT + - name: Build with Gradle - run: ./gradlew build + run: ./gradlew build --parallel --max-workers=2 --daemon - name: Build Release APK run: | echo "Starting assembleRelease..." - ./gradlew assembleRelease --info --stacktrace + ./gradlew assembleRelease --parallel --max-workers=2 --daemon --info --stacktrace echo "assembleRelease completed with exit code: $?" echo "Checking if APK was generated..." if [ -f "app/build/outputs/apk/release/app-release.apk" ]; then @@ -199,45 +226,35 @@ jobs: APK: Prod Release Version - - name: action-slack - uses: 8398a7/action-slack@v3 - with: - status: ${{ job.status }} - author_name: RunCombi Android CI - fields: repo,message,commit,author,action,eventName,ref,workflow,job,took - if_mention: failure,cancelled - custom_payload: | - { - "attachments": [{ - "color": "${{ job.status == 'success' && '#36a64f' || '#ff0000' }}", - "title": "🎉 RunCombi Android 빌드 완료", - "fields": [ - { - "title": "📱 앱 버전", - "value": "v${{ steps.app_version.outputs.version_name }} (${{ steps.app_version.outputs.version_code }})", - "short": true - }, - { - "title": "🔗 PR 제목", - "value": "${{ github.event.pull_request.title }}", - "short": true - }, - { - "title": "📝 PR 내용", - "value": "${{ github.event.pull_request.body || '내용이 없습니다.' }}", - "short": false - }, - { - "title": "🚀 GitHub Release", - "value": "https://github.com/${{ github.repository }}/releases/tag/v${{ steps.app_version.outputs.version_name }}", - "short": false - } - ], - "footer": "RunCombi Android CI", - "ts": "${{ env.TIMESTAMP }}" - }] - } + - name: If Success, Send notification on Slack + if: ${{success()}} + uses: rtCamp/action-slack-notify@v2 + env: + SLACK_COLOR: '#60E0C5' + SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }} + SLACK_TITLE: 'RunCombi Android 빌드 성공 ✅' + MSG_MINIMAL: true + SLACK_USERNAME: RunCombi Android CI + SLACK_MESSAGE: | + 🎉 RunCombi Android 빌드 성공! + + 📱 **앱 버전**: v${{ steps.app_version.outputs.version_name }} (${{ steps.app_version.outputs.version_code }}) + 🔗 **PR 제목**: ${{ github.event.pull_request.title }} + 📝 **PR 내용**: ${{ github.event.pull_request.body || '내용이 없습니다.' }} + 🚀 **GitHub Release**: https://github.com/${{ github.repository }}/releases/tag/v${{ steps.app_version.outputs.version_name }} + + - name: If Fail, Send notification on Slack + if: ${{failure()}} + uses: rtCamp/action-slack-notify@v2 env: - SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} - TIMESTAMP: ${{ steps.timestamp.outputs.timestamp }} - if: always() + SLACK_COLOR: '#ff0000' + SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }} + SLACK_TITLE: 'RunCombi Android 빌드 실패 ❌' + MSG_MINIMAL: true + SLACK_USERNAME: RunCombi Android CI + SLACK_MESSAGE: | + ❌ RunCombi Android 빌드 실패 - 확인이 필요합니다! + + 📱 **앱 버전**: v${{ steps.app_version.outputs.version_name }} (${{ steps.app_version.outputs.version_code }}) + 🔗 **PR 제목**: ${{ github.event.pull_request.title }} + 📝 **PR 내용**: ${{ github.event.pull_request.body || '내용이 없습니다.' }} From c38501e8e76cb3b155b35a4384197544c47668a7 Mon Sep 17 00:00:00 2001 From: changs97 Date: Fri, 15 Aug 2025 13:26:38 +0900 Subject: [PATCH 27/27] =?UTF-8?q?feat:=20ci/cd=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/android.yml | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index 32da721..8aa920c 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -243,18 +243,4 @@ jobs: 📝 **PR 내용**: ${{ github.event.pull_request.body || '내용이 없습니다.' }} 🚀 **GitHub Release**: https://github.com/${{ github.repository }}/releases/tag/v${{ steps.app_version.outputs.version_name }} - - name: If Fail, Send notification on Slack - if: ${{failure()}} - uses: rtCamp/action-slack-notify@v2 - env: - SLACK_COLOR: '#ff0000' - SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }} - SLACK_TITLE: 'RunCombi Android 빌드 실패 ❌' - MSG_MINIMAL: true - SLACK_USERNAME: RunCombi Android CI - SLACK_MESSAGE: | - ❌ RunCombi Android 빌드 실패 - 확인이 필요합니다! - - 📱 **앱 버전**: v${{ steps.app_version.outputs.version_name }} (${{ steps.app_version.outputs.version_code }}) - 🔗 **PR 제목**: ${{ github.event.pull_request.title }} - 📝 **PR 내용**: ${{ github.event.pull_request.body || '내용이 없습니다.' }} +