Skip to content

Commit

Permalink
Merge pull request #349 from Runnect/feature/improve-developer-mode
Browse files Browse the repository at this point in the history
[Refactor] 개발자 모드 개선 & 네트워크 예외 처리 오류 수정 (FlowCallAdapter)
  • Loading branch information
dongx0915 authored Jun 5, 2024
2 parents ff8c1c5 + 8d2d3b3 commit 89150b0
Show file tree
Hide file tree
Showing 26 changed files with 658 additions and 61 deletions.
6 changes: 3 additions & 3 deletions app/src/debug/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@
tools:targetApi="tiramisu">

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

<intent-filter>
<action android:name="android.intent.action.VIEW" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.runnect.runnect.developer.data.dto

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
data class ResponseServerStatus(
@SerialName("status")
val status: String
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.runnect.runnect.developer.data.repository

import com.runnect.runnect.data.network.FlowResult
import com.runnect.runnect.data.network.toEntityResult
import com.runnect.runnect.developer.data.source.remote.ServerStatusDataSource
import com.runnect.runnect.developer.domain.ServerStatusRepository
import javax.inject.Inject

class ServerStatusRepositoryImpl @Inject constructor(
private val serverStatusDataSource: ServerStatusDataSource
) : ServerStatusRepository {

override suspend fun checkServerStatus(serverUrl: String): FlowResult<String> {
return serverStatusDataSource.checkServerStatus(serverUrl).toEntityResult {
it.status
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.runnect.runnect.developer.data.service

import com.runnect.runnect.developer.data.dto.ResponseServerStatus
import kotlinx.coroutines.flow.Flow
import retrofit2.http.GET
import retrofit2.http.Url

interface ServerStatusService {

@GET
fun checkServerStatus(
@Url url: String
): Flow<Result<ResponseServerStatus>>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.runnect.runnect.developer.data.source.remote

import com.runnect.runnect.data.network.FlowResult
import com.runnect.runnect.developer.data.dto.ResponseServerStatus
import com.runnect.runnect.developer.data.service.ServerStatusService
import javax.inject.Inject

class ServerStatusDataSource @Inject constructor(
private val serverStatusService: ServerStatusService,
) {

fun checkServerStatus(serverUrl: String): FlowResult<ResponseServerStatus> {
return serverStatusService.checkServerStatus(serverUrl)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.runnect.runnect.developer.domain

import com.runnect.runnect.data.network.FlowResult

interface ServerStatusRepository {

suspend fun checkServerStatus(serverUrl: String): FlowResult<String>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.runnect.runnect.developer.enum

import androidx.annotation.ColorRes
import androidx.annotation.StringRes
import com.runnect.runnect.R
import com.runnect.runnect.developer.presentation.RunnectDeveloperViewModel.ServerState

enum class ServerStatus(
@ColorRes val colorRes: Int,
@StringRes val statusRes: Int,
@StringRes val summaryRes: Int,
) {

CHECKING(R.color.blue, R.string.developer_server_status_checking_title, R.string.developer_server_status_checking_sub),
RUNNING(R.color.green, R.string.developer_server_status_running_title, R.string.developer_server_status_running_sub),
DEGRADED(R.color.orange, R.string.developer_server_status_degraded_title, R.string.developer_server_status_degraded_sub),
ERROR(R.color.red, R.string.developer_server_status_error_title, R.string.developer_server_status_error_sub),
UNKNOWN(R.color.grey, R.string.developer_server_status_unknown_title, R.string.developer_server_status_unknown_sub);

companion object {
fun getStatus(state: ServerState): ServerStatus {
return when (state) {
ServerState.Running -> RUNNING
ServerState.Degraded -> DEGRADED
ServerState.Error -> ERROR
ServerState.Unknown -> UNKNOWN
ServerState.Checking -> CHECKING
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
package com.runnect.runnect.developer
package com.runnect.runnect.developer.presentation

import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
import android.util.DisplayMetrics
import android.view.WindowInsets
import android.view.WindowManager
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.lifecycleScope
import androidx.preference.ListPreference
import androidx.preference.Preference
Expand All @@ -17,40 +20,88 @@ import com.runnect.runnect.R
import com.runnect.runnect.application.ApiMode
import com.runnect.runnect.application.ApplicationClass
import com.runnect.runnect.application.PreferenceManager
import com.runnect.runnect.developer.enum.ServerStatus
import com.runnect.runnect.developer.presentation.custom.ServerStatusPreference
import com.runnect.runnect.util.custom.toast.RunnectToast
import com.runnect.runnect.util.preference.AuthUtil.getAccessToken
import com.runnect.runnect.util.preference.AuthUtil.getNewToken
import com.runnect.runnect.util.preference.AuthUtil.saveToken
import com.runnect.runnect.util.preference.StatusType.LoginStatus
import com.runnect.runnect.util.extension.repeatOnStarted
import com.runnect.runnect.util.extension.setStatusBarColor
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlin.system.exitProcess

@AndroidEntryPoint
class RunnectDeveloperActivity : AppCompatActivity(R.layout.activity_runnect_developer) {

class RunnectDeveloperFragment : PreferenceFragmentCompat() {

private val viewModel: RunnectDeveloperViewModel by activityViewModels()

private val clipboardManager: ClipboardManager? by lazy {
context?.getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager
}

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

initUserInfo()
initApiMode()
initDeviceInfo()
initDisplayInfo()
initObserve()
requestApi()
}

private fun requestApi() {
with(viewModel) {
checkProdServerStatus()
checkTestServerStatus()
}
}

private fun initObserve() {
val prodPref = findPreference<ServerStatusPreference>("dev_pref_prod_server_status")
val testPref = findPreference<ServerStatusPreference>("dev_pref_test_server_status")

repeatOnStarted(
{
viewModel.prodStatus.collect {
prodPref?.setServerStatus(ServerStatus.getStatus(it))
}
},
{
viewModel.testStatus.collect {
testPref?.setServerStatus(ServerStatus.getStatus(it))
}
}
)
}

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

setPreferenceSummary("dev_pref_key_access_token", accessToken)
setPreferenceSummary("dev_pref_key_refresh_token", refreshToken)
setPreferenceClickListener("dev_pref_key_share_tokens") {
Intent().apply {
action = Intent.ACTION_SEND
type = "text/plain"
putExtra(Intent.EXTRA_TEXT, combinedToken)
}.let {
startActivity(Intent.createChooser(it, "Share tokens via:"))
}
}
}

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

title = currentApi.name
setValueIndex(selectIndex)
setOnPreferenceChangeListener { preference, newValue ->
setOnPreferenceChangeListener { _, newValue ->
val selectItem = newValue.toString()
this.title = selectItem

PreferenceManager.apply {
setString(ctx, ApplicationClass.API_MODE, selectItem)
with(ctx) {
PreferenceManager.setString(this, ApplicationClass.API_MODE, selectItem)
saveToken(
accessToken = LoginStatus.NONE.value,
refreshToken = LoginStatus.NONE.value
)

restartApplication(this)
}
ctx.saveToken(
accessToken = LoginStatus.NONE.value,
refreshToken = LoginStatus.NONE.value
)
destroyApp(ctx)

true
}
}
Expand All @@ -96,15 +149,9 @@ class RunnectDeveloperActivity : AppCompatActivity(R.layout.activity_runnect_dev
val naviBarHeight = getNaviBarHeight(windowManager)

with(metrics) {
setPreferenceSummary(
"dev_pref_display_ratio",
"$widthPixels x ${heightPixels + statusBarHeight + naviBarHeight}"
)
setPreferenceSummary("dev_pref_display_ratio", "$widthPixels x ${heightPixels + statusBarHeight + naviBarHeight}")
setPreferenceSummary("dev_pref_display_density", "${densityDpi}dp")
setPreferenceSummary(
"dev_pref_display_resource_bucket",
getDeviceResourseBucket(this)
)
setPreferenceSummary("dev_pref_display_resource_bucket", getDeviceResourseBucket(this))
}
}

Expand All @@ -125,8 +172,7 @@ class RunnectDeveloperActivity : AppCompatActivity(R.layout.activity_runnect_dev
private fun getStatusBarHeight(windowManager: WindowManager): Int {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
val windowMetrics = windowManager.currentWindowMetrics
val insets =
windowMetrics.windowInsets.getInsetsIgnoringVisibility(WindowInsets.Type.statusBars())
val insets = windowMetrics.windowInsets.getInsetsIgnoringVisibility(WindowInsets.Type.statusBars())
insets.top
} else {
0
Expand All @@ -136,8 +182,7 @@ class RunnectDeveloperActivity : AppCompatActivity(R.layout.activity_runnect_dev
private fun getNaviBarHeight(windowManager: WindowManager): Int {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
val windowMetrics = windowManager.currentWindowMetrics
val insets =
windowMetrics.windowInsets.getInsetsIgnoringVisibility(WindowInsets.Type.navigationBars())
val insets = windowMetrics.windowInsets.getInsetsIgnoringVisibility(WindowInsets.Type.navigationBars())
insets.bottom
} else {
0
Expand All @@ -153,6 +198,15 @@ class RunnectDeveloperActivity : AppCompatActivity(R.layout.activity_runnect_dev
}
}

private fun setPreferenceClickListener(key: String, onClick: () -> Unit) {
findPreference<Preference>(key)?.let { pref ->
pref.setOnPreferenceClickListener {
onClick.invoke()
true
}
}
}

private fun copyToText(text: String): Boolean {
val clipData = ClipData.newPlainText(CLIPBOARD_LABEL, text)
clipboardManager?.setPrimaryClip(clipData)
Expand All @@ -166,14 +220,19 @@ class RunnectDeveloperActivity : AppCompatActivity(R.layout.activity_runnect_dev
return true
}

private fun destroyApp(context: Context) {
private fun restartApplication(context: Context) {
val packageManager: PackageManager = context.packageManager
val packageName = packageManager.getLaunchIntentForPackage(context.packageName)
val component = packageName?.component

lifecycleScope.launch(Dispatchers.Main) {
RunnectToast.createToast(context, getString(R.string.dev_mode_require_restart))
.show()
delay(3000)
RunnectToast.createToast(context, getString(R.string.dev_mode_require_restart)).show()
delay(2000)

activity?.finishAffinity() //루트액티비티 종료
exitProcess(0)
Intent.makeRestartActivityTask(component).apply {
startActivity(this)
exitProcess(0)
}
}
}

Expand Down
Loading

0 comments on commit 89150b0

Please sign in to comment.