Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Refactor] 개발자 모드 개선 & 네트워크 예외 처리 오류 수정 (FlowCallAdapter) #349

Merged
merged 25 commits into from
Jun 5, 2024
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
e989f71
[mod] #348 개발자 모드 제목 및 테마 변경
dongx0915 Apr 21, 2024
662ce8c
[feat] #348 토큰 공유하기, 앱 재시작, 서버 상태 체크 옵저버 코드 추가
dongx0915 Apr 21, 2024
156d751
[mod] #348 앱 재시작 문구 변경
dongx0915 Apr 21, 2024
6617ef0
[add] #348 상단바 색상 변경 확장 함수 추가
dongx0915 Apr 21, 2024
3b77043
[mod] #348 개발자 모드 타이틀 아이콘 추가
dongx0915 Apr 21, 2024
f46154b
[feat] #348 토큰 공유하기 항목 추가
dongx0915 Apr 21, 2024
f331926
[feat] #348 서버 상태 체크 레이아웃 추가
dongx0915 Apr 21, 2024
6663d22
[feat] #348 서버 상태 체크 기능 구현
dongx0915 Apr 21, 2024
9aa7899
[add] #348 서버 상태 enum 추가
dongx0915 Apr 21, 2024
4441356
[feat] #348 FlowCallAdapter 추가
dongx0915 Apr 21, 2024
bc232a9
[mod] #348 패키지 변경
dongx0915 Apr 21, 2024
30eca4c
[refactor] #348 코드 정리
dongx0915 Apr 24, 2024
dad348b
[feat] #348 ServerStatus 문자열 리소스 분리 & Error 타입 추가
dongx0915 Apr 30, 2024
5c712d3
[mod] #348 Flow 확장 함수 nullable 처리
dongx0915 Apr 30, 2024
46e327e
[mod] #348 Api 요청 디스패처 선언, 코드 정리
dongx0915 Apr 30, 2024
732a294
[mod] #348 서버 상태 레이아웃 패딩 수정
dongx0915 Apr 30, 2024
10246bd
[mod] #348 Flow CallAdapter 코드 정리
dongx0915 Apr 30, 2024
910bf3d
[mod] #348 개발자모드에서 사용되는 문자열 리소스 debug 패키지로 이동
dongx0915 Apr 30, 2024
2aed46b
[refactor] #348 테스트 코드 정리
dongx0915 Apr 30, 2024
aa8ddfe
Merge branch 'develop' into feature/improve-developer-mode
dongx0915 Apr 30, 2024
131c747
[feat] #348 토큰 공유 시 서버 명 표기하도록 수정
dongx0915 May 1, 2024
fc68223
Merge branch 'develop' into feature/improve-developer-mode
dongx0915 May 2, 2024
7159064
[mod] named argument 추가
dongx0915 May 11, 2024
fb47c47
[mod] DataSource 클래스명 remote 제거
dongx0915 May 11, 2024
8d2d3b3
[mod] CallAdapter runCatching 제거
dongx0915 May 12, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.RemoteServerStatusDataSource
import com.runnect.runnect.developer.domain.ServerStatusRepository
import javax.inject.Inject

class ServerStatusRepositoryImpl @Inject constructor(
private val serverStatusDataSource: RemoteServerStatusDataSource
) : 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>>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

여기서는 사용자 정의 타입 FlowResult 를 사용하지 않은 특별한 이유가 있나요??

Copy link
Member Author

@dongx0915 dongx0915 May 9, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@leeeha 회의록에 적어둔대로 적용할 지 의견을 들어본 후 수정할 예정이라 다 고치진 않았습니다~!

}
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 RemoteServerStatusDataSource @Inject constructor(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

전부터 느꼈지만 remote가 상위 패키지 이름이므로 클래스 앞에 매번 Remote라는 접두사는 안 붙여도 될 거 같다는 생각이 듭니다! 사소하지만 코멘트 남겨봅니다 ㅎ.ㅎ

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(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이런 기능은 어디서 쇽샥 해오신 건지.. 👍👍

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@leeeha 서버팀 레포 구경하다가 Actuator 설정 되어있길래 바로 적용 했슴니다😎

@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, true, R.color.white)
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

named argument 모든 인자에 대해 적용해주면 더 좋을 거 같습니다 : )

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@leeeha 오 네네 수정해두겠습니다~!


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"

Copy link
Member

@leeeha leeeha May 2, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

귀찮으니까 기능 하나 더 만들어버리는 ㅎㅎㅎ 최고 👍

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)
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

굿!! 사용하기 더 편해졌네요ㅎㅎ

}

Expand Down
Loading
Loading