Skip to content

Commit 89150b0

Browse files
authored
Merge pull request #349 from Runnect/feature/improve-developer-mode
[Refactor] 개발자 모드 개선 & 네트워크 예외 처리 오류 수정 (FlowCallAdapter)
2 parents ff8c1c5 + 8d2d3b3 commit 89150b0

26 files changed

+658
-61
lines changed

app/src/debug/AndroidManifest.xml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,10 @@
2121
tools:targetApi="tiramisu">
2222

2323
<activity
24-
android:name=".developer.RunnectDeveloperActivity"
24+
android:name=".developer.presentation.RunnectDeveloperActivity"
2525
android:exported="true"
26-
android:label="개발자 모드"
27-
android:theme="@style/Theme.AppCompat.Light.DarkActionBar">
26+
android:label="Runnect Developer Mode"
27+
android:theme="@style/Theme.Material3.Light.NoActionBar">
2828

2929
<intent-filter>
3030
<action android:name="android.intent.action.VIEW" />
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package com.runnect.runnect.developer.data.dto
2+
3+
import kotlinx.serialization.SerialName
4+
import kotlinx.serialization.Serializable
5+
6+
@Serializable
7+
data class ResponseServerStatus(
8+
@SerialName("status")
9+
val status: String
10+
)
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package com.runnect.runnect.developer.data.repository
2+
3+
import com.runnect.runnect.data.network.FlowResult
4+
import com.runnect.runnect.data.network.toEntityResult
5+
import com.runnect.runnect.developer.data.source.remote.ServerStatusDataSource
6+
import com.runnect.runnect.developer.domain.ServerStatusRepository
7+
import javax.inject.Inject
8+
9+
class ServerStatusRepositoryImpl @Inject constructor(
10+
private val serverStatusDataSource: ServerStatusDataSource
11+
) : ServerStatusRepository {
12+
13+
override suspend fun checkServerStatus(serverUrl: String): FlowResult<String> {
14+
return serverStatusDataSource.checkServerStatus(serverUrl).toEntityResult {
15+
it.status
16+
}
17+
}
18+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package com.runnect.runnect.developer.data.service
2+
3+
import com.runnect.runnect.developer.data.dto.ResponseServerStatus
4+
import kotlinx.coroutines.flow.Flow
5+
import retrofit2.http.GET
6+
import retrofit2.http.Url
7+
8+
interface ServerStatusService {
9+
10+
@GET
11+
fun checkServerStatus(
12+
@Url url: String
13+
): Flow<Result<ResponseServerStatus>>
14+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package com.runnect.runnect.developer.data.source.remote
2+
3+
import com.runnect.runnect.data.network.FlowResult
4+
import com.runnect.runnect.developer.data.dto.ResponseServerStatus
5+
import com.runnect.runnect.developer.data.service.ServerStatusService
6+
import javax.inject.Inject
7+
8+
class ServerStatusDataSource @Inject constructor(
9+
private val serverStatusService: ServerStatusService,
10+
) {
11+
12+
fun checkServerStatus(serverUrl: String): FlowResult<ResponseServerStatus> {
13+
return serverStatusService.checkServerStatus(serverUrl)
14+
}
15+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package com.runnect.runnect.developer.domain
2+
3+
import com.runnect.runnect.data.network.FlowResult
4+
5+
interface ServerStatusRepository {
6+
7+
suspend fun checkServerStatus(serverUrl: String): FlowResult<String>
8+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package com.runnect.runnect.developer.enum
2+
3+
import androidx.annotation.ColorRes
4+
import androidx.annotation.StringRes
5+
import com.runnect.runnect.R
6+
import com.runnect.runnect.developer.presentation.RunnectDeveloperViewModel.ServerState
7+
8+
enum class ServerStatus(
9+
@ColorRes val colorRes: Int,
10+
@StringRes val statusRes: Int,
11+
@StringRes val summaryRes: Int,
12+
) {
13+
14+
CHECKING(R.color.blue, R.string.developer_server_status_checking_title, R.string.developer_server_status_checking_sub),
15+
RUNNING(R.color.green, R.string.developer_server_status_running_title, R.string.developer_server_status_running_sub),
16+
DEGRADED(R.color.orange, R.string.developer_server_status_degraded_title, R.string.developer_server_status_degraded_sub),
17+
ERROR(R.color.red, R.string.developer_server_status_error_title, R.string.developer_server_status_error_sub),
18+
UNKNOWN(R.color.grey, R.string.developer_server_status_unknown_title, R.string.developer_server_status_unknown_sub);
19+
20+
companion object {
21+
fun getStatus(state: ServerState): ServerStatus {
22+
return when (state) {
23+
ServerState.Running -> RUNNING
24+
ServerState.Degraded -> DEGRADED
25+
ServerState.Error -> ERROR
26+
ServerState.Unknown -> UNKNOWN
27+
ServerState.Checking -> CHECKING
28+
}
29+
}
30+
}
31+
}

app/src/debug/java/com/runnect/runnect/developer/RunnectDeveloperActivity.kt renamed to app/src/debug/java/com/runnect/runnect/developer/presentation/RunnectDeveloperActivity.kt

Lines changed: 86 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
1-
package com.runnect.runnect.developer
1+
package com.runnect.runnect.developer.presentation
22

33
import android.content.ClipData
44
import android.content.ClipboardManager
55
import android.content.Context
6+
import android.content.Intent
7+
import android.content.pm.PackageManager
68
import android.os.Build
79
import android.os.Bundle
810
import android.util.DisplayMetrics
911
import android.view.WindowInsets
1012
import android.view.WindowManager
1113
import androidx.appcompat.app.AppCompatActivity
14+
import androidx.fragment.app.activityViewModels
1215
import androidx.lifecycle.lifecycleScope
1316
import androidx.preference.ListPreference
1417
import androidx.preference.Preference
@@ -17,40 +20,88 @@ import com.runnect.runnect.R
1720
import com.runnect.runnect.application.ApiMode
1821
import com.runnect.runnect.application.ApplicationClass
1922
import com.runnect.runnect.application.PreferenceManager
23+
import com.runnect.runnect.developer.enum.ServerStatus
24+
import com.runnect.runnect.developer.presentation.custom.ServerStatusPreference
2025
import com.runnect.runnect.util.custom.toast.RunnectToast
2126
import com.runnect.runnect.util.preference.AuthUtil.getAccessToken
2227
import com.runnect.runnect.util.preference.AuthUtil.getNewToken
2328
import com.runnect.runnect.util.preference.AuthUtil.saveToken
2429
import com.runnect.runnect.util.preference.StatusType.LoginStatus
30+
import com.runnect.runnect.util.extension.repeatOnStarted
31+
import com.runnect.runnect.util.extension.setStatusBarColor
32+
import dagger.hilt.android.AndroidEntryPoint
2533
import kotlinx.coroutines.Dispatchers
2634
import kotlinx.coroutines.delay
2735
import kotlinx.coroutines.launch
2836
import kotlin.system.exitProcess
2937

38+
@AndroidEntryPoint
3039
class RunnectDeveloperActivity : AppCompatActivity(R.layout.activity_runnect_developer) {
3140

3241
class RunnectDeveloperFragment : PreferenceFragmentCompat() {
3342

43+
private val viewModel: RunnectDeveloperViewModel by activityViewModels()
44+
3445
private val clipboardManager: ClipboardManager? by lazy {
3546
context?.getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager
3647
}
3748

3849
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
3950
setPreferencesFromResource(R.xml.preferences_developer_menu, rootKey)
51+
activity?.apply {
52+
setStatusBarColor(window = window, isLightColor = true, colorResource = R.color.white)
53+
}
4054

4155
initUserInfo()
4256
initApiMode()
4357
initDeviceInfo()
4458
initDisplayInfo()
59+
initObserve()
60+
requestApi()
61+
}
62+
63+
private fun requestApi() {
64+
with(viewModel) {
65+
checkProdServerStatus()
66+
checkTestServerStatus()
67+
}
68+
}
69+
70+
private fun initObserve() {
71+
val prodPref = findPreference<ServerStatusPreference>("dev_pref_prod_server_status")
72+
val testPref = findPreference<ServerStatusPreference>("dev_pref_test_server_status")
73+
74+
repeatOnStarted(
75+
{
76+
viewModel.prodStatus.collect {
77+
prodPref?.setServerStatus(ServerStatus.getStatus(it))
78+
}
79+
},
80+
{
81+
viewModel.testStatus.collect {
82+
testPref?.setServerStatus(ServerStatus.getStatus(it))
83+
}
84+
}
85+
)
4586
}
4687

4788
private fun initUserInfo() {
4889
val ctx: Context = context ?: return
4990
val accessToken = ctx.getAccessToken()
5091
val refreshToken = ctx.getNewToken()
92+
val combinedToken = "${ApiMode.getCurrentApiMode(ctx).name} 서버\n[Access Token]: $accessToken\n\n---\n\n[Refresh Token]: $refreshToken"
5193

5294
setPreferenceSummary("dev_pref_key_access_token", accessToken)
5395
setPreferenceSummary("dev_pref_key_refresh_token", refreshToken)
96+
setPreferenceClickListener("dev_pref_key_share_tokens") {
97+
Intent().apply {
98+
action = Intent.ACTION_SEND
99+
type = "text/plain"
100+
putExtra(Intent.EXTRA_TEXT, combinedToken)
101+
}.let {
102+
startActivity(Intent.createChooser(it, "Share tokens via:"))
103+
}
104+
}
54105
}
55106

56107
private fun initApiMode() {
@@ -66,18 +117,20 @@ class RunnectDeveloperActivity : AppCompatActivity(R.layout.activity_runnect_dev
66117

67118
title = currentApi.name
68119
setValueIndex(selectIndex)
69-
setOnPreferenceChangeListener { preference, newValue ->
120+
setOnPreferenceChangeListener { _, newValue ->
70121
val selectItem = newValue.toString()
71122
this.title = selectItem
72123

73-
PreferenceManager.apply {
74-
setString(ctx, ApplicationClass.API_MODE, selectItem)
124+
with(ctx) {
125+
PreferenceManager.setString(this, ApplicationClass.API_MODE, selectItem)
126+
saveToken(
127+
accessToken = LoginStatus.NONE.value,
128+
refreshToken = LoginStatus.NONE.value
129+
)
130+
131+
restartApplication(this)
75132
}
76-
ctx.saveToken(
77-
accessToken = LoginStatus.NONE.value,
78-
refreshToken = LoginStatus.NONE.value
79-
)
80-
destroyApp(ctx)
133+
81134
true
82135
}
83136
}
@@ -96,15 +149,9 @@ class RunnectDeveloperActivity : AppCompatActivity(R.layout.activity_runnect_dev
96149
val naviBarHeight = getNaviBarHeight(windowManager)
97150

98151
with(metrics) {
99-
setPreferenceSummary(
100-
"dev_pref_display_ratio",
101-
"$widthPixels x ${heightPixels + statusBarHeight + naviBarHeight}"
102-
)
152+
setPreferenceSummary("dev_pref_display_ratio", "$widthPixels x ${heightPixels + statusBarHeight + naviBarHeight}")
103153
setPreferenceSummary("dev_pref_display_density", "${densityDpi}dp")
104-
setPreferenceSummary(
105-
"dev_pref_display_resource_bucket",
106-
getDeviceResourseBucket(this)
107-
)
154+
setPreferenceSummary("dev_pref_display_resource_bucket", getDeviceResourseBucket(this))
108155
}
109156
}
110157

@@ -125,8 +172,7 @@ class RunnectDeveloperActivity : AppCompatActivity(R.layout.activity_runnect_dev
125172
private fun getStatusBarHeight(windowManager: WindowManager): Int {
126173
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
127174
val windowMetrics = windowManager.currentWindowMetrics
128-
val insets =
129-
windowMetrics.windowInsets.getInsetsIgnoringVisibility(WindowInsets.Type.statusBars())
175+
val insets = windowMetrics.windowInsets.getInsetsIgnoringVisibility(WindowInsets.Type.statusBars())
130176
insets.top
131177
} else {
132178
0
@@ -136,8 +182,7 @@ class RunnectDeveloperActivity : AppCompatActivity(R.layout.activity_runnect_dev
136182
private fun getNaviBarHeight(windowManager: WindowManager): Int {
137183
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
138184
val windowMetrics = windowManager.currentWindowMetrics
139-
val insets =
140-
windowMetrics.windowInsets.getInsetsIgnoringVisibility(WindowInsets.Type.navigationBars())
185+
val insets = windowMetrics.windowInsets.getInsetsIgnoringVisibility(WindowInsets.Type.navigationBars())
141186
insets.bottom
142187
} else {
143188
0
@@ -153,6 +198,15 @@ class RunnectDeveloperActivity : AppCompatActivity(R.layout.activity_runnect_dev
153198
}
154199
}
155200

201+
private fun setPreferenceClickListener(key: String, onClick: () -> Unit) {
202+
findPreference<Preference>(key)?.let { pref ->
203+
pref.setOnPreferenceClickListener {
204+
onClick.invoke()
205+
true
206+
}
207+
}
208+
}
209+
156210
private fun copyToText(text: String): Boolean {
157211
val clipData = ClipData.newPlainText(CLIPBOARD_LABEL, text)
158212
clipboardManager?.setPrimaryClip(clipData)
@@ -166,14 +220,19 @@ class RunnectDeveloperActivity : AppCompatActivity(R.layout.activity_runnect_dev
166220
return true
167221
}
168222

169-
private fun destroyApp(context: Context) {
223+
private fun restartApplication(context: Context) {
224+
val packageManager: PackageManager = context.packageManager
225+
val packageName = packageManager.getLaunchIntentForPackage(context.packageName)
226+
val component = packageName?.component
227+
170228
lifecycleScope.launch(Dispatchers.Main) {
171-
RunnectToast.createToast(context, getString(R.string.dev_mode_require_restart))
172-
.show()
173-
delay(3000)
229+
RunnectToast.createToast(context, getString(R.string.dev_mode_require_restart)).show()
230+
delay(2000)
174231

175-
activity?.finishAffinity() //루트액티비티 종료
176-
exitProcess(0)
232+
Intent.makeRestartActivityTask(component).apply {
233+
startActivity(this)
234+
exitProcess(0)
235+
}
177236
}
178237
}
179238

0 commit comments

Comments
 (0)