From e989f71d1df5b74fd929e3b0f860c998bcf37523 Mon Sep 17 00:00:00 2001 From: Donghyeon0915 Date: Mon, 22 Apr 2024 03:55:00 +0900 Subject: [PATCH 01/23] =?UTF-8?q?[mod]=20#348=20=EA=B0=9C=EB=B0=9C?= =?UTF-8?q?=EC=9E=90=20=EB=AA=A8=EB=93=9C=20=EC=A0=9C=EB=AA=A9=20=EB=B0=8F?= =?UTF-8?q?=20=ED=85=8C=EB=A7=88=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/debug/AndroidManifest.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/debug/AndroidManifest.xml b/app/src/debug/AndroidManifest.xml index 3729628fc..0c6e8b4fe 100644 --- a/app/src/debug/AndroidManifest.xml +++ b/app/src/debug/AndroidManifest.xml @@ -20,10 +20,10 @@ tools:targetApi="tiramisu"> + android:label="Runnect Developer Mode" + android:theme="@style/Theme.Material3.Light.NoActionBar"> From 662ce8c2376240b6d1ef2b867ea66c00a8ea912d Mon Sep 17 00:00:00 2001 From: Donghyeon0915 Date: Mon, 22 Apr 2024 04:03:59 +0900 Subject: [PATCH 02/23] =?UTF-8?q?[feat]=20#348=20=ED=86=A0=ED=81=B0=20?= =?UTF-8?q?=EA=B3=B5=EC=9C=A0=ED=95=98=EA=B8=B0,=20=EC=95=B1=20=EC=9E=AC?= =?UTF-8?q?=EC=8B=9C=EC=9E=91,=20=EC=84=9C=EB=B2=84=20=EC=83=81=ED=83=9C?= =?UTF-8?q?=20=EC=B2=B4=ED=81=AC=20=EC=98=B5=EC=A0=80=EB=B2=84=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../RunnectDeveloperActivity.kt | 80 +++++++++++++++++-- 1 file changed, 73 insertions(+), 7 deletions(-) rename app/src/debug/java/com/runnect/runnect/developer/{ => presentation}/RunnectDeveloperActivity.kt (69%) diff --git a/app/src/debug/java/com/runnect/runnect/developer/RunnectDeveloperActivity.kt b/app/src/debug/java/com/runnect/runnect/developer/presentation/RunnectDeveloperActivity.kt similarity index 69% rename from app/src/debug/java/com/runnect/runnect/developer/RunnectDeveloperActivity.kt rename to app/src/debug/java/com/runnect/runnect/developer/presentation/RunnectDeveloperActivity.kt index 42f3bffc7..169871269 100644 --- a/app/src/debug/java/com/runnect/runnect/developer/RunnectDeveloperActivity.kt +++ b/app/src/debug/java/com/runnect/runnect/developer/presentation/RunnectDeveloperActivity.kt @@ -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 @@ -18,37 +21,85 @@ import com.runnect.runnect.application.ApiMode import com.runnect.runnect.application.ApplicationClass import com.runnect.runnect.application.PreferenceManager import com.runnect.runnect.data.service.TokenAuthenticator +import com.runnect.runnect.developer.enum.ServerStatus +import com.runnect.runnect.developer.presentation.custom.ServerStatusPreference import com.runnect.runnect.presentation.mypage.setting.accountinfo.MySettingAccountInfoFragment import com.runnect.runnect.util.custom.toast.RunnectToast +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) + } initUserInfo() initApiMode() initDeviceInfo() initDisplayInfo() + initObserve() + requestApi() + } + + private fun requestApi() { + with(viewModel) { + checkProdServerStatus() + checkTestServerStatus() + } + } + + private fun initObserve() { + val prodPref = findPreference("dev_pref_prod_server_status") + val testPref = findPreference("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 = PreferenceManager.getString(ctx, TokenAuthenticator.TOKEN_KEY_ACCESS) ?: "" val refreshToken = PreferenceManager.getString(ctx, TokenAuthenticator.TOKEN_KEY_REFRESH) ?: "" + val combinedToken = "[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() { @@ -64,7 +115,7 @@ 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 @@ -74,7 +125,7 @@ class RunnectDeveloperActivity : AppCompatActivity(R.layout.activity_runnect_dev setString(ctx, MySettingAccountInfoFragment.TOKEN_KEY_REFRESH, "none") } - destroyApp(ctx) + restartApplication(ctx) true } } @@ -142,6 +193,15 @@ class RunnectDeveloperActivity : AppCompatActivity(R.layout.activity_runnect_dev } } + private fun setPreferenceClickListener(key: String, onClick: () -> Unit) { + findPreference(key)?.let { pref -> + pref.setOnPreferenceClickListener { + onClick.invoke() + true + } + } + } + private fun copyToText(text: String): Boolean { val clipData = ClipData.newPlainText(CLIPBOARD_LABEL, text) clipboardManager?.setPrimaryClip(clipData) @@ -155,13 +215,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) + delay(2000) - activity?.finishAffinity() //루트액티비티 종료 - exitProcess(0) + Intent.makeRestartActivityTask(component).apply { + startActivity(this) + exitProcess(0) + } } } From 156d75151dd30bf15f15ac09bbe063de0c7dae01 Mon Sep 17 00:00:00 2001 From: Donghyeon0915 Date: Mon, 22 Apr 2024 04:04:24 +0900 Subject: [PATCH 03/23] =?UTF-8?q?[mod]=20#348=20=EC=95=B1=20=EC=9E=AC?= =?UTF-8?q?=EC=8B=9C=EC=9E=91=20=EB=AC=B8=EA=B5=AC=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/res/values/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c19876abe..17c5b1d08 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -119,7 +119,7 @@ 지도를 움직여 출발지를 설정해주세요 지도에서 선택 다음으로 - 3초 후 강제 종료됩니다. 앱을 재실행 해주세요. + 앱이 재실행됩니다. 잠시만 기다려주세요. "클립보드에 복사되었습니다." From 6617ef0a8321e5e636373b30f784a2cff5ba10ff Mon Sep 17 00:00:00 2001 From: Donghyeon0915 Date: Mon, 22 Apr 2024 04:05:07 +0900 Subject: [PATCH 04/23] =?UTF-8?q?[add]=20#348=20=EC=83=81=EB=8B=A8?= =?UTF-8?q?=EB=B0=94=20=EC=83=89=EC=83=81=20=EB=B3=80=EA=B2=BD=20=ED=99=95?= =?UTF-8?q?=EC=9E=A5=20=ED=95=A8=EC=88=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../runnect/util/extension/ContextExt.kt | 39 ++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/runnect/runnect/util/extension/ContextExt.kt b/app/src/main/java/com/runnect/runnect/util/extension/ContextExt.kt index 7caac816e..df4131940 100644 --- a/app/src/main/java/com/runnect/runnect/util/extension/ContextExt.kt +++ b/app/src/main/java/com/runnect/runnect/util/extension/ContextExt.kt @@ -7,9 +7,12 @@ import android.graphics.Color import android.graphics.Typeface import android.graphics.drawable.ColorDrawable import android.net.Uri +import android.os.Build import android.view.Gravity import android.view.LayoutInflater import android.view.View +import android.view.Window +import android.view.WindowInsetsController import android.view.inputmethod.InputMethodManager import android.widget.Toast import androidx.annotation.ColorRes @@ -33,6 +36,9 @@ import kotlinx.android.synthetic.main.custom_dialog_delete.view.tv_dialog import kotlinx.android.synthetic.main.custom_dialog_edit_mode.layout_delete_frame import kotlinx.android.synthetic.main.custom_dialog_edit_mode.layout_edit_frame import kotlinx.android.synthetic.main.fragment_bottom_sheet.btn_delete_yes +import timber.log.Timber +import java.lang.IllegalArgumentException +import java.lang.NullPointerException fun Context.setActivityDialog( layoutInflater: LayoutInflater, @@ -188,4 +194,35 @@ fun Context.colorOf(@ColorRes resId: Int) = ContextCompat.getColor(this, resId) fun Context.drawableOf(@DrawableRes resId: Int) = ContextCompat.getDrawable(this, resId) fun Context.fontOf(@FontRes resId: Int, @StyleRes style: Int): Typeface = - Typeface.create(ResourcesCompat.getFont(this, resId), style) \ No newline at end of file + Typeface.create(ResourcesCompat.getFont(this, resId), style) + +/** + * Status Bar 색상을 변경 + * isLightColor : 변경할 status bar 색상이 밝은 색인지 (시계, 노티 아이콘 등 색상을 어둡게 변경하기 위해서) + * colorResource : 변경할 색상 리소스 + */ +fun Context.setStatusBarColor(window: Window?, isLightColor: Boolean, colorResource: Int) { + runCatching { + window?.apply { + // 조건에 따른 시스템 UI 가시성 설정 + if (isLightColor) { + val flag = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS + } else { + View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + insetsController?.setSystemBarsAppearance(flag, flag) + } else { + decorView.systemUiVisibility = flag + } + } + + // Status bar 색상 설정 + statusBarColor = ContextCompat.getColor(context, colorResource) + } + }.onFailure { e -> + Timber.e("Failed to set status bar color: ${e.message}") + } +} \ No newline at end of file From 3b7704379d38e30f14c04c50bf53c3d9ef055fba Mon Sep 17 00:00:00 2001 From: Donghyeon0915 Date: Mon, 22 Apr 2024 04:06:29 +0900 Subject: [PATCH 05/23] =?UTF-8?q?[mod]=20#348=20=EA=B0=9C=EB=B0=9C?= =?UTF-8?q?=EC=9E=90=20=EB=AA=A8=EB=93=9C=20=ED=83=80=EC=9D=B4=ED=8B=80=20?= =?UTF-8?q?=EC=95=84=EC=9D=B4=EC=BD=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/debug/res/xml/preferences_developer_menu.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/src/debug/res/xml/preferences_developer_menu.xml b/app/src/debug/res/xml/preferences_developer_menu.xml index 4684b1ecc..a60f1b9a4 100644 --- a/app/src/debug/res/xml/preferences_developer_menu.xml +++ b/app/src/debug/res/xml/preferences_developer_menu.xml @@ -2,6 +2,11 @@ + + From f46154b6eef45289b423f2dc4a0e5ea2e1d3c648 Mon Sep 17 00:00:00 2001 From: Donghyeon0915 Date: Mon, 22 Apr 2024 04:06:53 +0900 Subject: [PATCH 06/23] =?UTF-8?q?[feat]=20#348=20=ED=86=A0=ED=81=B0=20?= =?UTF-8?q?=EA=B3=B5=EC=9C=A0=ED=95=98=EA=B8=B0=20=ED=95=AD=EB=AA=A9=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/debug/res/xml/preferences_developer_menu.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/src/debug/res/xml/preferences_developer_menu.xml b/app/src/debug/res/xml/preferences_developer_menu.xml index a60f1b9a4..6ccd5a5eb 100644 --- a/app/src/debug/res/xml/preferences_developer_menu.xml +++ b/app/src/debug/res/xml/preferences_developer_menu.xml @@ -20,6 +20,11 @@ android:key="dev_pref_key_refresh_token" android:title="리프레시 토큰" app:iconSpaceReserved="false" /> + + Date: Mon, 22 Apr 2024 04:07:49 +0900 Subject: [PATCH 07/23] =?UTF-8?q?[feat]=20#348=20=EC=84=9C=EB=B2=84=20?= =?UTF-8?q?=EC=83=81=ED=83=9C=20=EC=B2=B4=ED=81=AC=20=EB=A0=88=EC=9D=B4?= =?UTF-8?q?=EC=95=84=EC=9B=83=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../custom/ServerStatusPreference.kt | 43 +++++++++++++++++++ .../shape_server_status_indicator.xml | 5 +++ .../res/layout/pref_server_status_layout.xml | 28 ++++++++++++ .../res/xml/preferences_developer_menu.xml | 19 +++++++- app/src/main/res/values/colors.xml | 5 +++ 5 files changed, 99 insertions(+), 1 deletion(-) create mode 100644 app/src/debug/java/com/runnect/runnect/developer/presentation/custom/ServerStatusPreference.kt create mode 100644 app/src/debug/res/drawable/shape_server_status_indicator.xml create mode 100644 app/src/debug/res/layout/pref_server_status_layout.xml diff --git a/app/src/debug/java/com/runnect/runnect/developer/presentation/custom/ServerStatusPreference.kt b/app/src/debug/java/com/runnect/runnect/developer/presentation/custom/ServerStatusPreference.kt new file mode 100644 index 000000000..99c38c089 --- /dev/null +++ b/app/src/debug/java/com/runnect/runnect/developer/presentation/custom/ServerStatusPreference.kt @@ -0,0 +1,43 @@ +package com.runnect.runnect.developer.presentation.custom + +import android.content.Context +import android.graphics.drawable.GradientDrawable +import android.util.AttributeSet +import android.view.View +import android.widget.TextView +import androidx.preference.Preference +import androidx.preference.PreferenceViewHolder +import com.runnect.runnect.R +import com.runnect.runnect.developer.enum.ServerStatus + +class ServerStatusPreference @JvmOverloads constructor( + context: Context, + attrs: AttributeSet, + defStyleAttr: Int = androidx.preference.R.attr.preferenceStyle, + defStyleRes: Int = 0 +) : Preference(context, attrs, defStyleAttr, defStyleRes) { + + init { + widgetLayoutResource = R.layout.pref_server_status_layout + } + + private var serverStatus: ServerStatus = ServerStatus.CHECKING + + override fun onBindViewHolder(holder: PreferenceViewHolder) { + super.onBindViewHolder(holder) + val indicator: View = holder.findViewById(R.id.viewIndicator) + val statusText: TextView = holder.findViewById(R.id.tvStatus) as TextView + val background = indicator.background as? GradientDrawable + + holder.itemView.post { + background?.setColor(serverStatus.getColor(context)) + statusText.text = serverStatus.statusText + summary = serverStatus.summary + } + } + + fun setServerStatus(status: ServerStatus) { + serverStatus = status + notifyChanged() + } +} \ No newline at end of file diff --git a/app/src/debug/res/drawable/shape_server_status_indicator.xml b/app/src/debug/res/drawable/shape_server_status_indicator.xml new file mode 100644 index 000000000..7858227bb --- /dev/null +++ b/app/src/debug/res/drawable/shape_server_status_indicator.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/app/src/debug/res/layout/pref_server_status_layout.xml b/app/src/debug/res/layout/pref_server_status_layout.xml new file mode 100644 index 000000000..d047d36b4 --- /dev/null +++ b/app/src/debug/res/layout/pref_server_status_layout.xml @@ -0,0 +1,28 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/debug/res/xml/preferences_developer_menu.xml b/app/src/debug/res/xml/preferences_developer_menu.xml index 6ccd5a5eb..24c0558e3 100644 --- a/app/src/debug/res/xml/preferences_developer_menu.xml +++ b/app/src/debug/res/xml/preferences_developer_menu.xml @@ -38,6 +38,23 @@ app:iconSpaceReserved="false" /> + + + + + + + @@ -49,7 +66,7 @@ + app:iconSpaceReserved="false" /> #26593EEC #FEE500 #80313131 + #9E9E9E + #5CFF62 + #FF473A + #FFC617 + #55B4FF \ No newline at end of file From 6663d2209588108d90d0b4c0ef4b3c03935d4508 Mon Sep 17 00:00:00 2001 From: Donghyeon0915 Date: Mon, 22 Apr 2024 04:09:00 +0900 Subject: [PATCH 08/23] =?UTF-8?q?[feat]=20#348=20=EC=84=9C=EB=B2=84=20?= =?UTF-8?q?=EC=83=81=ED=83=9C=20=EC=B2=B4=ED=81=AC=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data/dto/ResponseServerStatus.kt | 10 +++ .../repository/ServerStatusRepositoryImpl.kt | 18 +++++ .../data/service/ServerStatusService.kt | 14 ++++ .../remote/RemoteServerStatusDataSource.kt | 16 ++++ .../domain/ServerStatusRepository.kt | 8 ++ .../presentation/RunnectDeveloperViewModel.kt | 75 +++++++++++++++++++ .../runnect/runnect/di/RepositoryModule.kt | 5 ++ .../com/runnect/runnect/di/RetrofitModule.kt | 23 ++++++ .../com/runnect/runnect/di/ServiceModule.kt | 6 ++ 9 files changed, 175 insertions(+) create mode 100644 app/src/debug/java/com/runnect/runnect/developer/data/dto/ResponseServerStatus.kt create mode 100644 app/src/debug/java/com/runnect/runnect/developer/data/repository/ServerStatusRepositoryImpl.kt create mode 100644 app/src/debug/java/com/runnect/runnect/developer/data/service/ServerStatusService.kt create mode 100644 app/src/debug/java/com/runnect/runnect/developer/data/source/remote/RemoteServerStatusDataSource.kt create mode 100644 app/src/debug/java/com/runnect/runnect/developer/domain/ServerStatusRepository.kt create mode 100644 app/src/debug/java/com/runnect/runnect/developer/presentation/RunnectDeveloperViewModel.kt diff --git a/app/src/debug/java/com/runnect/runnect/developer/data/dto/ResponseServerStatus.kt b/app/src/debug/java/com/runnect/runnect/developer/data/dto/ResponseServerStatus.kt new file mode 100644 index 000000000..31b71cc41 --- /dev/null +++ b/app/src/debug/java/com/runnect/runnect/developer/data/dto/ResponseServerStatus.kt @@ -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 +) \ No newline at end of file diff --git a/app/src/debug/java/com/runnect/runnect/developer/data/repository/ServerStatusRepositoryImpl.kt b/app/src/debug/java/com/runnect/runnect/developer/data/repository/ServerStatusRepositoryImpl.kt new file mode 100644 index 000000000..3a85f02ad --- /dev/null +++ b/app/src/debug/java/com/runnect/runnect/developer/data/repository/ServerStatusRepositoryImpl.kt @@ -0,0 +1,18 @@ +package com.runnect.runnect.developer.data.repository + +import com.runnect.runnect.data.network.FlowResult +import com.runnect.runnect.data.network.toEntity +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 { + return serverStatusDataSource.checkServerStatus(serverUrl).toEntity { + it.status + } + } +} \ No newline at end of file diff --git a/app/src/debug/java/com/runnect/runnect/developer/data/service/ServerStatusService.kt b/app/src/debug/java/com/runnect/runnect/developer/data/service/ServerStatusService.kt new file mode 100644 index 000000000..6316f7a83 --- /dev/null +++ b/app/src/debug/java/com/runnect/runnect/developer/data/service/ServerStatusService.kt @@ -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> +} \ No newline at end of file diff --git a/app/src/debug/java/com/runnect/runnect/developer/data/source/remote/RemoteServerStatusDataSource.kt b/app/src/debug/java/com/runnect/runnect/developer/data/source/remote/RemoteServerStatusDataSource.kt new file mode 100644 index 000000000..a62e06819 --- /dev/null +++ b/app/src/debug/java/com/runnect/runnect/developer/data/source/remote/RemoteServerStatusDataSource.kt @@ -0,0 +1,16 @@ +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 kotlinx.coroutines.flow.Flow +import javax.inject.Inject + +class RemoteServerStatusDataSource @Inject constructor( + private val serverStatusService: ServerStatusService, +) { + + fun checkServerStatus(serverUrl: String): FlowResult { + return serverStatusService.checkServerStatus(serverUrl) + } +} \ No newline at end of file diff --git a/app/src/debug/java/com/runnect/runnect/developer/domain/ServerStatusRepository.kt b/app/src/debug/java/com/runnect/runnect/developer/domain/ServerStatusRepository.kt new file mode 100644 index 000000000..c5a788c1f --- /dev/null +++ b/app/src/debug/java/com/runnect/runnect/developer/domain/ServerStatusRepository.kt @@ -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 +} \ No newline at end of file diff --git a/app/src/debug/java/com/runnect/runnect/developer/presentation/RunnectDeveloperViewModel.kt b/app/src/debug/java/com/runnect/runnect/developer/presentation/RunnectDeveloperViewModel.kt new file mode 100644 index 000000000..2da8c3891 --- /dev/null +++ b/app/src/debug/java/com/runnect/runnect/developer/presentation/RunnectDeveloperViewModel.kt @@ -0,0 +1,75 @@ +package com.runnect.runnect.developer.presentation + +import androidx.lifecycle.viewModelScope +import com.runnect.runnect.BuildConfig +import com.runnect.runnect.developer.domain.ServerStatusRepository +import com.runnect.runnect.presentation.base.BaseViewModel +import com.runnect.runnect.util.extension.onEachResult +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.channels.BufferOverflow +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.SharedFlow +import kotlinx.coroutines.flow.asSharedFlow +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.onStart +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class RunnectDeveloperViewModel @Inject constructor( + private val serverStatusRepository: ServerStatusRepository +) : BaseViewModel() { + + private val _prodStatus: MutableSharedFlow = MutableSharedFlow( + replay = 1, + onBufferOverflow = BufferOverflow.DROP_OLDEST + ) + val prodStatus: SharedFlow = _prodStatus.asSharedFlow() + + private val _testStatus: MutableSharedFlow = MutableSharedFlow( + replay = 1, + onBufferOverflow = BufferOverflow.DROP_OLDEST + ) + val testStatus: SharedFlow = _testStatus.asSharedFlow() + + fun checkProdServerStatus() { + val prodServerUrl = "${BuildConfig.RUNNECT_PROD_URL}/actuator/health" + checkServerStatus(prodServerUrl, _prodStatus) + } + + fun checkTestServerStatus() { + val testServerUrl = "${BuildConfig.RUNNECT_NODE_URL}/actuator/health" + checkServerStatus(testServerUrl, _testStatus) + } + + private fun checkServerStatus( + serverUrl: String, + state: MutableSharedFlow + ) = launchWithHandler { + serverStatusRepository.checkServerStatus(serverUrl) + .onStart { + state.emit(ServerState.Checking) + }.onEachResult( + onSuccess = { + state.tryEmit(ServerState.Running) + }, + onFailure = { + when (it.message) { + "DOWN", + "OUT_OF_SERVICE" -> ServerState.Degraded + else -> ServerState.NotRunning + }.let(state::tryEmit) + } + ).catch { + state.tryEmit(ServerState.NotRunning) + }.collect() + } + + sealed interface ServerState { + object Running : ServerState + object Degraded : ServerState + object NotRunning : ServerState + object Checking : ServerState + } +} \ No newline at end of file diff --git a/app/src/main/java/com/runnect/runnect/di/RepositoryModule.kt b/app/src/main/java/com/runnect/runnect/di/RepositoryModule.kt index 7c7c7554b..74382a1c3 100644 --- a/app/src/main/java/com/runnect/runnect/di/RepositoryModule.kt +++ b/app/src/main/java/com/runnect/runnect/di/RepositoryModule.kt @@ -3,6 +3,8 @@ package com.runnect.runnect.di import com.runnect.runnect.data.repository.* import com.runnect.runnect.data.service.* import com.runnect.runnect.data.source.remote.* +import com.runnect.runnect.developer.data.repository.ServerStatusRepositoryImpl +import com.runnect.runnect.developer.domain.ServerStatusRepository import com.runnect.runnect.domain.* import com.runnect.runnect.domain.repository.BannerRepository import com.runnect.runnect.domain.repository.CourseRepository @@ -48,4 +50,7 @@ interface RepositoryModule { @Binds fun bindBannerRepository(bannerRepositoryImpl: BannerRepositoryImpl): BannerRepository + @Singleton + @Binds + fun bindServerStatusRepository(serverStatusRepositoryImpl: ServerStatusRepositoryImpl): ServerStatusRepository } \ No newline at end of file diff --git a/app/src/main/java/com/runnect/runnect/di/RetrofitModule.kt b/app/src/main/java/com/runnect/runnect/di/RetrofitModule.kt index e4a321326..6fcc7ce09 100644 --- a/app/src/main/java/com/runnect/runnect/di/RetrofitModule.kt +++ b/app/src/main/java/com/runnect/runnect/di/RetrofitModule.kt @@ -4,6 +4,7 @@ import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFact import com.runnect.runnect.BuildConfig import com.runnect.runnect.application.ApplicationClass import com.runnect.runnect.data.network.calladapter.ResultCallAdapterFactory +import com.runnect.runnect.data.network.calladapter.flow.FlowCallAdapterFactory import com.runnect.runnect.data.network.interceptor.ResponseInterceptor import com.runnect.runnect.data.service.* import com.runnect.runnect.data.repository.* @@ -36,6 +37,10 @@ object RetrofitModule { @Retention(AnnotationRetention.BINARY) annotation class RetrofitV2 + @Qualifier + @Retention(AnnotationRetention.BINARY) + annotation class RetrofitFlow + @Qualifier @Retention(AnnotationRetention.BINARY) annotation class Tmap @@ -131,6 +136,24 @@ object RetrofitModule { return retrofit ?: throw RuntimeException("Retrofit creation failed.") } + @OptIn(ExperimentalSerializationApi::class, InternalCoroutinesApi::class) + @Provides + @Singleton + @RetrofitFlow + fun provideRunnectRetrofitFlow( + @HttpClientV2 client: OkHttpClient + ): Retrofit { + val baseUrl = ApplicationClass.getBaseUrl() + val retrofit = Retrofit.Builder() + .baseUrl(baseUrl) + .client(client) + .addConverterFactory(GsonConverterFactory.create()) + .addCallAdapterFactory(FlowCallAdapterFactory.create()) + .build() + + return retrofit ?: throw RuntimeException("Retrofit creation failed.") + } + @OptIn(ExperimentalSerializationApi::class, InternalCoroutinesApi::class) @Provides @Singleton diff --git a/app/src/main/java/com/runnect/runnect/di/ServiceModule.kt b/app/src/main/java/com/runnect/runnect/di/ServiceModule.kt index d83a93bb9..8cf9e6b37 100644 --- a/app/src/main/java/com/runnect/runnect/di/ServiceModule.kt +++ b/app/src/main/java/com/runnect/runnect/di/ServiceModule.kt @@ -7,6 +7,7 @@ import com.runnect.runnect.data.service.* import com.runnect.runnect.data.service.LoginService import com.runnect.runnect.data.service.UserService import com.runnect.runnect.data.source.remote.* +import com.runnect.runnect.developer.data.service.ServerStatusService import com.runnect.runnect.domain.* import dagger.Module import dagger.Provides @@ -34,6 +35,11 @@ object ServiceModule { fun providePCourseService(@RetrofitModule.RetrofitV2 retrofitV2: Retrofit) = retrofitV2.create(CourseService::class.java) + @Singleton + @Provides + fun provideSeverStatusService(@RetrofitModule.RetrofitFlow retrofitV2Flow: Retrofit) = + retrofitV2Flow.create(ServerStatusService::class.java) + @Singleton @Provides fun provideKSearchService(@RetrofitModule.Tmap tmapRetrofit: Retrofit) = From 9aa7899d99d7a4d53eed842db0f90fe90e52df91 Mon Sep 17 00:00:00 2001 From: Donghyeon0915 Date: Mon, 22 Apr 2024 04:09:11 +0900 Subject: [PATCH 09/23] =?UTF-8?q?[add]=20#348=20=EC=84=9C=EB=B2=84=20?= =?UTF-8?q?=EC=83=81=ED=83=9C=20enum=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../runnect/developer/enum/ServerStatus.kt | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 app/src/debug/java/com/runnect/runnect/developer/enum/ServerStatus.kt diff --git a/app/src/debug/java/com/runnect/runnect/developer/enum/ServerStatus.kt b/app/src/debug/java/com/runnect/runnect/developer/enum/ServerStatus.kt new file mode 100644 index 000000000..897462fff --- /dev/null +++ b/app/src/debug/java/com/runnect/runnect/developer/enum/ServerStatus.kt @@ -0,0 +1,29 @@ +package com.runnect.runnect.developer.enum + +import android.content.Context +import com.runnect.runnect.R +import com.runnect.runnect.developer.presentation.RunnectDeveloperViewModel.ServerState + +enum class ServerStatus( + private val colorRes: Int, + val statusText: String, + val summary: String, +) { + CHECKING(R.color.blue, "Checking...", "서버 상태를 확인 중입니다."), + RUNNING(R.color.green, "Running", "서버가 정상적으로 작동하고 있습니다."), + DEGRADED(R.color.orange, "Degraded", "작동하고 있으나 일부 기능에 문제가 있습니다."), + NOT_RUNNING(R.color.red, "Not Running", "작동 중이지 않거나 오류가 있습니다."); + + fun getColor(context: Context): Int = context.getColor(colorRes) + + companion object { + fun getStatus(state: ServerState): ServerStatus { + return when (state) { + ServerState.Running -> RUNNING + ServerState.Degraded -> DEGRADED + ServerState.NotRunning -> NOT_RUNNING + ServerState.Checking -> CHECKING + } + } + } +} \ No newline at end of file From 44413562816ce550d1b6eb9c541c916ad1c7fd8d Mon Sep 17 00:00:00 2001 From: Donghyeon0915 Date: Mon, 22 Apr 2024 04:13:00 +0900 Subject: [PATCH 10/23] =?UTF-8?q?[feat]=20#348=20FlowCallAdapter=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 네트워크 요청에서 예외가 발생하면 flow의 catch 연산자로 잡히지 않음 * Flow로 변환되기 전에 Interceptor에서 예외가 발생하기 때문 * 따라서, Retrofit에서 Flow로 반환하도록 변경 * 네트워크 요청에서 발생하는 예외도 flow로 보내도록 수정 --- .../runnect/data/network/FlowResultExt.kt | 44 +++++++ .../calladapter/flow/FlowCallAdapter.kt | 112 ++++++++++++++++++ .../flow/FlowCallAdapterFactory.kt | 46 +++++++ .../runnect/data/network/mapToFlowResult.kt | 23 ---- .../runnect/runnect/util/extension/FlowExt.kt | 30 +++++ 5 files changed, 232 insertions(+), 23 deletions(-) create mode 100644 app/src/main/java/com/runnect/runnect/data/network/FlowResultExt.kt create mode 100644 app/src/main/java/com/runnect/runnect/data/network/calladapter/flow/FlowCallAdapter.kt create mode 100644 app/src/main/java/com/runnect/runnect/data/network/calladapter/flow/FlowCallAdapterFactory.kt delete mode 100644 app/src/main/java/com/runnect/runnect/data/network/mapToFlowResult.kt diff --git a/app/src/main/java/com/runnect/runnect/data/network/FlowResultExt.kt b/app/src/main/java/com/runnect/runnect/data/network/FlowResultExt.kt new file mode 100644 index 000000000..d407aac83 --- /dev/null +++ b/app/src/main/java/com/runnect/runnect/data/network/FlowResultExt.kt @@ -0,0 +1,44 @@ +package com.runnect.runnect.data.network + +import com.runnect.runnect.domain.common.RunnectException +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.map + +typealias FlowResult = Flow> + +@Deprecated(message = "Use toDomainResult instead.") +fun Result.mapToFlowResult( + mapper: (R) -> D +): FlowResult = flow { + val result = when { + this@mapToFlowResult.isSuccess -> Result.success( + // CallAdapter에서 body가 null인 경우도 걸러주고 있으므로 + // Result.success의 데이터가 null인 경우는 없을듯함 + mapper(this@mapToFlowResult.getOrNull()!!) + ) + + else -> Result.failure( + this@mapToFlowResult.exceptionOrNull() ?: RunnectException() + ) + } + + emit(result) +} + +fun FlowResult.toEntity(mapper: (R) -> D): FlowResult { + fun Result.parseResult(mapper: (R) -> D): Result = when { + this.isSuccess -> Result.success( + // CallAdapter에서 body가 null인 경우도 걸러주고 있으므로 + // Result.success의 데이터가 null인 경우는 없을듯함 + mapper(this.getOrNull()!!) + ) + else -> Result.failure( + this.exceptionOrNull() ?: RunnectException() + ) + } + + return map { + it.parseResult(mapper) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/runnect/runnect/data/network/calladapter/flow/FlowCallAdapter.kt b/app/src/main/java/com/runnect/runnect/data/network/calladapter/flow/FlowCallAdapter.kt new file mode 100644 index 000000000..5cadd5e17 --- /dev/null +++ b/app/src/main/java/com/runnect/runnect/data/network/calladapter/flow/FlowCallAdapter.kt @@ -0,0 +1,112 @@ +package com.runnect.runnect.data.network.calladapter.flow + +import com.google.gson.Gson +import com.runnect.runnect.data.dto.response.base.ErrorResponse +import com.runnect.runnect.developer.data.dto.ResponseServerStatus +import com.runnect.runnect.domain.common.RunnectException +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.suspendCancellableCoroutine +import retrofit2.Call +import retrofit2.CallAdapter +import retrofit2.Callback +import retrofit2.Response +import java.lang.reflect.Type +import kotlin.coroutines.resume +import kotlin.coroutines.resumeWithException + +class FlowCallAdapter( + private val responseType: Type +) : CallAdapter>> { + + private val gson = Gson() + override fun responseType() = responseType + + // Retrofit의 Call을 Result<>로 변환 + override fun adapt(call: Call): Flow> = flow { + val apiResult = suspendCancellableCoroutine { continuation -> + runCatching { + call.enqueue(object : Callback { + override fun onResponse(call: Call, response: Response) { + val result = parseResponse(response) + continuation.resume(result) + } + + override fun onFailure(call: Call, t: Throwable) { + continuation.resumeWithException(t) + } + }) + }.onFailure { + continuation.resumeWithException(it) + } + + // Coroutine이 취소 되면 네트워크 요청도 취소 + continuation.invokeOnCancellation { + call.cancel() + } + } + + emit(apiResult) + } + + private fun parseResponse(response: Response): Result { + if (response.isSuccessful) { + return response.body()?.let { + Result.success(it) + } ?: Result.failure( + RunnectException( + code = response.code(), + message = ERROR_MSG_RESPONSE_IS_NULL + ) + ) + } + + return Result.failure(parseErrorResponse(response)) + } + + // Response에서 오류를 파싱하여 RunnectException 객체를 생성 + private fun parseErrorResponse(response: Response<*>): RunnectException { + val code = response.code() + val errorBodyString = response.errorBody()?.string() + + errorBodyString?.let { errorBody -> + // 서버 상태 응답 파싱 시도 + parseServerErrorResponse(code, errorBody)?.let { return it } + // 일반 에러 응답 파싱 시도 + parseBaseErrorResponse(code, errorBody)?.let { return it } + } + + return RunnectException(code = code, message = ERROR_MSG_COMMON) + } + + // 서버 상태 응답을 파싱하여 RunnectException 생성 + private fun parseServerErrorResponse(code: Int, errorBody: String): RunnectException? { + return runCatching { + gson.fromJson(errorBody, ResponseServerStatus::class.java) + }.getOrNull()?.let { serverStatusResponse -> + val status = serverStatusResponse.status + if (status.toIntOrNull() != null) { + null + } else { + RunnectException(code, status) + } + } + } + + // 일반적인 에러 응답을 파싱하여 RunnectException 생성 + private fun parseBaseErrorResponse(code: Int, errorBody: String): RunnectException? { + return runCatching { + gson.fromJson(errorBody, ErrorResponse::class.java) + }.getOrNull()?.let { errorResponse -> + RunnectException( + code = code, + message = errorResponse.message ?: errorResponse.error ?: ERROR_MSG_COMMON + ) + } + } + + companion object { + private const val ERROR_MSG_COMMON = "알 수 없는 에러가 발생하였습니다." + private const val ERROR_MSG_RESPONSE_IS_NULL = "데이터를 불러올 수 없습니다." + } +} \ No newline at end of file diff --git a/app/src/main/java/com/runnect/runnect/data/network/calladapter/flow/FlowCallAdapterFactory.kt b/app/src/main/java/com/runnect/runnect/data/network/calladapter/flow/FlowCallAdapterFactory.kt new file mode 100644 index 000000000..d6419eb26 --- /dev/null +++ b/app/src/main/java/com/runnect/runnect/data/network/calladapter/flow/FlowCallAdapterFactory.kt @@ -0,0 +1,46 @@ +package com.runnect.runnect.data.network.calladapter.flow + +import kotlinx.coroutines.flow.Flow +import retrofit2.CallAdapter +import retrofit2.Retrofit +import java.lang.reflect.ParameterizedType +import java.lang.reflect.Type + +class FlowCallAdapterFactory private constructor() : CallAdapter.Factory() { + + override fun get( + returnType: Type, + annotations: Array, + retrofit: Retrofit + ): CallAdapter<*, *>? { + // 최상위 타입이 Flow인지 체크 + if (getRawType(returnType) != Flow::class.java) { + return null + } + + check(returnType is ParameterizedType) { + "Flow return type must be parameterized as Flow or Flow" + } + + val responseType = getParameterUpperBound(0, returnType) + if (getRawType(responseType) != Result::class.java) { + return null + } + + check(responseType is ParameterizedType) { + "ApiResult return type must be parameterized as ApiResult or ApiResult" + } + + return FlowCallAdapter( + getParameterUpperBound( + 0, + responseType + ) + ) + } + + companion object { + @JvmStatic + fun create() = FlowCallAdapterFactory() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/runnect/runnect/data/network/mapToFlowResult.kt b/app/src/main/java/com/runnect/runnect/data/network/mapToFlowResult.kt deleted file mode 100644 index 958d453f1..000000000 --- a/app/src/main/java/com/runnect/runnect/data/network/mapToFlowResult.kt +++ /dev/null @@ -1,23 +0,0 @@ -package com.runnect.runnect.data.network - -import com.runnect.runnect.domain.common.RunnectException -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.flow - -fun Result.mapToFlowResult( - mapper: (R) -> D -): Flow> = flow { - val result = when { - this@mapToFlowResult.isSuccess -> Result.success( - // CallAdapter에서 body가 null인 경우도 걸러주고 있으므로 - // Result.success의 데이터가 null인 경우는 없을듯함 - mapper(this@mapToFlowResult.getOrNull()!!) - ) - - else -> Result.failure( - this@mapToFlowResult.exceptionOrNull() ?: RunnectException() - ) - } - - emit(result) -} \ No newline at end of file diff --git a/app/src/main/java/com/runnect/runnect/util/extension/FlowExt.kt b/app/src/main/java/com/runnect/runnect/util/extension/FlowExt.kt index b640d4f61..b78902590 100644 --- a/app/src/main/java/com/runnect/runnect/util/extension/FlowExt.kt +++ b/app/src/main/java/com/runnect/runnect/util/extension/FlowExt.kt @@ -1,6 +1,13 @@ package com.runnect.runnect.util.extension +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.launch suspend fun Flow>.collectResult( onSuccess: (T) -> Unit, @@ -9,4 +16,27 @@ suspend fun Flow>.collectResult( collect { result -> result.fold(onSuccess, onFailure) } +} + +fun Flow>.onEachResult( + onSuccess: (T) -> Unit, + onFailure: (Throwable) -> Unit +): Flow> { + return onEach { result -> + result.fold(onSuccess, onFailure) + } +} + +fun LifecycleOwner.repeatOnStarted(block: suspend CoroutineScope.() -> Unit) { + lifecycleScope.launch { + lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED, block) + } +} + +fun LifecycleOwner.repeatOnStarted(vararg blocks: suspend CoroutineScope.() -> Unit) { + blocks.forEach { + lifecycleScope.launch { + lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED, it) + } + } } \ No newline at end of file From bc232a9996ac219b8016c491b28f1cfa1cb94cce Mon Sep 17 00:00:00 2001 From: Donghyeon0915 Date: Mon, 22 Apr 2024 04:13:08 +0900 Subject: [PATCH 11/23] =?UTF-8?q?[mod]=20#348=20=ED=8C=A8=ED=82=A4?= =?UTF-8?q?=EC=A7=80=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/res/layout/activity_runnect_developer.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/layout/activity_runnect_developer.xml b/app/src/main/res/layout/activity_runnect_developer.xml index 44d518832..f94693f2f 100644 --- a/app/src/main/res/layout/activity_runnect_developer.xml +++ b/app/src/main/res/layout/activity_runnect_developer.xml @@ -5,11 +5,11 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/white" - tools:context=".developer.RunnectDeveloperActivity"> + tools:context=".developer.presentation.RunnectDeveloperActivity"> From 30eca4c8672defc5434e7bcdd132459b9816c5fe Mon Sep 17 00:00:00 2001 From: Donghyeon0915 Date: Wed, 24 Apr 2024 21:46:36 +0900 Subject: [PATCH 12/23] =?UTF-8?q?[refactor]=20#348=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/ServerStatusRepositoryImpl.kt | 4 +-- .../runnect/developer/enum/ServerStatus.kt | 4 +-- .../presentation/RunnectDeveloperViewModel.kt | 16 +++++------- .../runnect/data/network/FlowResultExt.kt | 4 +-- .../calladapter/flow/FlowCallAdapter.kt | 26 +++---------------- .../flow/FlowCallAdapterFactory.kt | 2 +- 6 files changed, 17 insertions(+), 39 deletions(-) diff --git a/app/src/debug/java/com/runnect/runnect/developer/data/repository/ServerStatusRepositoryImpl.kt b/app/src/debug/java/com/runnect/runnect/developer/data/repository/ServerStatusRepositoryImpl.kt index 3a85f02ad..fe5a81682 100644 --- a/app/src/debug/java/com/runnect/runnect/developer/data/repository/ServerStatusRepositoryImpl.kt +++ b/app/src/debug/java/com/runnect/runnect/developer/data/repository/ServerStatusRepositoryImpl.kt @@ -1,7 +1,7 @@ package com.runnect.runnect.developer.data.repository import com.runnect.runnect.data.network.FlowResult -import com.runnect.runnect.data.network.toEntity +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 @@ -11,7 +11,7 @@ class ServerStatusRepositoryImpl @Inject constructor( ) : ServerStatusRepository { override suspend fun checkServerStatus(serverUrl: String): FlowResult { - return serverStatusDataSource.checkServerStatus(serverUrl).toEntity { + return serverStatusDataSource.checkServerStatus(serverUrl).toEntityResult { it.status } } diff --git a/app/src/debug/java/com/runnect/runnect/developer/enum/ServerStatus.kt b/app/src/debug/java/com/runnect/runnect/developer/enum/ServerStatus.kt index 897462fff..b48aaf4ae 100644 --- a/app/src/debug/java/com/runnect/runnect/developer/enum/ServerStatus.kt +++ b/app/src/debug/java/com/runnect/runnect/developer/enum/ServerStatus.kt @@ -12,7 +12,7 @@ enum class ServerStatus( CHECKING(R.color.blue, "Checking...", "서버 상태를 확인 중입니다."), RUNNING(R.color.green, "Running", "서버가 정상적으로 작동하고 있습니다."), DEGRADED(R.color.orange, "Degraded", "작동하고 있으나 일부 기능에 문제가 있습니다."), - NOT_RUNNING(R.color.red, "Not Running", "작동 중이지 않거나 오류가 있습니다."); + UNKNOWN(R.color.red, "Unknown", "작동 중이지 않거나 오류가 있습니다."); fun getColor(context: Context): Int = context.getColor(colorRes) @@ -21,7 +21,7 @@ enum class ServerStatus( return when (state) { ServerState.Running -> RUNNING ServerState.Degraded -> DEGRADED - ServerState.NotRunning -> NOT_RUNNING + ServerState.Unknown -> UNKNOWN ServerState.Checking -> CHECKING } } diff --git a/app/src/debug/java/com/runnect/runnect/developer/presentation/RunnectDeveloperViewModel.kt b/app/src/debug/java/com/runnect/runnect/developer/presentation/RunnectDeveloperViewModel.kt index 2da8c3891..d62a3f0b3 100644 --- a/app/src/debug/java/com/runnect/runnect/developer/presentation/RunnectDeveloperViewModel.kt +++ b/app/src/debug/java/com/runnect/runnect/developer/presentation/RunnectDeveloperViewModel.kt @@ -1,8 +1,8 @@ package com.runnect.runnect.developer.presentation -import androidx.lifecycle.viewModelScope import com.runnect.runnect.BuildConfig import com.runnect.runnect.developer.domain.ServerStatusRepository +import com.runnect.runnect.domain.common.getCode import com.runnect.runnect.presentation.base.BaseViewModel import com.runnect.runnect.util.extension.onEachResult import dagger.hilt.android.lifecycle.HiltViewModel @@ -13,7 +13,6 @@ import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.onStart -import kotlinx.coroutines.launch import javax.inject.Inject @HiltViewModel @@ -39,7 +38,7 @@ class RunnectDeveloperViewModel @Inject constructor( } fun checkTestServerStatus() { - val testServerUrl = "${BuildConfig.RUNNECT_NODE_URL}/actuator/health" + val testServerUrl = "${BuildConfig.RUNNECT_DEV_URL}/actuator/health" checkServerStatus(testServerUrl, _testStatus) } @@ -55,21 +54,20 @@ class RunnectDeveloperViewModel @Inject constructor( state.tryEmit(ServerState.Running) }, onFailure = { - when (it.message) { - "DOWN", - "OUT_OF_SERVICE" -> ServerState.Degraded - else -> ServerState.NotRunning + when (it.getCode()) { + 503 -> ServerState.Degraded + else -> ServerState.Unknown }.let(state::tryEmit) } ).catch { - state.tryEmit(ServerState.NotRunning) + state.tryEmit(ServerState.Unknown) }.collect() } sealed interface ServerState { object Running : ServerState object Degraded : ServerState - object NotRunning : ServerState + object Unknown : ServerState object Checking : ServerState } } \ No newline at end of file diff --git a/app/src/main/java/com/runnect/runnect/data/network/FlowResultExt.kt b/app/src/main/java/com/runnect/runnect/data/network/FlowResultExt.kt index d407aac83..a790933a6 100644 --- a/app/src/main/java/com/runnect/runnect/data/network/FlowResultExt.kt +++ b/app/src/main/java/com/runnect/runnect/data/network/FlowResultExt.kt @@ -7,7 +7,7 @@ import kotlinx.coroutines.flow.map typealias FlowResult = Flow> -@Deprecated(message = "Use toDomainResult instead.") +@Deprecated(message = "Use toEntityResult instead.") fun Result.mapToFlowResult( mapper: (R) -> D ): FlowResult = flow { @@ -26,7 +26,7 @@ fun Result.mapToFlowResult( emit(result) } -fun FlowResult.toEntity(mapper: (R) -> D): FlowResult { +fun FlowResult.toEntityResult(mapper: (R) -> D): FlowResult { fun Result.parseResult(mapper: (R) -> D): Result = when { this.isSuccess -> Result.success( // CallAdapter에서 body가 null인 경우도 걸러주고 있으므로 diff --git a/app/src/main/java/com/runnect/runnect/data/network/calladapter/flow/FlowCallAdapter.kt b/app/src/main/java/com/runnect/runnect/data/network/calladapter/flow/FlowCallAdapter.kt index 5cadd5e17..da52cd7f7 100644 --- a/app/src/main/java/com/runnect/runnect/data/network/calladapter/flow/FlowCallAdapter.kt +++ b/app/src/main/java/com/runnect/runnect/data/network/calladapter/flow/FlowCallAdapter.kt @@ -2,7 +2,6 @@ package com.runnect.runnect.data.network.calladapter.flow import com.google.gson.Gson import com.runnect.runnect.data.dto.response.base.ErrorResponse -import com.runnect.runnect.developer.data.dto.ResponseServerStatus import com.runnect.runnect.domain.common.RunnectException import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow @@ -69,28 +68,9 @@ class FlowCallAdapter( val code = response.code() val errorBodyString = response.errorBody()?.string() - errorBodyString?.let { errorBody -> - // 서버 상태 응답 파싱 시도 - parseServerErrorResponse(code, errorBody)?.let { return it } - // 일반 에러 응답 파싱 시도 - parseBaseErrorResponse(code, errorBody)?.let { return it } - } - - return RunnectException(code = code, message = ERROR_MSG_COMMON) - } - - // 서버 상태 응답을 파싱하여 RunnectException 생성 - private fun parseServerErrorResponse(code: Int, errorBody: String): RunnectException? { - return runCatching { - gson.fromJson(errorBody, ResponseServerStatus::class.java) - }.getOrNull()?.let { serverStatusResponse -> - val status = serverStatusResponse.status - if (status.toIntOrNull() != null) { - null - } else { - RunnectException(code, status) - } - } + return errorBodyString?.let { errorBody -> + parseBaseErrorResponse(code, errorBody) + } ?: RunnectException(code, ERROR_MSG_COMMON) } // 일반적인 에러 응답을 파싱하여 RunnectException 생성 diff --git a/app/src/main/java/com/runnect/runnect/data/network/calladapter/flow/FlowCallAdapterFactory.kt b/app/src/main/java/com/runnect/runnect/data/network/calladapter/flow/FlowCallAdapterFactory.kt index d6419eb26..c0a7599a8 100644 --- a/app/src/main/java/com/runnect/runnect/data/network/calladapter/flow/FlowCallAdapterFactory.kt +++ b/app/src/main/java/com/runnect/runnect/data/network/calladapter/flow/FlowCallAdapterFactory.kt @@ -28,7 +28,7 @@ class FlowCallAdapterFactory private constructor() : CallAdapter.Factory() { } check(responseType is ParameterizedType) { - "ApiResult return type must be parameterized as ApiResult or ApiResult" + "Result return type must be parameterized as Result or Result" } return FlowCallAdapter( From dad348bc30a8bd18fb247a608d697e7f6819a798 Mon Sep 17 00:00:00 2001 From: Donghyeon0915 Date: Tue, 30 Apr 2024 17:45:37 +0900 Subject: [PATCH 13/23] =?UTF-8?q?[feat]=20#348=20ServerStatus=20=EB=AC=B8?= =?UTF-8?q?=EC=9E=90=EC=97=B4=20=EB=A6=AC=EC=86=8C=EC=8A=A4=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC=20&=20Error=20=ED=83=80=EC=9E=85=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../runnect/developer/enum/ServerStatus.kt | 20 ++++++++++--------- .../presentation/RunnectDeveloperViewModel.kt | 1 + .../custom/ServerStatusPreference.kt | 6 +++--- app/src/main/res/values/strings.xml | 13 ++++++++++++ 4 files changed, 28 insertions(+), 12 deletions(-) diff --git a/app/src/debug/java/com/runnect/runnect/developer/enum/ServerStatus.kt b/app/src/debug/java/com/runnect/runnect/developer/enum/ServerStatus.kt index b48aaf4ae..f9c547b98 100644 --- a/app/src/debug/java/com/runnect/runnect/developer/enum/ServerStatus.kt +++ b/app/src/debug/java/com/runnect/runnect/developer/enum/ServerStatus.kt @@ -1,26 +1,28 @@ package com.runnect.runnect.developer.enum -import android.content.Context +import androidx.annotation.ColorRes +import androidx.annotation.StringRes import com.runnect.runnect.R import com.runnect.runnect.developer.presentation.RunnectDeveloperViewModel.ServerState enum class ServerStatus( - private val colorRes: Int, - val statusText: String, - val summary: String, + @ColorRes val colorRes: Int, + @StringRes val statusRes: Int, + @StringRes val summaryRes: Int, ) { - CHECKING(R.color.blue, "Checking...", "서버 상태를 확인 중입니다."), - RUNNING(R.color.green, "Running", "서버가 정상적으로 작동하고 있습니다."), - DEGRADED(R.color.orange, "Degraded", "작동하고 있으나 일부 기능에 문제가 있습니다."), - UNKNOWN(R.color.red, "Unknown", "작동 중이지 않거나 오류가 있습니다."); - fun getColor(context: Context): Int = context.getColor(colorRes) + 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 } diff --git a/app/src/debug/java/com/runnect/runnect/developer/presentation/RunnectDeveloperViewModel.kt b/app/src/debug/java/com/runnect/runnect/developer/presentation/RunnectDeveloperViewModel.kt index d62a3f0b3..59748ad28 100644 --- a/app/src/debug/java/com/runnect/runnect/developer/presentation/RunnectDeveloperViewModel.kt +++ b/app/src/debug/java/com/runnect/runnect/developer/presentation/RunnectDeveloperViewModel.kt @@ -67,6 +67,7 @@ class RunnectDeveloperViewModel @Inject constructor( sealed interface ServerState { object Running : ServerState object Degraded : ServerState + object Error : ServerState object Unknown : ServerState object Checking : ServerState } diff --git a/app/src/debug/java/com/runnect/runnect/developer/presentation/custom/ServerStatusPreference.kt b/app/src/debug/java/com/runnect/runnect/developer/presentation/custom/ServerStatusPreference.kt index 99c38c089..0bbfb857b 100644 --- a/app/src/debug/java/com/runnect/runnect/developer/presentation/custom/ServerStatusPreference.kt +++ b/app/src/debug/java/com/runnect/runnect/developer/presentation/custom/ServerStatusPreference.kt @@ -30,9 +30,9 @@ class ServerStatusPreference @JvmOverloads constructor( val background = indicator.background as? GradientDrawable holder.itemView.post { - background?.setColor(serverStatus.getColor(context)) - statusText.text = serverStatus.statusText - summary = serverStatus.summary + background?.setColor(context.getColor(serverStatus.colorRes)) + statusText.text = context.getString(serverStatus.statusRes) + summary = context.getString(serverStatus.summaryRes) } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 17c5b1d08..ee3558337 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -174,4 +174,17 @@ 코스의 출발지 설정과 러닝 트래킹을 위해 현재 위치 정보를 사용하도록 허용합니다. 권한을 허용해주세요. [설정] > [앱 및 알림] > [고급] > [앱 권한] 권한이 거부되었습니다. + + + Checking... + Running + Degraded + Error + Unknown + 서버 상태를 확인 중입니다. + 서버가 정상적으로 작동하고 있습니다. + 작동하고 있으나 일부 기능에 문제가 있습니다. + 작동 중이지 않거나 오류가 있습니다. + 서버 상태를 체크할 수 없습니다. + \ No newline at end of file From 5c712d3e660d8fa535ef302cc0ffb44375db6b5b Mon Sep 17 00:00:00 2001 From: Donghyeon0915 Date: Tue, 30 Apr 2024 17:46:08 +0900 Subject: [PATCH 14/23] =?UTF-8?q?[mod]=20#348=20Flow=20=ED=99=95=EC=9E=A5?= =?UTF-8?q?=20=ED=95=A8=EC=88=98=20nullable=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/runnect/runnect/util/extension/FlowExt.kt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/runnect/runnect/util/extension/FlowExt.kt b/app/src/main/java/com/runnect/runnect/util/extension/FlowExt.kt index b78902590..d231da898 100644 --- a/app/src/main/java/com/runnect/runnect/util/extension/FlowExt.kt +++ b/app/src/main/java/com/runnect/runnect/util/extension/FlowExt.kt @@ -10,20 +10,20 @@ import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch suspend fun Flow>.collectResult( - onSuccess: (T) -> Unit, - onFailure: (Throwable) -> Unit + onSuccess: ((T) -> Unit)? = null, + onFailure: ((Throwable) -> Unit)? = null ) { collect { result -> - result.fold(onSuccess, onFailure) + result.fold(onSuccess ?: {}, onFailure ?: {}) } } fun Flow>.onEachResult( - onSuccess: (T) -> Unit, - onFailure: (Throwable) -> Unit + onSuccess: ((T) -> Unit)? = null, + onFailure: ((Throwable) -> Unit)? = null ): Flow> { return onEach { result -> - result.fold(onSuccess, onFailure) + result.fold(onSuccess ?: {}, onFailure ?: {}) } } From 46e327e0150ca7bf44205c7207d1bee2dc1751d6 Mon Sep 17 00:00:00 2001 From: Donghyeon0915 Date: Tue, 30 Apr 2024 17:47:05 +0900 Subject: [PATCH 15/23] =?UTF-8?q?[mod]=20#348=20Api=20=EC=9A=94=EC=B2=AD?= =?UTF-8?q?=20=EB=94=94=EC=8A=A4=ED=8C=A8=EC=B2=98=20=EC=84=A0=EC=96=B8,?= =?UTF-8?q?=20=EC=BD=94=EB=93=9C=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../remote/RemoteServerStatusDataSource.kt | 1 - .../presentation/RunnectDeveloperViewModel.kt | 18 ++++++++++-------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/app/src/debug/java/com/runnect/runnect/developer/data/source/remote/RemoteServerStatusDataSource.kt b/app/src/debug/java/com/runnect/runnect/developer/data/source/remote/RemoteServerStatusDataSource.kt index a62e06819..9a532ed01 100644 --- a/app/src/debug/java/com/runnect/runnect/developer/data/source/remote/RemoteServerStatusDataSource.kt +++ b/app/src/debug/java/com/runnect/runnect/developer/data/source/remote/RemoteServerStatusDataSource.kt @@ -3,7 +3,6 @@ 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 kotlinx.coroutines.flow.Flow import javax.inject.Inject class RemoteServerStatusDataSource @Inject constructor( diff --git a/app/src/debug/java/com/runnect/runnect/developer/presentation/RunnectDeveloperViewModel.kt b/app/src/debug/java/com/runnect/runnect/developer/presentation/RunnectDeveloperViewModel.kt index 59748ad28..fe4612759 100644 --- a/app/src/debug/java/com/runnect/runnect/developer/presentation/RunnectDeveloperViewModel.kt +++ b/app/src/debug/java/com/runnect/runnect/developer/presentation/RunnectDeveloperViewModel.kt @@ -4,14 +4,15 @@ import com.runnect.runnect.BuildConfig import com.runnect.runnect.developer.domain.ServerStatusRepository import com.runnect.runnect.domain.common.getCode import com.runnect.runnect.presentation.base.BaseViewModel -import com.runnect.runnect.util.extension.onEachResult +import com.runnect.runnect.util.extension.collectResult import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.catch -import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.onStart import javax.inject.Inject @@ -38,7 +39,7 @@ class RunnectDeveloperViewModel @Inject constructor( } fun checkTestServerStatus() { - val testServerUrl = "${BuildConfig.RUNNECT_DEV_URL}/actuator/health" + val testServerUrl = "${BuildConfig.RUNNECT_NODE_URL}/actuator/health" checkServerStatus(testServerUrl, _testStatus) } @@ -47,21 +48,22 @@ class RunnectDeveloperViewModel @Inject constructor( state: MutableSharedFlow ) = launchWithHandler { serverStatusRepository.checkServerStatus(serverUrl) + .flowOn(Dispatchers.IO) .onStart { state.emit(ServerState.Checking) - }.onEachResult( + }.catch { + state.tryEmit(ServerState.Unknown) + }.collectResult( onSuccess = { state.tryEmit(ServerState.Running) }, onFailure = { when (it.getCode()) { 503 -> ServerState.Degraded - else -> ServerState.Unknown + else -> ServerState.Error }.let(state::tryEmit) } - ).catch { - state.tryEmit(ServerState.Unknown) - }.collect() + ) } sealed interface ServerState { From 732a2948974a678ab417ff8265d26b0e98561e25 Mon Sep 17 00:00:00 2001 From: Donghyeon0915 Date: Tue, 30 Apr 2024 17:47:17 +0900 Subject: [PATCH 16/23] =?UTF-8?q?[mod]=20#348=20=EC=84=9C=EB=B2=84=20?= =?UTF-8?q?=EC=83=81=ED=83=9C=20=EB=A0=88=EC=9D=B4=EC=95=84=EC=9B=83=20?= =?UTF-8?q?=ED=8C=A8=EB=94=A9=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/debug/res/layout/pref_server_status_layout.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/debug/res/layout/pref_server_status_layout.xml b/app/src/debug/res/layout/pref_server_status_layout.xml index d047d36b4..e159ad4d0 100644 --- a/app/src/debug/res/layout/pref_server_status_layout.xml +++ b/app/src/debug/res/layout/pref_server_status_layout.xml @@ -3,7 +3,7 @@ xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="wrap_content" - android:padding="16dp"> + android:paddingVertical="16dp"> Date: Tue, 30 Apr 2024 17:47:36 +0900 Subject: [PATCH 17/23] =?UTF-8?q?[mod]=20#348=20Flow=20CallAdapter=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../calladapter/flow/FlowCallAdapter.kt | 52 +++++++------------ 1 file changed, 20 insertions(+), 32 deletions(-) diff --git a/app/src/main/java/com/runnect/runnect/data/network/calladapter/flow/FlowCallAdapter.kt b/app/src/main/java/com/runnect/runnect/data/network/calladapter/flow/FlowCallAdapter.kt index da52cd7f7..8a39931c2 100644 --- a/app/src/main/java/com/runnect/runnect/data/network/calladapter/flow/FlowCallAdapter.kt +++ b/app/src/main/java/com/runnect/runnect/data/network/calladapter/flow/FlowCallAdapter.kt @@ -23,12 +23,15 @@ class FlowCallAdapter( // Retrofit의 Call을 Result<>로 변환 override fun adapt(call: Call): Flow> = flow { - val apiResult = suspendCancellableCoroutine { continuation -> + emit(flowApiCall(call)) + } + + private suspend fun flowApiCall(call: Call): Result { + return suspendCancellableCoroutine { continuation -> runCatching { call.enqueue(object : Callback { override fun onResponse(call: Call, response: Response) { - val result = parseResponse(response) - continuation.resume(result) + continuation.resume(parseResponse(response)) } override fun onFailure(call: Call, t: Throwable) { @@ -39,50 +42,35 @@ class FlowCallAdapter( continuation.resumeWithException(it) } - // Coroutine이 취소 되면 네트워크 요청도 취소 continuation.invokeOnCancellation { call.cancel() } } - - emit(apiResult) } private fun parseResponse(response: Response): Result { - if (response.isSuccessful) { - return response.body()?.let { - Result.success(it) - } ?: Result.failure( - RunnectException( - code = response.code(), - message = ERROR_MSG_RESPONSE_IS_NULL - ) - ) + val nullBodyException by lazy { + RunnectException(response.code(), ERROR_MSG_RESPONSE_IS_NULL) + } + + if (!response.isSuccessful) { + return Result.failure(parseErrorResponse(response)) } - return Result.failure(parseErrorResponse(response)) + return response.body()?.let { + Result.success(it) + } ?: Result.failure(nullBodyException) } // Response에서 오류를 파싱하여 RunnectException 객체를 생성 private fun parseErrorResponse(response: Response<*>): RunnectException { - val code = response.code() val errorBodyString = response.errorBody()?.string() - - return errorBodyString?.let { errorBody -> - parseBaseErrorResponse(code, errorBody) - } ?: RunnectException(code, ERROR_MSG_COMMON) - } - - // 일반적인 에러 응답을 파싱하여 RunnectException 생성 - private fun parseBaseErrorResponse(code: Int, errorBody: String): RunnectException? { - return runCatching { - gson.fromJson(errorBody, ErrorResponse::class.java) - }.getOrNull()?.let { errorResponse -> - RunnectException( - code = code, - message = errorResponse.message ?: errorResponse.error ?: ERROR_MSG_COMMON - ) + val errorResponse = errorBodyString?.let { + gson.fromJson(it, ErrorResponse::class.java) } + + val errorMessage = errorResponse?.message ?: errorResponse?.error ?: ERROR_MSG_COMMON + return RunnectException(response.code(), errorMessage) } companion object { From 910bf3d771c9df5070fe6d5692c08e0b9b39e97f Mon Sep 17 00:00:00 2001 From: Donghyeon0915 Date: Tue, 30 Apr 2024 18:08:49 +0900 Subject: [PATCH 18/23] =?UTF-8?q?[mod]=20#348=20=EA=B0=9C=EB=B0=9C?= =?UTF-8?q?=EC=9E=90=EB=AA=A8=EB=93=9C=EC=97=90=EC=84=9C=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=EB=90=98=EB=8A=94=20=EB=AC=B8=EC=9E=90=EC=97=B4=20?= =?UTF-8?q?=EB=A6=AC=EC=86=8C=EC=8A=A4=20debug=20=ED=8C=A8=ED=82=A4?= =?UTF-8?q?=EC=A7=80=EB=A1=9C=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/debug/res/values/strings.xml | 14 ++++++++++++++ app/src/main/res/values/strings.xml | 12 ------------ 2 files changed, 14 insertions(+), 12 deletions(-) create mode 100644 app/src/debug/res/values/strings.xml diff --git a/app/src/debug/res/values/strings.xml b/app/src/debug/res/values/strings.xml new file mode 100644 index 000000000..9fdc0e2a2 --- /dev/null +++ b/app/src/debug/res/values/strings.xml @@ -0,0 +1,14 @@ + + + + Checking... + Running + Degraded + Error + Unknown + 서버 상태를 확인 중입니다. + 서버가 정상적으로 작동하고 있습니다. + 작동하고 있으나 일부 기능에 문제가 있습니다. + 작동 중이지 않거나 오류가 있습니다. + 서버 상태를 체크할 수 없습니다. + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ee3558337..492723c04 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -175,16 +175,4 @@ 권한을 허용해주세요. [설정] > [앱 및 알림] > [고급] > [앱 권한] 권한이 거부되었습니다. - - Checking... - Running - Degraded - Error - Unknown - 서버 상태를 확인 중입니다. - 서버가 정상적으로 작동하고 있습니다. - 작동하고 있으나 일부 기능에 문제가 있습니다. - 작동 중이지 않거나 오류가 있습니다. - 서버 상태를 체크할 수 없습니다. - \ No newline at end of file From 2aed46b8364b68a78d4524f8cd53b2f1c13e9be6 Mon Sep 17 00:00:00 2001 From: Donghyeon0915 Date: Tue, 30 Apr 2024 22:07:46 +0900 Subject: [PATCH 19/23] =?UTF-8?q?[refactor]=20#348=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../runnect/developer/presentation/RunnectDeveloperViewModel.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/debug/java/com/runnect/runnect/developer/presentation/RunnectDeveloperViewModel.kt b/app/src/debug/java/com/runnect/runnect/developer/presentation/RunnectDeveloperViewModel.kt index fe4612759..cdac7709a 100644 --- a/app/src/debug/java/com/runnect/runnect/developer/presentation/RunnectDeveloperViewModel.kt +++ b/app/src/debug/java/com/runnect/runnect/developer/presentation/RunnectDeveloperViewModel.kt @@ -39,7 +39,7 @@ class RunnectDeveloperViewModel @Inject constructor( } fun checkTestServerStatus() { - val testServerUrl = "${BuildConfig.RUNNECT_NODE_URL}/actuator/health" + val testServerUrl = "${BuildConfig.RUNNECT_DEV_URL}/actuator/health" checkServerStatus(testServerUrl, _testStatus) } From 131c747a47e1633b7419cdb97ccf28c5fcbfe0a8 Mon Sep 17 00:00:00 2001 From: Donghyeon0915 Date: Wed, 1 May 2024 15:10:14 +0900 Subject: [PATCH 20/23] =?UTF-8?q?[feat]=20#348=20=ED=86=A0=ED=81=B0=20?= =?UTF-8?q?=EA=B3=B5=EC=9C=A0=20=EC=8B=9C=20=EC=84=9C=EB=B2=84=20=EB=AA=85?= =?UTF-8?q?=20=ED=91=9C=EA=B8=B0=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../runnect/developer/presentation/RunnectDeveloperActivity.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/debug/java/com/runnect/runnect/developer/presentation/RunnectDeveloperActivity.kt b/app/src/debug/java/com/runnect/runnect/developer/presentation/RunnectDeveloperActivity.kt index 0d022f3de..f27b46971 100644 --- a/app/src/debug/java/com/runnect/runnect/developer/presentation/RunnectDeveloperActivity.kt +++ b/app/src/debug/java/com/runnect/runnect/developer/presentation/RunnectDeveloperActivity.kt @@ -89,7 +89,7 @@ class RunnectDeveloperActivity : AppCompatActivity(R.layout.activity_runnect_dev val ctx: Context = context ?: return val accessToken = ctx.getAccessToken() val refreshToken = ctx.getNewToken() - val combinedToken = "[Access Token]: $accessToken\n\n---\n\n[Refresh Token]: $refreshToken" + 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) From 7159064a5ea14e55a7eb8db9c0434a1bd1f01490 Mon Sep 17 00:00:00 2001 From: Donghyeon0915 Date: Sat, 11 May 2024 15:04:34 +0900 Subject: [PATCH 21/23] =?UTF-8?q?[mod]=20named=20argument=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit *PR 대응 --- .../runnect/developer/presentation/RunnectDeveloperActivity.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/debug/java/com/runnect/runnect/developer/presentation/RunnectDeveloperActivity.kt b/app/src/debug/java/com/runnect/runnect/developer/presentation/RunnectDeveloperActivity.kt index f27b46971..116e79db4 100644 --- a/app/src/debug/java/com/runnect/runnect/developer/presentation/RunnectDeveloperActivity.kt +++ b/app/src/debug/java/com/runnect/runnect/developer/presentation/RunnectDeveloperActivity.kt @@ -49,7 +49,7 @@ class RunnectDeveloperActivity : AppCompatActivity(R.layout.activity_runnect_dev override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { setPreferencesFromResource(R.xml.preferences_developer_menu, rootKey) activity?.apply { - setStatusBarColor(window = window, true, R.color.white) + setStatusBarColor(window = window, isLightColor = true, colorResource = R.color.white) } initUserInfo() From fb47c47a10521434b4329d839ddb636148186357 Mon Sep 17 00:00:00 2001 From: Donghyeon0915 Date: Sat, 11 May 2024 15:11:43 +0900 Subject: [PATCH 22/23] =?UTF-8?q?[mod]=20DataSource=20=ED=81=B4=EB=9E=98?= =?UTF-8?q?=EC=8A=A4=EB=AA=85=20remote=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit *PR 대응 --- .../developer/data/repository/ServerStatusRepositoryImpl.kt | 4 ++-- ...oteServerStatusDataSource.kt => ServerStatusDataSource.kt} | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) rename app/src/debug/java/com/runnect/runnect/developer/data/source/remote/{RemoteServerStatusDataSource.kt => ServerStatusDataSource.kt} (89%) diff --git a/app/src/debug/java/com/runnect/runnect/developer/data/repository/ServerStatusRepositoryImpl.kt b/app/src/debug/java/com/runnect/runnect/developer/data/repository/ServerStatusRepositoryImpl.kt index fe5a81682..244918822 100644 --- a/app/src/debug/java/com/runnect/runnect/developer/data/repository/ServerStatusRepositoryImpl.kt +++ b/app/src/debug/java/com/runnect/runnect/developer/data/repository/ServerStatusRepositoryImpl.kt @@ -2,12 +2,12 @@ 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.data.source.remote.ServerStatusDataSource import com.runnect.runnect.developer.domain.ServerStatusRepository import javax.inject.Inject class ServerStatusRepositoryImpl @Inject constructor( - private val serverStatusDataSource: RemoteServerStatusDataSource + private val serverStatusDataSource: ServerStatusDataSource ) : ServerStatusRepository { override suspend fun checkServerStatus(serverUrl: String): FlowResult { diff --git a/app/src/debug/java/com/runnect/runnect/developer/data/source/remote/RemoteServerStatusDataSource.kt b/app/src/debug/java/com/runnect/runnect/developer/data/source/remote/ServerStatusDataSource.kt similarity index 89% rename from app/src/debug/java/com/runnect/runnect/developer/data/source/remote/RemoteServerStatusDataSource.kt rename to app/src/debug/java/com/runnect/runnect/developer/data/source/remote/ServerStatusDataSource.kt index 9a532ed01..afa6f3cf4 100644 --- a/app/src/debug/java/com/runnect/runnect/developer/data/source/remote/RemoteServerStatusDataSource.kt +++ b/app/src/debug/java/com/runnect/runnect/developer/data/source/remote/ServerStatusDataSource.kt @@ -5,7 +5,7 @@ import com.runnect.runnect.developer.data.dto.ResponseServerStatus import com.runnect.runnect.developer.data.service.ServerStatusService import javax.inject.Inject -class RemoteServerStatusDataSource @Inject constructor( +class ServerStatusDataSource @Inject constructor( private val serverStatusService: ServerStatusService, ) { From 8d2d3b382b3b4a85da50a733b44432a1f0904ddd Mon Sep 17 00:00:00 2001 From: Donghyeon0915 Date: Sun, 12 May 2024 23:23:29 +0900 Subject: [PATCH 23/23] =?UTF-8?q?[mod]=20CallAdapter=20runCatching=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit *PR 대응 --- .../calladapter/flow/FlowCallAdapter.kt | 20 ++++++++----------- 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/com/runnect/runnect/data/network/calladapter/flow/FlowCallAdapter.kt b/app/src/main/java/com/runnect/runnect/data/network/calladapter/flow/FlowCallAdapter.kt index 8a39931c2..4d473b167 100644 --- a/app/src/main/java/com/runnect/runnect/data/network/calladapter/flow/FlowCallAdapter.kt +++ b/app/src/main/java/com/runnect/runnect/data/network/calladapter/flow/FlowCallAdapter.kt @@ -28,19 +28,15 @@ class FlowCallAdapter( private suspend fun flowApiCall(call: Call): Result { return suspendCancellableCoroutine { continuation -> - runCatching { - call.enqueue(object : Callback { - override fun onResponse(call: Call, response: Response) { - continuation.resume(parseResponse(response)) - } + call.enqueue(object : Callback { + override fun onResponse(call: Call, response: Response) { + continuation.resume(parseResponse(response)) + } - override fun onFailure(call: Call, t: Throwable) { - continuation.resumeWithException(t) - } - }) - }.onFailure { - continuation.resumeWithException(it) - } + override fun onFailure(call: Call, t: Throwable) { + continuation.resumeWithException(t) + } + }) continuation.invokeOnCancellation { call.cancel()