Skip to content

Commit f3fb8c0

Browse files
authored
Merge pull request #246 from Team-Clody/develop
[TEST] 테스트 앱 배포를 진행합니다.
2 parents 515c6ff + 4f5da1f commit f3fb8c0

File tree

14 files changed

+332
-15
lines changed

14 files changed

+332
-15
lines changed

.github/PULL_REQUEST_TEMPLATE.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
## 📌 ISSUE
22
<!--이슈 번호 및 제목을 적어주세요!-->
3+
closed #<issue_number>
34

45
## 📄 Work Description
56
<!--어떤 작업을 했는지 작성해주세요!-->

.github/workflows/android_cd.yml

Lines changed: 56 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,24 @@ jobs:
4141
- name: Change gradlew permissions
4242
run: chmod +x gradlew
4343

44+
# Install Firebase CLI
45+
- name: Install Firebase CLI
46+
run: curl -sL https://firebase.tools | bash
47+
4448
# google-services.json
4549
- name: Decode google-services.json
4650
env:
4751
FIREBASE_SECRET: ${{ secrets.FIREBASE_SECRET }}
4852
run: echo $FIREBASE_SECRET | base64 --decode > app/google-services.json
4953

54+
# keystore 복호화
55+
- name: Decode keystore file
56+
env:
57+
STORE_FILE: ${{ secrets.STORE_FILE }}
58+
run: |
59+
mkdir -p app/keystore
60+
echo "$STORE_FILE" | base64 --decode > app/keystore/clody_release.jks
61+
5062
# local.properties
5163
- name: Generate local.properties
5264
env:
@@ -55,33 +67,68 @@ jobs:
5567
AMPLITUDE_API_KEY: ${{ secrets.AMPLITUDE_API_KEY }}
5668
GOOGLE_ADMOB_APP_ID: ${{ secrets.GOOGLE_ADMOB_APP_ID }}
5769
GOOGLE_ADMOB_UNIT_ID: ${{ secrets.GOOGLE_ADMOB_UNIT_ID }}
70+
STORE_PASSWORD: ${{ secrets.STORE_PASSWORD }}
71+
KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
72+
KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
5873
run: |
5974
echo "baseUrl=$BASE_URL" >> local.properties
6075
echo "kakao.api.key=$KAKAO_API_KEY" >> local.properties
6176
echo "amplitude.api.key=$AMPLITUDE_API_KEY" >> local.properties
6277
echo "googleAdmob.app.id=$GOOGLE_ADMOB_APP_ID" >> local.properties
6378
echo "googleAdmob.unit.id=$GOOGLE_ADMOB_UNIT_ID" >> local.properties
79+
echo "storeFile=keystore/clody_release.jks" >> local.properties
80+
echo "storePassword=$STORE_PASSWORD" >> local.properties
81+
echo "keyAlias=$KEY_ALIAS" >> local.properties
82+
echo "keyPassword=$KEY_PASSWORD" >> local.properties
6483
65-
# Release AAB 빌드
66-
- name: Build Release AAB
67-
run: ./gradlew bundleRelease --stacktrace
84+
# Release APK 빌드
85+
- name: Build Release APK
86+
run: ./gradlew assembleRelease --stacktrace
6887

69-
# Firebase 인증
88+
# Set up Firebase Service Account Credentials
7089
- name: Set up Firebase Service Account Credentials
7190
env:
7291
GOOGLE_APPLICATION_CREDENTIALS_JSON: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS_JSON }}
7392
run: |
7493
echo "$GOOGLE_APPLICATION_CREDENTIALS_JSON" | base64 --decode > $HOME/firebase-credentials.json
94+
echo "🔥 Firebase Credentials JSON 생성 완료!"
95+
ls -l $HOME/firebase-credentials.json
96+
export GOOGLE_APPLICATION_CREDENTIALS=$HOME/firebase-credentials.json
97+
echo "GOOGLE_APPLICATION_CREDENTIALS=$GOOGLE_APPLICATION_CREDENTIALS"
98+
99+
# Firebase CLI 인증 확인
100+
- name: Check Firebase CLI Authentication
101+
run: |
75102
export GOOGLE_APPLICATION_CREDENTIALS=$HOME/firebase-credentials.json
76103
77-
# Firebase App Distribution
78-
- name: Upload AAB to Firebase App Distribution
104+
echo "📌 GOOGLE_APPLICATION_CREDENTIALS 설정 값:"
105+
echo $GOOGLE_APPLICATION_CREDENTIALS
106+
ls -l $GOOGLE_APPLICATION_CREDENTIALS
107+
108+
echo "📌 현재 Firebase 프로젝트 목록 확인:"
109+
firebase projects:list || (echo "❌ Firebase 인증 실패!"; exit 1)
110+
111+
# Firebase App Distribution Upload
112+
- name: Upload APK to Firebase App Distribution
79113
env:
114+
GOOGLE_APPLICATION_CREDENTIALS: $HOME/firebase-credentials.json
80115
FIREBASE_APP_ID: ${{ secrets.FIREBASE_APP_ID }}
81116
run: |
82-
firebase appdistribution:distribute app/build/outputs/bundle/release/app-release.aab \
117+
echo "🔥 FIREBASE_APP_ID 확인: $FIREBASE_APP_ID"
118+
119+
# 만약 FIREBASE_APP_ID가 없으면 에러 출력 후 종료
120+
if [ -z "$FIREBASE_APP_ID" ]; then
121+
echo "❌ ERROR: FIREBASE_APP_ID가 설정되지 않았습니다. GitHub Secrets에서 확인하세요."
122+
exit 1
123+
fi
124+
125+
# GOOGLE_APPLICATION_CREDENTIALS 재 설정
126+
export GOOGLE_APPLICATION_CREDENTIALS=$HOME/firebase-credentials.json
127+
echo "GOOGLE_APPLICATION_CREDENTIALS=$GOOGLE_APPLICATION_CREDENTIALS"
128+
129+
firebase appdistribution:distribute app/build/outputs/apk/release/app-release.apk \
83130
--app "$FIREBASE_APP_ID" \
84-
--release-notes "🍀 새로운 테스트 버전이 업로드되었습니다!" \
131+
--release-notes "🍀 새로운 테스트 버전이 업로드되었습니다~" \
85132
--groups "clody-tester-group"
86133
87134
# Discord Notification
@@ -91,5 +138,5 @@ jobs:
91138
run: |
92139
curl -H "Content-Type: application/json" \
93140
-X POST \
94-
-d '{"content": "🍀 새로운 테스트 버전이 업로드되었습니다!"}' \
141+
-d '{"content": "🍀 새로운 테스트 버전이 업로드되었습니다~\nAPK 다운로드: https://appdistribution.firebase.google.com"}' \
95142
$DISCORD_WEBHOOK_URL

.github/workflows/android_ci.yml

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,14 @@ jobs:
6363
FIREBASE_SECRET: ${{ secrets.FIREBASE_SECRET }} # base64로 암호화된 json 사용
6464
run: echo $FIREBASE_SECRET | base64 --decode > app/google-services.json
6565

66+
# keystore 복호화
67+
- name: Decode keystore file
68+
env:
69+
STORE_FILE_BASE: ${{ secrets.STORE_FILE }}
70+
run: |
71+
mkdir -p keystore
72+
echo "$STORE_FILE" | base64 --decode > keystore/clody_release.jks
73+
6674
# local.properties 생성
6775
- name: Generate local.properties
6876
env:
@@ -71,13 +79,19 @@ jobs:
7179
AMPLITUDE_API_KEY: ${{ secrets.AMPLITUDE_API_KEY }}
7280
GOOGLE_ADMOB_APP_ID: ${{ secrets.GOOGLE_ADMOB_APP_ID }}
7381
GOOGLE_ADMOB_UNIT_ID: ${{ secrets.GOOGLE_ADMOB_UNIT_ID }}
82+
STORE_PASSWORD: ${{ secrets.STORE_PASSWORD }}
83+
KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
84+
KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
7485
run: |
7586
echo "baseUrl=$BASE_URL" >> local.properties
7687
echo "kakao.api.key=$KAKAO_API_KEY" >> local.properties
7788
echo "amplitude.api.key=$AMPLITUDE_API_KEY" >> local.properties
7889
echo "googleAdmob.app.id=$GOOGLE_ADMOB_APP_ID" >> local.properties
7990
echo "googleAdmob.unit.id=$GOOGLE_ADMOB_UNIT_ID" >> local.properties
80-
91+
echo "storeFile=keystore/clody_release.jks" >> local.properties
92+
echo "storePassword=$STORE_PASSWORD" >> local.properties
93+
echo "keyAlias=$KEY_ALIAS" >> local.properties
94+
echo "keyPassword=$KEY_PASSWORD" >> local.properties
8195
8296
# ------- Build & Lint -------
8397
- name: Run Lint and Build

app/build.gradle.kts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ android {
2525
applicationId = "com.sopt.clody"
2626
minSdk = 28
2727
targetSdk = 35
28-
versionCode = 18
29-
versionName = "1.0.6"
28+
versionCode = 23
29+
versionName = "1.1.0"
3030
val kakaoApiKey: String = properties.getProperty("kakao.api.key")
3131
val amplitudeApiKey: String = properties.getProperty("amplitude.api.key")
3232
val googleAdmobAppId: String = properties.getProperty("GOOGLE_ADMOB_APP_ID", "")
@@ -40,6 +40,15 @@ android {
4040
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
4141
}
4242

43+
signingConfigs {
44+
create("release") {
45+
storeFile = file(properties.getProperty("storeFile") ?: "")
46+
storePassword = properties.getProperty("storePassword") ?: ""
47+
keyAlias = properties.getProperty("keyAlias") ?: ""
48+
keyPassword = properties.getProperty("keyPassword") ?: ""
49+
}
50+
}
51+
4352
buildTypes {
4453
debug {
4554
isMinifyEnabled = false
@@ -54,6 +63,7 @@ android {
5463
getDefaultProguardFile("proguard-android-optimize.txt"),
5564
"proguard-rules.pro",
5665
)
66+
signingConfig = signingConfigs.getByName("release")
5767
}
5868
}
5969
compileOptions {
@@ -70,7 +80,6 @@ android {
7080
}
7181

7282
dependencies {
73-
7483
// Test
7584
testImplementation(libs.junit)
7685
androidTestImplementation(platform(libs.compose.bom))
@@ -99,6 +108,7 @@ dependencies {
99108
// FireBase
100109
implementation(platform(libs.firebase.bom))
101110
implementation(libs.bundles.firebase)
111+
implementation(libs.firebase.config.ktx)
102112

103113
// Amplitude
104114
implementation(libs.amplitude)
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package com.sopt.clody.data.remote.appupdate
2+
3+
import com.sopt.clody.data.remote.datasource.RemoteConfigDataSource
4+
import com.sopt.clody.domain.appupdate.AppUpdateChecker
5+
import com.sopt.clody.domain.model.AppUpdateState
6+
import com.sopt.clody.domain.util.VersionComparator
7+
import javax.inject.Inject
8+
9+
class AppUpdateCheckerImpl @Inject constructor(
10+
private val remoteConfigDataSource: RemoteConfigDataSource,
11+
) : AppUpdateChecker {
12+
13+
override suspend fun getAppUpdateState(currentVersion: String): AppUpdateState {
14+
remoteConfigDataSource.fetch()
15+
16+
val latestVersion = remoteConfigDataSource.getLatestVersion()
17+
val minimumVersion = remoteConfigDataSource.getMinimumVersion()
18+
19+
return when {
20+
VersionComparator.compare(currentVersion, minimumVersion) < 0 -> {
21+
AppUpdateState.HardUpdate(latestVersion)
22+
}
23+
24+
VersionComparator.compare(currentVersion, latestVersion) < 0 -> {
25+
AppUpdateState.SoftUpdate(latestVersion)
26+
}
27+
28+
else -> {
29+
AppUpdateState.Latest
30+
}
31+
}
32+
}
33+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package com.sopt.clody.data.remote.datasource
2+
3+
import com.google.firebase.remoteconfig.FirebaseRemoteConfig
4+
import com.sopt.clody.BuildConfig
5+
import kotlinx.coroutines.tasks.await
6+
import javax.inject.Inject
7+
import javax.inject.Singleton
8+
9+
@Singleton
10+
class RemoteConfigDataSource @Inject constructor(
11+
private val remoteConfig: FirebaseRemoteConfig,
12+
) {
13+
suspend fun fetch() {
14+
remoteConfig.fetchAndActivate().await()
15+
}
16+
17+
fun getLatestVersion(): String =
18+
remoteConfig.getString(KEY_LATEST_VERSION).ifEmpty { BuildConfig.VERSION_NAME }
19+
20+
fun getMinimumVersion(): String =
21+
remoteConfig.getString(KEY_MINIMUM_VERSION).ifEmpty { BuildConfig.VERSION_NAME }
22+
23+
companion object {
24+
private const val KEY_LATEST_VERSION = "latest_version"
25+
private const val KEY_MINIMUM_VERSION = "min_required_version"
26+
}
27+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package com.sopt.clody.di
2+
3+
import com.google.firebase.remoteconfig.FirebaseRemoteConfig
4+
import com.sopt.clody.data.remote.appupdate.AppUpdateCheckerImpl
5+
import com.sopt.clody.data.remote.datasource.RemoteConfigDataSource
6+
import com.sopt.clody.domain.appupdate.AppUpdateChecker
7+
import dagger.Module
8+
import dagger.Provides
9+
import dagger.hilt.InstallIn
10+
import dagger.hilt.components.SingletonComponent
11+
import javax.inject.Singleton
12+
13+
@Module
14+
@InstallIn(SingletonComponent::class)
15+
object AppUpdateModule {
16+
17+
@Provides
18+
@Singleton
19+
fun provideFirebaseRemoteConfig(): FirebaseRemoteConfig =
20+
FirebaseRemoteConfig.getInstance()
21+
22+
@Provides
23+
@Singleton
24+
fun provideAppUpdateChecker(
25+
remoteConfigDataSource: RemoteConfigDataSource,
26+
): AppUpdateChecker = AppUpdateCheckerImpl(remoteConfigDataSource)
27+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package com.sopt.clody.domain.appupdate
2+
3+
import com.sopt.clody.domain.model.AppUpdateState
4+
5+
interface AppUpdateChecker {
6+
suspend fun getAppUpdateState(currentVersion: String): AppUpdateState
7+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package com.sopt.clody.domain.model
2+
3+
sealed interface AppUpdateState {
4+
data object Latest : AppUpdateState
5+
data class SoftUpdate(val latestVersion: String) : AppUpdateState
6+
data class HardUpdate(val latestVersion: String) : AppUpdateState
7+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package com.sopt.clody.domain.util
2+
3+
object VersionComparator {
4+
fun compare(current: String, latest: String): Int {
5+
val currentParts = current.split(".").map { it.toIntOrNull() ?: 0 }
6+
val latestParts = latest.split(".").map { it.toIntOrNull() ?: 0 }
7+
8+
for (i in 0 until maxOf(currentParts.size, latestParts.size)) {
9+
val c = currentParts.getOrNull(i) ?: 0
10+
val l = latestParts.getOrNull(i) ?: 0
11+
if (c < l) return -1
12+
if (c > l) return 1
13+
}
14+
return 0
15+
}
16+
}

0 commit comments

Comments
 (0)