Skip to content

Commit ca41de7

Browse files
committed
Merge branch 'release/v2.2.1' into develop
2 parents 7417e63 + bafd3c6 commit ca41de7

File tree

12 files changed

+129
-65
lines changed

12 files changed

+129
-65
lines changed

app/build.gradle

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,12 +74,18 @@ android {
7474
}
7575
}
7676

77+
packaging {
78+
jniLibs {
79+
useLegacyPackaging = false
80+
}
81+
}
82+
7783
applicationVariants.all { variant ->
7884
variant.outputs.all {
7985
def timestamp = new Date().format('yyyyMMdd_HHmmss')
8086
def versionName = variant.versionName
8187
def ext = outputFileName.endsWith('.aab') ? 'aab' : 'apk'
82-
outputFileName = "Runnect-${variant.buildType.name}-v${versionName}-${timestamp}.${ext}"
88+
outputFileName = "v${versionName}-${variant.buildType.name}-${timestamp}.${ext}"
8389
}
8490
}
8591

@@ -93,6 +99,25 @@ android {
9399
}
94100
}
95101

102+
tasks.register('renameReleaseBundle') {
103+
def vName = libs.versions.versionName.get()
104+
def bundlePath = layout.buildDirectory.dir("outputs/bundle/release")
105+
106+
doLast {
107+
def timestamp = new Date().format('yyyyMMdd_HHmmss')
108+
bundlePath.get().asFile.listFiles()?.findAll { it.name.endsWith('.aab') }?.each { aab ->
109+
def newName = "v${vName}-release-${timestamp}.aab"
110+
def newFile = new File(aab.parentFile, newName)
111+
aab.renameTo(newFile)
112+
println "AAB renamed: ${newFile.name}"
113+
}
114+
}
115+
}
116+
117+
tasks.matching { it.name == 'bundleRelease' }.configureEach {
118+
finalizedBy 'renameReleaseBundle'
119+
}
120+
96121
dependencies {
97122
// AndroidX
98123
implementation libs.androidx.core.ktx

app/proguard-rules.pro

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,18 @@
1111
-keepattributes SourceFile,LineNumberTable
1212
-keep public class * extends java.lang.Exception
1313

14+
# --- DTO ---
15+
# RetrofitV2/RetrofitFlow가 GsonConverterFactory를 사용하므로
16+
# Gson 리플렉션으로 직렬화되는 DTO 필드명 보존 필요
17+
-keepclassmembers class com.runnect.runnect.data.dto.** { <fields>; }
18+
19+
# --- Retrofit + kotlin.Result ---
20+
# kotlin.Result는 inline class라 R8이 제네릭 타입 정보를 최적화함
21+
# Retrofit이 Call<Result<T>>의 타입 파라미터를 리플렉션으로 읽지 못해 CallAdapter 생성 실패
22+
# https://github.com/square/retrofit/issues/3880
23+
-keep class kotlin.Result { *; }
24+
-keepattributes Signature
25+
1426
# --- Kakao SDK ---
1527
# 공식 문서: https://developers.kakao.com/docs/latest/en/android/getting-started#configure-for-shrinking-and-obfuscation-(optional)
1628
-keep class com.kakao.sdk.**.model.* { <fields>; }

app/src/main/AndroidManifest.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
android:supportsRtl="true"
4242
android:theme="@style/Theme.Runnect"
4343
android:usesCleartextTraffic="true"
44+
android:extractNativeLibs="false"
4445
tools:targetApi="tiramisu">
4546

4647
<service
@@ -158,6 +159,9 @@
158159
<activity
159160
android:name=".presentation.mypage.upload.MyUploadActivity"
160161
android:exported="false" />
162+
<activity
163+
android:name=".presentation.mypage.setting.MySettingActivity"
164+
android:exported="false" />
161165
<activity
162166
android:name=".presentation.profile.ProfileActivity"
163167
android:exported="false" />

app/src/main/java/com/runnect/runnect/data/network/calladapter/ResultCall.kt

Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
package com.runnect.runnect.data.network.calladapter
22

3-
import com.google.gson.Gson
4-
import com.runnect.runnect.data.dto.response.base.ErrorResponse
53
import com.runnect.runnect.domain.common.RunnectException
4+
import kotlinx.serialization.json.Json
5+
import kotlinx.serialization.json.int
6+
import kotlinx.serialization.json.jsonObject
7+
import kotlinx.serialization.json.jsonPrimitive
68
import retrofit2.Call
79
import retrofit2.Callback
810
import retrofit2.Response
@@ -11,7 +13,7 @@ import okio.Timeout
1113

1214
class ResultCall<T>(private val call: Call<T>) : Call<Result<T>> {
1315

14-
private val gson = Gson()
16+
private val json = Json { ignoreUnknownKeys = true }
1517

1618
override fun execute(): Response<Result<T>> {
1719
throw UnsupportedOperationException("ResultCall doesn't support execute")
@@ -46,23 +48,17 @@ class ResultCall<T>(private val call: Call<T>) : Call<Result<T>> {
4648
}
4749

4850
private fun parseErrorResponse(response: Response<*>): RunnectException {
49-
val errorJson = response.errorBody()?.string()
51+
val errorString = response.errorBody()?.string()
5052

5153
return runCatching {
52-
val errorBody = gson.fromJson(errorJson, ErrorResponse::class.java)
53-
val message = errorBody?.run {
54-
message ?: error ?: ERROR_MSG_COMMON
55-
}
56-
57-
RunnectException(
58-
code = errorBody.status,
59-
message = message
60-
)
54+
val jsonObject = json.parseToJsonElement(errorString.orEmpty()).jsonObject
55+
val status = jsonObject["status"]?.jsonPrimitive?.int ?: response.code()
56+
val message = jsonObject["message"]?.jsonPrimitive?.content
57+
?: jsonObject["error"]?.jsonPrimitive?.content
58+
?: ERROR_MSG_COMMON
59+
RunnectException(code = status, message = message)
6160
}.getOrElse {
62-
RunnectException(
63-
code = response.code(),
64-
message = ERROR_MSG_COMMON
65-
)
61+
RunnectException(code = response.code(), message = ERROR_MSG_COMMON)
6662
}
6763
}
6864

app/src/main/java/com/runnect/runnect/data/network/calladapter/flow/FlowCallAdapter.kt

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
package com.runnect.runnect.data.network.calladapter.flow
22

3-
import com.google.gson.Gson
4-
import com.runnect.runnect.data.dto.response.base.ErrorResponse
53
import com.runnect.runnect.domain.common.RunnectException
64
import kotlinx.coroutines.flow.Flow
75
import kotlinx.coroutines.flow.flow
86
import kotlinx.coroutines.suspendCancellableCoroutine
7+
import kotlinx.serialization.json.Json
8+
import kotlinx.serialization.json.jsonObject
9+
import kotlinx.serialization.json.jsonPrimitive
910
import retrofit2.Call
1011
import retrofit2.CallAdapter
1112
import retrofit2.Callback
@@ -18,7 +19,7 @@ class FlowCallAdapter<T>(
1819
private val responseType: Type
1920
) : CallAdapter<T, Flow<Result<T>>> {
2021

21-
private val gson = Gson()
22+
private val json = Json { ignoreUnknownKeys = true }
2223
override fun responseType() = responseType
2324

2425
// Retrofit의 Call을 Result<>로 변환
@@ -58,15 +59,18 @@ class FlowCallAdapter<T>(
5859
} ?: Result.failure(nullBodyException)
5960
}
6061

61-
// Response에서 오류를 파싱하여 RunnectException 객체를 생성
6262
private fun parseErrorResponse(response: Response<*>): RunnectException {
6363
val errorBodyString = response.errorBody()?.string()
64-
val errorResponse = errorBodyString?.let {
65-
gson.fromJson(it, ErrorResponse::class.java)
66-
}
6764

68-
val errorMessage = errorResponse?.message ?: errorResponse?.error ?: ERROR_MSG_COMMON
69-
return RunnectException(response.code(), errorMessage)
65+
return runCatching {
66+
val jsonObject = json.parseToJsonElement(errorBodyString.orEmpty()).jsonObject
67+
val message = jsonObject["message"]?.jsonPrimitive?.content
68+
?: jsonObject["error"]?.jsonPrimitive?.content
69+
?: ERROR_MSG_COMMON
70+
RunnectException(response.code(), message)
71+
}.getOrElse {
72+
RunnectException(response.code(), ERROR_MSG_COMMON)
73+
}
7074
}
7175

7276
companion object {
Lines changed: 11 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,8 @@
11
package com.runnect.runnect.data.network.interceptor
22

3-
import com.google.gson.Gson
4-
import com.google.gson.JsonElement
5-
import com.google.gson.JsonObject
6-
import com.google.gson.JsonParseException
7-
import com.google.gson.JsonSyntaxException
8-
import com.runnect.runnect.data.dto.response.base.BaseResponse
3+
import kotlinx.serialization.json.Json
4+
import kotlinx.serialization.json.JsonObject
5+
import kotlinx.serialization.json.jsonObject
96
import okhttp3.Interceptor
107
import okhttp3.MediaType.Companion.toMediaTypeOrNull
118
import okhttp3.Response
@@ -18,14 +15,14 @@ import timber.log.Timber
1815
*/
1916
class ResponseInterceptor : Interceptor {
2017

21-
private val gson = Gson()
18+
private val json = Json { ignoreUnknownKeys = true }
2219

2320
override fun intercept(chain: Interceptor.Chain): Response {
2421
val originalResponse = chain.proceed(chain.request())
2522
if (!originalResponse.isSuccessful) return originalResponse
2623

2724
val bodyString = originalResponse.peekBody(Long.MAX_VALUE).string()
28-
val newResponseBodyString = jsonToBaseResponse(bodyString)?.let {
25+
val newResponseBodyString = extractData(bodyString)?.let {
2926
it.toResponseBody("application/json".toMediaTypeOrNull())
3027
} ?: return originalResponse
3128

@@ -42,26 +39,18 @@ class ResponseInterceptor : Interceptor {
4239
}
4340
}
4441

45-
private fun jsonToBaseResponse(body: String): String? {
42+
private fun extractData(body: String): String? {
4643
return try {
47-
val jsonElement: JsonElement = gson.fromJson(body, JsonElement::class.java)
48-
if (!isBaseResponse(jsonElement.asJsonObject)) {
49-
return null
50-
}
51-
52-
val baseResponse = gson.fromJson(body, BaseResponse::class.java)
53-
gson.toJson(baseResponse.data)
54-
} catch (e: JsonSyntaxException) {
55-
null // JSON 구문 분석 오류 발생 시 원래 형식을 반환
56-
} catch (e: JsonParseException) {
57-
null // JSON 파싱 오류 발생 시 원래 형식을 반환
44+
val jsonObject = json.parseToJsonElement(body).jsonObject
45+
if (!isBaseResponse(jsonObject)) return null
46+
jsonObject["data"].toString()
5847
} catch (e: Exception) {
59-
null // 기타 예외 발생 시 원래 형식을 반환
48+
null
6049
}
6150
}
6251

6352
private fun isBaseResponse(jsonObject: JsonObject): Boolean {
6453
val requiredFields = listOf("status", "success", "message", "data")
65-
return requiredFields.all { jsonObject.has(it) }
54+
return requiredFields.all { it in jsonObject }
6655
}
6756
}

app/src/main/java/com/runnect/runnect/presentation/detail/CourseDetailActivity.kt

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ class CourseDetailActivity :
7979
private var isFromDeepLink: Boolean = false
8080

8181
// 인텐트 부가 데이터
82-
private lateinit var rootScreen: CourseDetailRootScreen
82+
private var rootScreen: CourseDetailRootScreen? = null
8383
private var publicCourseId: Int = -1
8484

8585
// 서버통신으로 초기화 할 데이터
@@ -407,9 +407,13 @@ class CourseDetailActivity :
407407
COURSE_STORAGE_SCRAP -> lifecycleScope.launch {
408408
screenRefreshEventBus.emit(ScreenRefreshEvent.RefreshStorageScrap)
409409
}
410-
COURSE_DISCOVER -> setActivityResult<MainActivity>()
411410
COURSE_DISCOVER_SEARCH -> setActivityResult<DiscoverSearchActivity>()
412411
MY_PAGE_UPLOAD_COURSE -> setActivityResult<MyUploadActivity>()
412+
COURSE_DISCOVER -> { /* finish()로 이전 MainActivity(코스 발견 탭)로 복귀 */ }
413+
null -> {
414+
navigateToMainScreen()
415+
return
416+
}
413417
}
414418

415419
finish()

app/src/main/java/com/runnect/runnect/presentation/mypage/MyPageFragment.kt

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,6 @@ import androidx.compose.ui.platform.ComposeView
1414
import androidx.compose.ui.platform.ViewCompositionStrategy
1515
import androidx.fragment.app.Fragment
1616
import androidx.fragment.app.activityViewModels
17-
import androidx.fragment.app.commit
18-
import androidx.fragment.app.replace
1917
import com.kakao.sdk.common.util.KakaoCustomTabsClient
2018
import com.kakao.sdk.talk.TalkApiClient
2119
import com.runnect.runnect.BuildConfig
@@ -25,7 +23,7 @@ import com.runnect.runnect.presentation.login.LoginActivity
2523
import com.runnect.runnect.presentation.mypage.editname.MyPageEditNameActivity
2624
import com.runnect.runnect.presentation.mypage.history.MyHistoryActivity
2725
import com.runnect.runnect.presentation.mypage.reward.MyRewardActivity
28-
import com.runnect.runnect.presentation.mypage.setting.MySettingFragment
26+
import com.runnect.runnect.presentation.mypage.setting.MySettingActivity
2927
import com.runnect.runnect.presentation.mypage.upload.MyUploadActivity
3028
import com.runnect.runnect.presentation.ui.theme.RunnectTheme
3129
import com.runnect.runnect.util.analytics.Analytics
@@ -122,11 +120,11 @@ class MyPageFragment : Fragment() {
122120
}
123121

124122
private fun moveToSettingFragment() {
125-
val bundle = Bundle().apply { putString(ACCOUNT_INFO_TAG, viewModel.currentState.email) }
126-
requireActivity().supportFragmentManager.commit {
127-
setCustomAnimations(R.anim.slide_in_right, R.anim.slide_out_left)
128-
replace<MySettingFragment>(R.id.fl_main, args = bundle)
123+
val intent = Intent(requireContext(), MySettingActivity::class.java).apply {
124+
putExtra(ACCOUNT_INFO_TAG, viewModel.currentState.email)
129125
}
126+
startActivity(intent)
127+
requireActivity().overridePendingTransition(R.anim.slide_in_right, R.anim.slide_out_left)
130128
}
131129

132130
private fun inquiryKakao() {
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package com.runnect.runnect.presentation.mypage.setting
2+
3+
import android.os.Bundle
4+
import androidx.appcompat.app.AppCompatActivity
5+
import androidx.fragment.app.commit
6+
import androidx.fragment.app.replace
7+
import com.runnect.runnect.R
8+
import dagger.hilt.android.AndroidEntryPoint
9+
10+
@AndroidEntryPoint
11+
class MySettingActivity : AppCompatActivity() {
12+
13+
override fun onCreate(savedInstanceState: Bundle?) {
14+
super.onCreate(savedInstanceState)
15+
setContentView(R.layout.activity_my_setting)
16+
17+
if (savedInstanceState == null) {
18+
val bundle = Bundle().apply {
19+
putString(
20+
MySettingFragment.ACCOUNT_INFO_TAG,
21+
intent.getStringExtra(MySettingFragment.ACCOUNT_INFO_TAG)
22+
)
23+
}
24+
supportFragmentManager.commit {
25+
replace<MySettingFragment>(R.id.fl_main, args = bundle)
26+
}
27+
}
28+
}
29+
}

app/src/main/java/com/runnect/runnect/presentation/mypage/setting/MySettingFragment.kt

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import com.runnect.runnect.BuildConfig
1212
import com.runnect.runnect.R
1313
import com.runnect.runnect.binding.BindingFragment
1414
import com.runnect.runnect.databinding.FragmentMySettingBinding
15-
import com.runnect.runnect.presentation.mypage.MyPageFragment
1615
import com.runnect.runnect.presentation.mypage.setting.accountinfo.MySettingAccountInfoFragment
1716
import com.runnect.runnect.util.extension.showWebBrowser
1817

@@ -57,10 +56,8 @@ class MySettingFragment : BindingFragment<FragmentMySettingBinding>(R.layout.fra
5756
}
5857

5958
private fun navigateToMyPageFragment() {
60-
parentFragmentManager.commit {
61-
setCustomAnimations(R.anim.slide_in_left, R.anim.slide_out_right)
62-
replace<MyPageFragment>(R.id.fl_main)
63-
}
59+
requireActivity().finish()
60+
requireActivity().overridePendingTransition(R.anim.slide_in_left, R.anim.slide_out_right)
6461
}
6562

6663
private fun moveToMySettingAccountInfo() {

0 commit comments

Comments
 (0)