Skip to content

Commit

Permalink
[MERGE] v2.1.0 -> production
Browse files Browse the repository at this point in the history
[PRODUCTION] 유료 결제 설정 스프린트 1차 QA용 앱 배포
  • Loading branch information
Marchbreeze authored Dec 2, 2024
2 parents 5cdcd0c + 935778b commit 298a066
Show file tree
Hide file tree
Showing 40 changed files with 777 additions and 181 deletions.
11 changes: 1 addition & 10 deletions .github/workflows/firebase_distribution_builder.yml
Original file line number Diff line number Diff line change
Expand Up @@ -83,13 +83,4 @@ jobs:
appId: ${{ secrets.FIREBASE_APP_ID }}
serviceCredentialsFileContent: ${{ secrets.FIREBASE_APP_DISTRIBUTION_KEY }}
groups: genti
file: app/build/outputs/apk/release/app-release.apk

- name: Sync Production to Develop
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git fetch origin
git checkout develop
git merge production --no-ff --no-edit --allow-unrelated-histories
git push origin develop
file: app/build/outputs/apk/release/app-release.apk
17 changes: 11 additions & 6 deletions .github/workflows/pr_checker.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,18 +53,23 @@ jobs:
echo "amplitude.api.key=\"$amplitude_api_key\"" >> local.properties
echo "amplitude.test.key=\"$amplitude_test_key\"" >> local.properties
- name: Access Keystore Properties
- name: Set up keystore
env:
keystore_file: ${{ secrets.KEYSTORE_FILE }}
run: |
echo "$keystore_file" > app/gentiKeyStore.b64
base64 -d -i app/gentiKeyStore.b64 > app/gentiKeyStore.jks
- name: Access Keystore Properties
env:
store_password: ${{ secrets.STORE_PASSWORD }}
key_password: ${{ secrets.KEY_PASSWORD }}
key_alias: ${{ secrets.KEY_ALIAS }}
run: |
echo "storeFile=keystore.jks" > keystore.properties
echo "storePassword=\"$store_password\"" >> keystore.properties
echo "keyAlias=\"$key_alias\"" >> keystore.properties
echo "keyPassword=\"$key_password\"" >> keystore.properties
echo "$keystore_file" | base64 -d > keystore.jks
echo "storeFile=gentiKeyStore.jks" > keystore.properties
echo "storePassword=$store_password" >> keystore.properties
echo "keyAlias=$key_alias" >> keystore.properties
echo "keyPassword=$key_password" >> keystore.properties
- name: Access Firebase Service
run: echo '${{ secrets.GOOGLE_SERVICES_JSON }}' > ./app/google-services.json
Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@
## EXPERIENCE

- AWS S3 Presigned Url를 활용하며 직접 클라우드에 이미지를 전송하는 기능을 도입하는 경험 ✔️
- 기존 buildSrc를 build-logic으로 전환하고 버전 카탈로그를 적용하여 의존성 관리의 효율성과 가독성을 개선하는 경험 ✔️
- Google Play 결제 라이브러리 v7로 인앱결제를 구현하는 경험 ✔️
- Github Actions를 활용해 Firebase App Distribution으로 앱을 자동 배포하는 CI/CD를 구현하는 경험 ✔️
- 버전 분기처리를 통해 PhotoPicker와 기존 갤러리 파일 탐색기를 활용해 유저가 사진을 선택할 수 있도록 구현하는 경험 ✔️
- 여러개의 사진을 업로드하는 과정을 coroutine을 통해 병렬로 비동기적 수행하도록 마이그레이션하여, 사진 전송 시간을 단축시키는 경험 ✔️
- FileProvider와 cacheDirectory를 활용해 카메라로 직접 찍은 사진을 업로드하도록 구현하는 경험 ✔️
Expand Down
1 change: 1 addition & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="com.android.vending.BILLING" />

<application
android:name=".MyApp"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ object Constants {
const val minSdk = 28
const val targetSdk = 34

const val versionCode = 19
const val versionCode = 21
const val versionName = "2.1.0"

const val jvmVersion = "17"
Expand Down
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,5 @@ plugins {
alias(libs.plugins.kotlin.serialization) apply false
alias(libs.plugins.hilt) apply false
alias(libs.plugins.google.services) apply false
alias(libs.plugins.google.crashlytics) apply false
alias(libs.plugins.firebase.crashlytics) apply false
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import kr.genti.data.dto.BaseResponse
import kr.genti.data.dto.request.CreateRequestDto
import kr.genti.data.dto.request.CreateTwoRequestDto
import kr.genti.data.dto.request.KeyRequestDto
import kr.genti.data.dto.request.PurchaseValidRequestDto
import kr.genti.data.dto.request.S3RequestDto
import kr.genti.data.dto.response.PromptExampleDto
import kr.genti.data.dto.response.S3PresignedUrlDto
Expand All @@ -21,5 +22,7 @@ interface CreateDataSource {

suspend fun postToVerify(request: KeyRequestDto): BaseResponse<Boolean>

suspend fun getPromptExample(): BaseResponse<List<PromptExampleDto>>
suspend fun getPromptExample(type: String): BaseResponse<List<PromptExampleDto>>

suspend fun postToValidatePurchase(request: PurchaseValidRequestDto): BaseResponse<Boolean>
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import kr.genti.data.dto.BaseResponse
import kr.genti.data.dto.request.CreateRequestDto
import kr.genti.data.dto.request.CreateTwoRequestDto
import kr.genti.data.dto.request.KeyRequestDto
import kr.genti.data.dto.request.PurchaseValidRequestDto
import kr.genti.data.dto.request.S3RequestDto
import kr.genti.data.dto.response.PromptExampleDto
import kr.genti.data.dto.response.S3PresignedUrlDto
Expand Down Expand Up @@ -34,6 +35,9 @@ constructor(
override suspend fun postToVerify(request: KeyRequestDto): BaseResponse<Boolean> =
createService.postToVerify(request)

override suspend fun getPromptExample(): BaseResponse<List<PromptExampleDto>> =
createService.getPromptExample()
override suspend fun getPromptExample(type: String): BaseResponse<List<PromptExampleDto>> =
createService.getPromptExample(type)

override suspend fun postToValidatePurchase(request: PurchaseValidRequestDto): BaseResponse<Boolean> =
createService.postToValidatePurchase(request)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package kr.genti.data.dto.request

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kr.genti.domain.entity.request.PurchaseValidRequestModel

@Serializable
data class PurchaseValidRequestDto(
@SerialName("packageName")
val packageName: String,
@SerialName("productId")
val productId: String,
@SerialName("purchaseToken")
val purchaseToken: String,
) {
companion object {
fun PurchaseValidRequestModel.toDto() = PurchaseValidRequestDto(
packageName,
productId,
purchaseToken
)

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@ data class SignupRequestDto(
val birthYear: String,
@SerialName("sex")
val sex: String,
@SerialName("phoneNumber")
val phoneNumber: String?,
) {
companion object {
fun SignupRequestModel.toDto() = SignupRequestDto(birthYear, sex)
fun SignupRequestModel.toDto() = SignupRequestDto(birthYear, sex, phoneNumber)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ import kr.genti.data.dataSource.CreateDataSource
import kr.genti.data.dto.request.CreateRequestDto.Companion.toDto
import kr.genti.data.dto.request.CreateTwoRequestDto.Companion.toDto
import kr.genti.data.dto.request.KeyRequestDto.Companion.toDto
import kr.genti.data.dto.request.PurchaseValidRequestDto.Companion.toDto
import kr.genti.data.dto.request.S3RequestDto.Companion.toDto
import kr.genti.domain.entity.request.CreateRequestModel
import kr.genti.domain.entity.request.CreateTwoRequestModel
import kr.genti.domain.entity.request.KeyRequestModel
import kr.genti.domain.entity.request.PurchaseValidRequestModel
import kr.genti.domain.entity.request.S3RequestModel
import kr.genti.domain.entity.response.PromptExampleModel
import kr.genti.domain.entity.response.S3PresignedUrlModel
Expand Down Expand Up @@ -49,8 +51,13 @@ constructor(
createDataSource.postToVerify(request.toDto()).response
}

override suspend fun getPromptExample(): Result<List<PromptExampleModel>> =
override suspend fun getPromptExample(type: String): Result<List<PromptExampleModel>> =
runCatching {
createDataSource.getPromptExample().response.map { it.toModel() }
createDataSource.getPromptExample(type).response.map { it.toModel() }
}

override suspend fun postToValidatePurchase(request: PurchaseValidRequestModel): Result<Boolean> =
runCatching {
createDataSource.postToValidatePurchase(request.toDto()).response
}
}
13 changes: 11 additions & 2 deletions data/src/main/java/kr/genti/data/service/CreateService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ import kr.genti.data.dto.BaseResponse
import kr.genti.data.dto.request.CreateRequestDto
import kr.genti.data.dto.request.CreateTwoRequestDto
import kr.genti.data.dto.request.KeyRequestDto
import kr.genti.data.dto.request.PurchaseValidRequestDto
import kr.genti.data.dto.request.S3RequestDto
import kr.genti.data.dto.response.PromptExampleDto
import kr.genti.data.dto.response.S3PresignedUrlDto
import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.POST
import retrofit2.http.Path

interface CreateService {
@POST("api/v1/presigned-url")
Expand Down Expand Up @@ -42,6 +44,13 @@ interface CreateService {
@Body request: KeyRequestDto,
): BaseResponse<Boolean>

@GET("api/v1/users/examples/with-picture-square")
suspend fun getPromptExample(): BaseResponse<List<PromptExampleDto>>
@GET("api/v2/users/examples/with-picture-square/{type}")
suspend fun getPromptExample(
@Path("type") type: String
): BaseResponse<List<PromptExampleDto>>

@POST("api/v1/users/in-app-purchases/google/receipt-validation")
suspend fun postToValidatePurchase(
@Body request: PurchaseValidRequestDto
): BaseResponse<Boolean>
}
2 changes: 1 addition & 1 deletion data/src/main/java/kr/genti/data/service/InfoService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import retrofit2.http.DELETE
import retrofit2.http.POST

interface InfoService {
@POST("api/v1/users/signup")
@POST("api/v2/users/signup")
suspend fun postSignupData(
@Body request: SignupRequestDto,
): BaseResponse<SignUpUserDto>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package kr.genti.domain.entity.request

data class PurchaseValidRequestModel(
val packageName: String,
val productId: String,
val purchaseToken: String,
)
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ package kr.genti.domain.entity.request
data class SignupRequestModel(
val birthYear: String,
val sex: String,
val phoneNumber: String?,
)
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package kr.genti.domain.repository
import kr.genti.domain.entity.request.CreateRequestModel
import kr.genti.domain.entity.request.CreateTwoRequestModel
import kr.genti.domain.entity.request.KeyRequestModel
import kr.genti.domain.entity.request.PurchaseValidRequestModel
import kr.genti.domain.entity.request.S3RequestModel
import kr.genti.domain.entity.response.PromptExampleModel
import kr.genti.domain.entity.response.S3PresignedUrlModel
Expand All @@ -20,5 +21,7 @@ interface CreateRepository {

suspend fun postToVerify(request: KeyRequestModel): Result<Boolean>

suspend fun getPromptExample(): Result<List<PromptExampleModel>>
suspend fun getPromptExample(type: String): Result<List<PromptExampleModel>>

suspend fun postToValidatePurchase(request: PurchaseValidRequestModel): Result<Boolean>
}
10 changes: 8 additions & 2 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,10 @@ amplitude = "1.17.3"

# Firebase
firebase-bom = "33.1.2"
crashlytics = "2.9.9"
firebase-crashlytics = "3.0.2"

# In-App Purchase
billing-client = "7.1.1"

# Kakao
kakao = "2.20.3"
Expand Down Expand Up @@ -131,6 +134,9 @@ espresso-core = { group = "androidx.test.espresso", name = "espresso-core", vers
# App Update
app-update = { group = "com.google.android.play", name = "app-update-ktx", version.ref = "app-update" }

# In-App Purchase
billing-client = { group = "com.android.billingclient", name = "billing-ktx", version.ref = "billing-client" }

# Timber
timber = { group = "com.jakewharton.timber", name = "timber", version.ref = "timber" }

Expand Down Expand Up @@ -186,4 +192,4 @@ kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
hilt = { id = "com.google.dagger.hilt.android", version.ref = "hilt" }
google-services = { id = "com.google.gms.google-services", version.ref = "google-services" }
google-crashlytics = { id = "com.google.firebase.crashlytics", version.ref = "crashlytics" }
firebase-crashlytics = { id = "com.google.firebase.crashlytics", version.ref = "firebase-crashlytics" }
1 change: 1 addition & 0 deletions presentation/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,5 @@ dependencies {
implementation(libs.kakao)
implementation(libs.app.update)
implementation(libs.amplitude)
implementation(libs.billing.client)
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ class SignupActivity : BaseActivity<ActivitySignupBinding>(R.layout.activity_sig
initSubmitBtnListener()
observePostSignupState()
observeYearInputState()
observePhoneInputState()
}

private fun initView() {
Expand All @@ -48,9 +49,7 @@ class SignupActivity : BaseActivity<ActivitySignupBinding>(R.layout.activity_sig
}

private fun observePostSignupState() {
viewModel.postSignupState
.flowWithLifecycle(lifecycle)
.distinctUntilChanged()
viewModel.postSignupState.flowWithLifecycle(lifecycle).distinctUntilChanged()
.onEach { state ->
when (state) {
is UiState.Success -> {
Expand All @@ -76,6 +75,15 @@ class SignupActivity : BaseActivity<ActivitySignupBinding>(R.layout.activity_sig
}.launchIn(lifecycleScope)
}

private fun observePhoneInputState() {
viewModel.isPhoneAllSelected.flowWithLifecycle(lifecycle).distinctUntilChanged()
.onEach { isAllSelected ->
if (isAllSelected) {
hideKeyboard(binding.root)
}
}.launchIn(lifecycleScope)
}

private fun setAmplitudeUserProperty(state: UiState.Success<SignUpUserModel>) {
AmplitudeManager.apply {
trackEvent("complete_infoget")
Expand All @@ -88,7 +96,10 @@ class SignupActivity : BaseActivity<ActivitySignupBinding>(R.layout.activity_sig
updateIntProperties("user_picturedownload", 0)
updateIntProperties("user_main_scroll", 0)
updateIntProperties("user_promptsuggest_refresh", 0)
updateIntProperties("user_piccreate", 0)
updateIntProperties("user_piccreate_total", 0)
updateIntProperties("user_piccreate_original", 0)
updateIntProperties("user_piccreate_oneparent", 0)
updateIntProperties("user_piccreate_twoparents", 0)
updateBooleanProperties("user_alarm", false)
updateBooleanProperties("user_verified", false)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ constructor(
private val _isYearAllSelected = MutableStateFlow<Boolean>(false)
val isYearAllSelected: StateFlow<Boolean> = _isYearAllSelected

val selectedPhone = MutableLiveData<String?>()
val isPhoneSelected = MutableLiveData<Boolean>(false)

private val _isPhoneAllSelected = MutableStateFlow<Boolean>(false)
val isPhoneAllSelected: StateFlow<Boolean> = _isPhoneAllSelected

val isAllSelected = MutableLiveData<Boolean>(false)

private val _postSignupState = MutableStateFlow<UiState<SignUpUserModel>>(UiState.Empty)
Expand All @@ -49,6 +55,11 @@ constructor(
checkAllSelected()
}

fun checkPhone() {
isPhoneSelected.value = selectedPhone.value?.isNotEmpty()
_isPhoneAllSelected.value = selectedPhone.value?.length == 11
}

private fun checkAllSelected() {
isAllSelected.value = isGenderSelected.value == true && isYearAllSelected.value == true
}
Expand All @@ -60,6 +71,7 @@ constructor(
SignupRequestModel(
selectedYear.value.toString(),
selectedGender.value.toString(),
selectedPhone.value
),
).onSuccess {
userRepository.setUserRole(ROLE_USER)
Expand Down
Loading

0 comments on commit 298a066

Please sign in to comment.