From 903e011abff4c285307466692da2cb2644bfd2ce Mon Sep 17 00:00:00 2001 From: Roman Choryev Date: Fri, 30 Sep 2022 18:21:12 +0300 Subject: [PATCH 01/36] Dependencies: Add Jetpack Compose support --- common/build.gradle.kts | 28 +++++++++++++++++---------- core/build.gradle.kts | 5 +++++ plugins/accounts/build.gradle.kts | 5 +++++ plugins/app-settings/build.gradle.kts | 5 +++++ plugins/flipper/build.gradle.kts | 5 +++++ plugins/servers/build.gradle.kts | 5 +++++ plugins/variable/build.gradle.kts | 5 +++++ sample/build.gradle.kts | 6 ++++++ 8 files changed, 54 insertions(+), 10 deletions(-) diff --git a/common/build.gradle.kts b/common/build.gradle.kts index bfcbfaf2..98b57c6e 100644 --- a/common/build.gradle.kts +++ b/common/build.gradle.kts @@ -44,23 +44,31 @@ android { } dependencies { - api(kotlin("stdlib")) + api(androidx.compose.animation) + api(stack.material.compose.themeAdapter) + api(androidx.compose.material) + api(androidx.compose.foundation) + api(androidx.compose.ui) + api(androidx.constraintlayout.compose) + api(androidx.activity.compose) + api(androidx.compose.ui.tooling.preview) + api(androidx.room.runtime) + api(androidx.room) + api(androidx.core) api(stack.okhttp) + api(stack.kotlinx.coroutines.android) + api(stack.timber) + api(rmr.itemsadapter.viewbinding) + api(rmr.flipper) + kapt(androidx.room.compiler) + // legacy api(androidx.appcompat) api(stack.material) api(androidx.constraintlayout) - api(stack.kotlinx.coroutines.android) + api(androidx.lifecycle.viewmodel) api(androidx.lifecycle.runtime) api(androidx.lifecycle.livedata) api(androidx.lifecycle.livedata.core) - api(androidx.lifecycle.viewmodel) - api(androidx.room.runtime) - api(androidx.room) - api(androidx.core) - api(rmr.itemsadapter.viewbinding) - api(rmr.flipper) - api(stack.timber) - kapt(androidx.room.compiler) } tasks.register("prepareKotlinBuildScriptModel") {} diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 89a50fc6..8d8d51e7 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -39,6 +39,11 @@ android { buildFeatures { viewBinding = true + compose = true + } + + composeOptions { + kotlinCompilerExtensionVersion = androidx.versions.compose.compiler.get() } namespace = "com.redmadrobot.debug.core" } diff --git a/plugins/accounts/build.gradle.kts b/plugins/accounts/build.gradle.kts index 79d1457d..77b33b95 100644 --- a/plugins/accounts/build.gradle.kts +++ b/plugins/accounts/build.gradle.kts @@ -44,6 +44,11 @@ android { buildFeatures { viewBinding = true + compose = true + } + + composeOptions { + kotlinCompilerExtensionVersion = androidx.versions.compose.compiler.get() } namespace = "com.redmadrobot.debug.plugin.accounts" } diff --git a/plugins/app-settings/build.gradle.kts b/plugins/app-settings/build.gradle.kts index 209530a4..31d3ed19 100644 --- a/plugins/app-settings/build.gradle.kts +++ b/plugins/app-settings/build.gradle.kts @@ -38,6 +38,11 @@ android { buildFeatures { viewBinding = true + compose = true + } + + composeOptions { + kotlinCompilerExtensionVersion = androidx.versions.compose.compiler.get() } namespace = "com.redmadrobot.debug.plugin.appsettings" } diff --git a/plugins/flipper/build.gradle.kts b/plugins/flipper/build.gradle.kts index 35ff6cad..97705035 100644 --- a/plugins/flipper/build.gradle.kts +++ b/plugins/flipper/build.gradle.kts @@ -39,6 +39,11 @@ android { buildFeatures { viewBinding = true + compose = true + } + + composeOptions { + kotlinCompilerExtensionVersion = androidx.versions.compose.compiler.get() } namespace = "com.redmadrobot.debug.plugin.flipper" } diff --git a/plugins/servers/build.gradle.kts b/plugins/servers/build.gradle.kts index 4bc6bb47..5574ed74 100644 --- a/plugins/servers/build.gradle.kts +++ b/plugins/servers/build.gradle.kts @@ -38,6 +38,11 @@ android { buildFeatures { viewBinding = true + compose = true + } + + composeOptions { + kotlinCompilerExtensionVersion = androidx.versions.compose.compiler.get() } namespace = "com.redmadrobot.debug.plugin.servers" } diff --git a/plugins/variable/build.gradle.kts b/plugins/variable/build.gradle.kts index 424f6d4f..c389d8b6 100644 --- a/plugins/variable/build.gradle.kts +++ b/plugins/variable/build.gradle.kts @@ -39,6 +39,11 @@ android { buildFeatures { viewBinding = true + compose = true + } + + composeOptions { + kotlinCompilerExtensionVersion = androidx.versions.compose.compiler.get() } namespace = "com.redmadrobot.debug.plugin.variable" } diff --git a/sample/build.gradle.kts b/sample/build.gradle.kts index d2d48124..593ba518 100644 --- a/sample/build.gradle.kts +++ b/sample/build.gradle.kts @@ -23,6 +23,11 @@ android { buildFeatures { viewBinding = true + compose = true + } + + composeOptions { + kotlinCompilerExtensionVersion = androidx.versions.compose.compiler.get() } buildTypes { getByName("release") { @@ -34,6 +39,7 @@ android { dependencies { implementation(kotlin("stdlib")) implementation(androidx.appcompat) + implementation(androidx.compose.runtime) implementation(stack.material) implementation(androidx.constraintlayout) implementation(rmr.flipper) From 2ae9fe1e8e0defc81fad44fe40f4386a6b45c082 Mon Sep 17 00:00:00 2001 From: "a.emogurov" Date: Thu, 18 Jul 2024 11:48:03 +0400 Subject: [PATCH 02/36] Dependencies: fix themeadapter dependency path --- common/build.gradle.kts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/common/build.gradle.kts b/common/build.gradle.kts index 98b57c6e..51e97d85 100644 --- a/common/build.gradle.kts +++ b/common/build.gradle.kts @@ -45,7 +45,9 @@ android { dependencies { api(androidx.compose.animation) - api(stack.material.compose.themeAdapter) + api(androidx.compose.material) + api(stack.accompanist.themeadapter.core) + api(stack.accompanist.themeadapter.material) api(androidx.compose.material) api(androidx.compose.foundation) api(androidx.compose.ui) From 79a9e32e89a3b75921e64cf871d65ce83bb4e848 Mon Sep 17 00:00:00 2001 From: Roman Choryev Date: Tue, 6 Dec 2022 13:41:06 +0300 Subject: [PATCH 03/36] Base:Fix Broadcast receiver action name --- .../redmadrobot/debug/core/util/DebugPanelBroadcastReceiver.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/core/src/main/kotlin/com/redmadrobot/debug/core/util/DebugPanelBroadcastReceiver.kt b/core/src/main/kotlin/com/redmadrobot/debug/core/util/DebugPanelBroadcastReceiver.kt index 5bb5c22b..e1cd0a29 100644 --- a/core/src/main/kotlin/com/redmadrobot/debug/core/util/DebugPanelBroadcastReceiver.kt +++ b/core/src/main/kotlin/com/redmadrobot/debug/core/util/DebugPanelBroadcastReceiver.kt @@ -11,8 +11,7 @@ internal class DebugPanelBroadcastReceiver( ) : BroadcastReceiver() { companion object { - const val ACTION_OPEN_DEBUG_PANEL = - "com.redmadrobot.debug_panel_core.ACTION_OPEN_DEBUG_PANEL" + const val ACTION_OPEN_DEBUG_PANEL = "com.redmadrobot.debug.core.ACTION_OPEN_DEBUG_PANEL" } override fun onReceive(context: Context, intent: Intent) { From 9dba5b51312a995780726f0eff10dc428a805452 Mon Sep 17 00:00:00 2001 From: Roman Choryev Date: Thu, 8 Dec 2022 16:44:11 +0300 Subject: [PATCH 04/36] Deps:Update `common` module dependencies --- common/build.gradle.kts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/common/build.gradle.kts b/common/build.gradle.kts index 51e97d85..122a98ff 100644 --- a/common/build.gradle.kts +++ b/common/build.gradle.kts @@ -51,6 +51,8 @@ dependencies { api(androidx.compose.material) api(androidx.compose.foundation) api(androidx.compose.ui) + api(androidx.compose.ui.viewbinding) + api(androidx.fragment) api(androidx.constraintlayout.compose) api(androidx.activity.compose) api(androidx.compose.ui.tooling.preview) From 4176edc5288a0418a178da783dc331da6d3baeb3 Mon Sep 17 00:00:00 2001 From: Roman Choryev Date: Thu, 8 Dec 2022 16:42:20 +0300 Subject: [PATCH 05/36] Core:Add 'content' and `settingsContent` composable functions for Plugin class --- .../redmadrobot/debug/core/plugin/Plugin.kt | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/core/src/main/kotlin/com/redmadrobot/debug/core/plugin/Plugin.kt b/core/src/main/kotlin/com/redmadrobot/debug/core/plugin/Plugin.kt index 7949ea2f..d5cbe79c 100644 --- a/core/src/main/kotlin/com/redmadrobot/debug/core/plugin/Plugin.kt +++ b/core/src/main/kotlin/com/redmadrobot/debug/core/plugin/Plugin.kt @@ -1,5 +1,7 @@ package com.redmadrobot.debug.core.plugin +import androidx.compose.runtime.Composable +import androidx.compose.ui.viewinterop.AndroidViewBinding import androidx.fragment.app.Fragment import com.redmadrobot.debug.core.DebugEvent import com.redmadrobot.debug.core.DebugPanelInstance @@ -21,10 +23,26 @@ public abstract class Plugin { public fun getContainer(): T = pluginContainer as T + @Deprecated( + "You should't use fragments for you plugins. Please use Jetpack Compose", + ReplaceWith("content()", "com.redmadrobot.debug.core.plugin.Plugin") + ) public open fun getFragment(): Fragment? = null + @Deprecated( + "You should't use fragments for you plugins. Please use Jetpack Compose", + ReplaceWith("content()", "com.redmadrobot.debug.core.plugin.Plugin") + ) public open fun getSettingFragment(): Fragment? = null + @Composable + public open fun content() { + } + + @Composable + public open fun settingsContent() { + } + public abstract fun getPluginContainer(commonContainer: CommonContainer): PluginDependencyContainer public abstract fun getName(): String From e8a3d9ca1bd95b5ce9848df714bfaf526d149b1d Mon Sep 17 00:00:00 2001 From: rchoriev Date: Sun, 24 Sep 2023 21:32:58 +0400 Subject: [PATCH 06/36] Base: Migrate to view binding from 'synthetics' --- .../debug/core/inapp/DebugBottomSheet.kt | 11 ++++++----- .../com/redmadrobot/debug/core/plugin/Plugin.kt | 4 ++-- .../com/redmadrobot/debug_sample/MainActivity.kt | 14 +++++++++++--- 3 files changed, 19 insertions(+), 10 deletions(-) diff --git a/core/src/main/kotlin/com/redmadrobot/debug/core/inapp/DebugBottomSheet.kt b/core/src/main/kotlin/com/redmadrobot/debug/core/inapp/DebugBottomSheet.kt index 8978ce42..0fc03ce3 100644 --- a/core/src/main/kotlin/com/redmadrobot/debug/core/inapp/DebugBottomSheet.kt +++ b/core/src/main/kotlin/com/redmadrobot/debug/core/inapp/DebugBottomSheet.kt @@ -40,7 +40,7 @@ internal class DebugBottomSheet : BottomSheetDialogFragment() { setBottomSheetSize() } dialog.setContentView(binding.root) - setViews(binding) + binding.setViews() return dialog } @@ -49,12 +49,12 @@ internal class DebugBottomSheet : BottomSheetDialogFragment() { _binding = null } - private fun setViews(binding: BottomSheetDebugPanelBinding) { + private fun BottomSheetDebugPanelBinding.setViews() { val plugins = getAllPlugins() /*Only Plugins with Fragment*/ .filter { it.getFragment() != null } - binding.debugSheetViewpager.adapter = DebugSheetViewPagerAdapter( + debugSheetViewpager.adapter = DebugSheetViewPagerAdapter( requireActivity(), plugins ) @@ -64,13 +64,14 @@ internal class DebugBottomSheet : BottomSheetDialogFragment() { } TabLayoutMediator( - binding.debugSheetTabLayout, - binding.debugSheetViewpager, + debugSheetTabLayout, + debugSheetViewpager, tabConfigurationStrategy ).attach() } private fun setBottomSheetSize() { + if (_binding == null) return val dialogContainer = binding.root.parent as? FrameLayout dialogContainer?.apply { layoutParams?.height = ViewGroup.LayoutParams.MATCH_PARENT diff --git a/core/src/main/kotlin/com/redmadrobot/debug/core/plugin/Plugin.kt b/core/src/main/kotlin/com/redmadrobot/debug/core/plugin/Plugin.kt index d5cbe79c..4b41476a 100644 --- a/core/src/main/kotlin/com/redmadrobot/debug/core/plugin/Plugin.kt +++ b/core/src/main/kotlin/com/redmadrobot/debug/core/plugin/Plugin.kt @@ -24,13 +24,13 @@ public abstract class Plugin { public fun getContainer(): T = pluginContainer as T @Deprecated( - "You should't use fragments for you plugins. Please use Jetpack Compose", + "You shouldn't use fragments for you plugins. Please use Jetpack Compose", ReplaceWith("content()", "com.redmadrobot.debug.core.plugin.Plugin") ) public open fun getFragment(): Fragment? = null @Deprecated( - "You should't use fragments for you plugins. Please use Jetpack Compose", + "You shouldn't use fragments for you plugins. Please use Jetpack Compose", ReplaceWith("content()", "com.redmadrobot.debug.core.plugin.Plugin") ) public open fun getSettingFragment(): Fragment? = null diff --git a/sample/src/main/kotlin/com/redmadrobot/debug_sample/MainActivity.kt b/sample/src/main/kotlin/com/redmadrobot/debug_sample/MainActivity.kt index 1df4ca24..40523371 100644 --- a/sample/src/main/kotlin/com/redmadrobot/debug_sample/MainActivity.kt +++ b/sample/src/main/kotlin/com/redmadrobot/debug_sample/MainActivity.kt @@ -53,10 +53,10 @@ class MainActivity : AppCompatActivity() { } DebugPanel.observeEvents() - ?.onEach { event -> + .onEach { event -> when (event) { is AccountSelectedEvent -> { - //Обработка выбора аккаунта + showSelectedAccount(event.debugAccount.login) } is ServerSelectedEvent -> { @@ -64,7 +64,7 @@ class MainActivity : AppCompatActivity() { } } } - ?.launchIn(lifecycleScope) + .launchIn(lifecycleScope) } private fun ActivityMainBinding.setViews() { @@ -146,6 +146,14 @@ class MainActivity : AppCompatActivity() { DebugPanel.showPanel(supportFragmentManager) } + private fun showSelectedAccount(account: String) { + Toast.makeText( + this, + "Account $account selected", + Toast.LENGTH_LONG + ).show() + } + @OptIn(DelicateCoroutinesApi::class) private fun observeFeatureToggles() { FlipperPlugin.addSource( From e27f4eb4dfb4d4bc7abcb5f578680590abf05983 Mon Sep 17 00:00:00 2001 From: Roman Choryev Date: Thu, 8 Dec 2022 20:11:35 +0400 Subject: [PATCH 07/36] AppSettings: Add the ability to open `appsettings` plugin from JetpackCompose --- .../plugin/appsettings/AppSettingsPlugin.kt | 19 ++++++++++++++++++- .../ui/ApplicationSettingsFragment.kt | 8 ++------ .../fragment_container_app_settings.xml | 6 ++++++ 3 files changed, 26 insertions(+), 7 deletions(-) create mode 100644 plugins/app-settings/src/main/res/layout/fragment_container_app_settings.xml diff --git a/plugins/app-settings/src/main/kotlin/com/redmadrobot/debug/plugin/appsettings/AppSettingsPlugin.kt b/plugins/app-settings/src/main/kotlin/com/redmadrobot/debug/plugin/appsettings/AppSettingsPlugin.kt index 184b7398..87d86dbf 100644 --- a/plugins/app-settings/src/main/kotlin/com/redmadrobot/debug/plugin/appsettings/AppSettingsPlugin.kt +++ b/plugins/app-settings/src/main/kotlin/com/redmadrobot/debug/plugin/appsettings/AppSettingsPlugin.kt @@ -1,6 +1,11 @@ package com.redmadrobot.debug.plugin.appsettings import android.content.SharedPreferences +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.viewinterop.AndroidViewBinding import androidx.fragment.app.Fragment import com.redmadrobot.debug.plugin.appsettings.data.DefaultSharedPreferences import com.redmadrobot.debug.plugin.appsettings.ui.ApplicationSettingsFragment @@ -22,7 +27,19 @@ public class AppSettingsPlugin( return AppSettingsPluginContainer(sharedPreferences) } - override fun getFragment(): Fragment? { + @Deprecated( + "You should't use fragments for you plugins. Please use Jetpack Compose", + replaceWith = ReplaceWith("content()", "com.redmadrobot.debug.core.plugin.Plugin") + ) + override fun getFragment(): Fragment { return ApplicationSettingsFragment() } + + @Composable + override fun content() { + AndroidViewBinding( + FragmentContainerAppSettingsBinding::inflate, + modifier = Modifier.verticalScroll(rememberScrollState()) + ) + } } diff --git a/plugins/app-settings/src/main/kotlin/com/redmadrobot/debug/plugin/appsettings/ui/ApplicationSettingsFragment.kt b/plugins/app-settings/src/main/kotlin/com/redmadrobot/debug/plugin/appsettings/ui/ApplicationSettingsFragment.kt index d466c04d..4a420118 100644 --- a/plugins/app-settings/src/main/kotlin/com/redmadrobot/debug/plugin/appsettings/ui/ApplicationSettingsFragment.kt +++ b/plugins/app-settings/src/main/kotlin/com/redmadrobot/debug/plugin/appsettings/ui/ApplicationSettingsFragment.kt @@ -26,17 +26,13 @@ internal class ApplicationSettingsFragment : PluginFragment(R.layout.fragment_ap } } - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) - observe(settingsViewModel.settingsLiveData, ::setSettingList) - settingsViewModel.loadSettings() - } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + observe(settingsViewModel.settingsLiveData, ::setSettingList) binding = FragmentAppSettingsBinding.bind(view).also { it.setViews() } + settingsViewModel.loadSettings() } private fun FragmentAppSettingsBinding.setViews() { diff --git a/plugins/app-settings/src/main/res/layout/fragment_container_app_settings.xml b/plugins/app-settings/src/main/res/layout/fragment_container_app_settings.xml new file mode 100644 index 00000000..2695a975 --- /dev/null +++ b/plugins/app-settings/src/main/res/layout/fragment_container_app_settings.xml @@ -0,0 +1,6 @@ + + From 73acd389ea5259bc148908d765645e7cbdab1e63 Mon Sep 17 00:00:00 2001 From: Roman Choryev Date: Thu, 8 Dec 2022 19:46:44 +0400 Subject: [PATCH 08/36] Flipper:Add the ability to open `flipper` plugin from JetpackCompose --- .../debug/plugin/flipper/FlipperPlugin.kt | 13 ++++++++++++- .../flipper/ui/FlipperFeaturesFragment.kt | 15 +++++++++++++++ .../fragment_container_flipper_plugin.xml | 7 +++++++ .../res/layout/fragment_flipper_features.xml | 17 ++++++++++------- 4 files changed, 44 insertions(+), 8 deletions(-) create mode 100644 plugins/flipper/src/main/res/layout/fragment_container_flipper_plugin.xml diff --git a/plugins/flipper/src/main/kotlin/com/redmadrobot/debug/plugin/flipper/FlipperPlugin.kt b/plugins/flipper/src/main/kotlin/com/redmadrobot/debug/plugin/flipper/FlipperPlugin.kt index b0a86c63..3d88812a 100644 --- a/plugins/flipper/src/main/kotlin/com/redmadrobot/debug/plugin/flipper/FlipperPlugin.kt +++ b/plugins/flipper/src/main/kotlin/com/redmadrobot/debug/plugin/flipper/FlipperPlugin.kt @@ -1,5 +1,7 @@ package com.redmadrobot.debug.plugin.flipper +import androidx.compose.runtime.Composable +import androidx.compose.ui.viewinterop.AndroidViewBinding import androidx.fragment.app.Fragment import com.redmadrobot.debug.core.internal.CommonContainer import com.redmadrobot.debug.core.extension.getPlugin @@ -8,7 +10,7 @@ import com.redmadrobot.debug.core.internal.PluginDependencyContainer import com.redmadrobot.flipper.config.FlipperValue import com.redmadrobot.debug.plugin.flipper.ui.FlipperFeaturesFragment import kotlinx.coroutines.flow.Flow -import java.util.* +import java.util.Collections public class FlipperPlugin( private val toggles: List, @@ -53,9 +55,18 @@ public class FlipperPlugin( ) } + @Deprecated( + "You should't use fragments for you plugins. Please use Jetpack Compose", + replaceWith = ReplaceWith("content()", "com.redmadrobot.debug.core.plugin.Plugin") + ) override fun getFragment(): Fragment { return FlipperFeaturesFragment() } + + @Composable + override fun content() { + AndroidViewBinding(FragmentContainerFlipperPluginBinding::inflate) + } } public data class PluginToggle( diff --git a/plugins/flipper/src/main/kotlin/com/redmadrobot/debug/plugin/flipper/ui/FlipperFeaturesFragment.kt b/plugins/flipper/src/main/kotlin/com/redmadrobot/debug/plugin/flipper/ui/FlipperFeaturesFragment.kt index 2f4d0e51..a78f54fb 100644 --- a/plugins/flipper/src/main/kotlin/com/redmadrobot/debug/plugin/flipper/ui/FlipperFeaturesFragment.kt +++ b/plugins/flipper/src/main/kotlin/com/redmadrobot/debug/plugin/flipper/ui/FlipperFeaturesFragment.kt @@ -1,11 +1,13 @@ package com.redmadrobot.debug.plugin.flipper.ui import android.os.Bundle +import android.view.MotionEvent import android.view.View import androidx.core.widget.addTextChangedListener import androidx.lifecycle.Lifecycle import androidx.lifecycle.flowWithLifecycle import androidx.lifecycle.lifecycleScope +import androidx.recyclerview.widget.RecyclerView import com.redmadrobot.debug.common.base.PluginFragment import com.redmadrobot.debug.common.extension.obtainShareViewModel import com.redmadrobot.debug.core.extension.getPlugin @@ -59,6 +61,19 @@ internal class FlipperFeaturesFragment : PluginFragment(R.layout.fragment_flippe featuresAdapter.submitList(state.items) } .launchIn(viewLifecycleOwner.lifecycleScope) + + // Workaround for nested scroll in compose + binding.featureList.addOnItemTouchListener(object : RecyclerView.OnItemTouchListener { + override fun onInterceptTouchEvent(rv: RecyclerView, event: MotionEvent): Boolean { + when (event.action) { + MotionEvent.ACTION_MOVE -> rv.parent.requestDisallowInterceptTouchEvent(true) + } + return false + } + + override fun onTouchEvent(rv: RecyclerView, e: MotionEvent) = Unit + override fun onRequestDisallowInterceptTouchEvent(disallowIntercept: Boolean) = Unit + }) } override fun onDestroyView() { diff --git a/plugins/flipper/src/main/res/layout/fragment_container_flipper_plugin.xml b/plugins/flipper/src/main/res/layout/fragment_container_flipper_plugin.xml new file mode 100644 index 00000000..6cae9cce --- /dev/null +++ b/plugins/flipper/src/main/res/layout/fragment_container_flipper_plugin.xml @@ -0,0 +1,7 @@ + + + diff --git a/plugins/flipper/src/main/res/layout/fragment_flipper_features.xml b/plugins/flipper/src/main/res/layout/fragment_flipper_features.xml index 10878557..0bdc8044 100644 --- a/plugins/flipper/src/main/res/layout/fragment_flipper_features.xml +++ b/plugins/flipper/src/main/res/layout/fragment_flipper_features.xml @@ -1,13 +1,14 @@ - + android:background="@color/white"> @@ -34,17 +35,19 @@ - + From 6154a48bf714cf2d757d4961afbe2c35296698e2 Mon Sep 17 00:00:00 2001 From: Roman Choryev Date: Thu, 8 Dec 2022 19:37:28 +0400 Subject: [PATCH 09/36] Servers: Add the ability to open `servers` plugin from JetpackCompose --- .../debug/common/base/PluginFragment.kt | 4 ++ .../debug/plugin/servers/ServersPlugin.kt | 41 +++++++++++++++---- .../plugin/servers/ui/ServersFragment.kt | 38 +++++++---------- .../res/layout/fragment_container_servers.xml | 7 ++++ 4 files changed, 58 insertions(+), 32 deletions(-) create mode 100644 plugins/servers/src/main/res/layout/fragment_container_servers.xml diff --git a/common/src/main/kotlin/com/redmadrobot/debug/common/base/PluginFragment.kt b/common/src/main/kotlin/com/redmadrobot/debug/common/base/PluginFragment.kt index a9a046cc..7c3278a2 100644 --- a/common/src/main/kotlin/com/redmadrobot/debug/common/base/PluginFragment.kt +++ b/common/src/main/kotlin/com/redmadrobot/debug/common/base/PluginFragment.kt @@ -10,6 +10,10 @@ import com.redmadrobot.debug.panel.common.R public open class PluginFragment(private val layoutId: Int) : Fragment() { + protected val isSettingMode: Boolean by lazy { + activity?.javaClass?.simpleName == "DebugActivity" + } + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, diff --git a/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/ServersPlugin.kt b/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/ServersPlugin.kt index d7c9312c..60130987 100644 --- a/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/ServersPlugin.kt +++ b/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/ServersPlugin.kt @@ -1,6 +1,10 @@ package com.redmadrobot.debug.plugin.servers -import androidx.core.os.bundleOf +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.viewinterop.AndroidViewBinding import androidx.fragment.app.Fragment import com.redmadrobot.debug.core.internal.CommonContainer import com.redmadrobot.debug.core.data.DebugDataProvider @@ -8,6 +12,7 @@ import com.redmadrobot.debug.core.extension.getPlugin import com.redmadrobot.debug.core.plugin.Plugin import com.redmadrobot.debug.core.internal.PluginDependencyContainer import com.redmadrobot.debug.plugin.servers.data.model.DebugServer +import com.redmadrobot.debug.plugin.servers.databinding.FragmentContainerServersBinding import com.redmadrobot.debug.plugin.servers.ui.ServersFragment import kotlinx.coroutines.runBlocking @@ -17,7 +22,7 @@ public class ServersPlugin( init { preInstalledServers.find { it.isDefault } - ?: throw IllegalStateException("DebugPanel - ServersPlugin can't be initialized. At least one server must be default") + ?: error("ServersPlugin can't be initialized. At least one server must be default") } public companion object { @@ -50,15 +55,35 @@ public class ServersPlugin( return ServersPluginContainer(preInstalledServers, commonContainer) } + @Deprecated( + "You should't use fragments for you plugins. Please use Jetpack Compose", + replaceWith = ReplaceWith("content()", "com.redmadrobot.debug.core.plugin.Plugin") + ) override fun getFragment(): Fragment { - return ServersFragment().apply { - arguments = bundleOf(ServersFragment.IS_EDIT_MODE_KEY to false) - } + return ServersFragment() } + @Deprecated( + "You should't use fragments for you plugins. Please use Jetpack Compose", + replaceWith = ReplaceWith("content()", "com.redmadrobot.debug.core.plugin.Plugin") + ) override fun getSettingFragment(): Fragment { - return ServersFragment().apply { - arguments = bundleOf(ServersFragment.IS_EDIT_MODE_KEY to true) - } + return ServersFragment() + } + + @Composable + override fun content() { + AndroidViewBinding( + FragmentContainerServersBinding::inflate, + modifier = Modifier.verticalScroll(rememberScrollState()) + ) + } + + @Composable + override fun settingsContent() { + AndroidViewBinding( + FragmentContainerServersBinding::inflate, + modifier = Modifier.verticalScroll(rememberScrollState()) + ) } } diff --git a/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/ui/ServersFragment.kt b/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/ui/ServersFragment.kt index 18abd101..083b858f 100644 --- a/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/ui/ServersFragment.kt +++ b/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/ui/ServersFragment.kt @@ -10,30 +10,22 @@ import com.redmadrobot.debug.common.extension.obtainShareViewModel import com.redmadrobot.debug.core.extension.getPlugin import com.redmadrobot.debug.panel.common.databinding.ItemSectionHeaderBinding import com.redmadrobot.debug.plugin.servers.R -import com.redmadrobot.itemsadapter.ItemsAdapter -import com.redmadrobot.itemsadapter.itemsAdapter +import com.redmadrobot.debug.plugin.servers.ServersPlugin +import com.redmadrobot.debug.plugin.servers.ServersPluginContainer import com.redmadrobot.debug.plugin.servers.data.model.DebugServer import com.redmadrobot.debug.plugin.servers.databinding.FragmentServersBinding import com.redmadrobot.debug.plugin.servers.databinding.ItemDebugServerBinding -import com.redmadrobot.debug.plugin.servers.ServersPlugin -import com.redmadrobot.debug.plugin.servers.ServersPluginContainer import com.redmadrobot.debug.plugin.servers.ui.item.DebugServerItems +import com.redmadrobot.itemsadapter.ItemsAdapter import com.redmadrobot.itemsadapter.bind +import com.redmadrobot.itemsadapter.itemsAdapter import com.redmadrobot.debug.panel.common.R as CommonR internal class ServersFragment : PluginFragment(R.layout.fragment_servers) { - companion object { - const val IS_EDIT_MODE_KEY = "IS_EDIT_MODE_KEY" - } - private var _binding: FragmentServersBinding? = null private val binding get() = checkNotNull(_binding) - private val isEditMode by lazy { - requireNotNull(arguments).getBoolean(IS_EDIT_MODE_KEY) - } - private val viewModel by lazy { obtainShareViewModel { getPlugin() @@ -42,16 +34,12 @@ internal class ServersFragment : PluginFragment(R.layout.fragment_servers) { } } - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) - observe(viewModel.state, ::render) - viewModel.loadServers() - } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) _binding = FragmentServersBinding.bind(view) + observe(viewModel.state, ::render) binding.setViews() + viewModel.loadServers() } override fun onDestroyView() { @@ -64,7 +52,7 @@ internal class ServersFragment : PluginFragment(R.layout.fragment_servers) { addServer.setOnClickListener { ServerHostDialog.show(childFragmentManager) } - addServer.isVisible = isEditMode + addServer.isVisible = isSettingMode } private fun render(state: ServersViewState) { @@ -81,26 +69,28 @@ internal class ServersFragment : PluginFragment(R.layout.fragment_servers) { itemSectionTitle.text = item.header } } + is DebugServerItems.PreinstalledServer -> { bind(R.layout.item_debug_server) { itemServerName.text = item.debugServer.name - isSelectedIcon.isVisible = item.isSelected && !isEditMode - if (!isEditMode) { + isSelectedIcon.isVisible = item.isSelected && !isSettingMode + if (!isSettingMode) { root.setOnClickListener { viewModel.onServerSelected(item.debugServer) } } } } + is DebugServerItems.AddedServer -> { bind(R.layout.item_debug_server) { itemServerName.text = item.debugServer.name - isSelectedIcon.isVisible = item.isSelected && !isEditMode - itemServerDelete.isVisible = isEditMode + isSelectedIcon.isVisible = item.isSelected && !isSettingMode + itemServerDelete.isVisible = isSettingMode val server = item.debugServer itemServerDelete.setOnClickListener { viewModel.removeServer(server) } root.setOnClickListener { - if (!isEditMode) { + if (!isSettingMode) { viewModel.onServerSelected(server) } else { editServerData(server) diff --git a/plugins/servers/src/main/res/layout/fragment_container_servers.xml b/plugins/servers/src/main/res/layout/fragment_container_servers.xml new file mode 100644 index 00000000..b2ae4cfc --- /dev/null +++ b/plugins/servers/src/main/res/layout/fragment_container_servers.xml @@ -0,0 +1,7 @@ + + + From 68a84988be715744edd2d374a7d8f0aecf4add9e Mon Sep 17 00:00:00 2001 From: Roman Choryev Date: Mon, 3 Oct 2022 22:37:16 +0400 Subject: [PATCH 10/36] Account:Migrate account plugin to Jetpack Compose --- .../plugin/accounts/ui/AccountsFragment.kt | 111 +-------- .../plugin/accounts/ui/AccountsScreen.kt | 228 ++++++++++++++++++ .../plugin/accounts/ui/AccountsViewModel.kt | 35 ++- .../plugin/accounts/ui/AccountsViewState.kt | 12 +- .../accounts/ui/item/DebugAccountItems.kt | 10 - .../src/main/res/drawable/icon_account.xml | 2 +- .../src/main/res/layout/fragment_accounts.xml | 27 --- .../res/layout/fragment_accounts_compose.xml | 6 + .../redmadrobot/debug_sample/MainActivity.kt | 9 +- 9 files changed, 279 insertions(+), 161 deletions(-) create mode 100644 plugins/accounts/src/main/kotlin/com/redmadrobot/debug/plugin/accounts/ui/AccountsScreen.kt delete mode 100644 plugins/accounts/src/main/kotlin/com/redmadrobot/debug/plugin/accounts/ui/item/DebugAccountItems.kt delete mode 100644 plugins/accounts/src/main/res/layout/fragment_accounts.xml create mode 100644 plugins/accounts/src/main/res/layout/fragment_accounts_compose.xml diff --git a/plugins/accounts/src/main/kotlin/com/redmadrobot/debug/plugin/accounts/ui/AccountsFragment.kt b/plugins/accounts/src/main/kotlin/com/redmadrobot/debug/plugin/accounts/ui/AccountsFragment.kt index d4efbbd2..02e318a0 100644 --- a/plugins/accounts/src/main/kotlin/com/redmadrobot/debug/plugin/accounts/ui/AccountsFragment.kt +++ b/plugins/accounts/src/main/kotlin/com/redmadrobot/debug/plugin/accounts/ui/AccountsFragment.kt @@ -2,37 +2,21 @@ package com.redmadrobot.debug.plugin.accounts.ui import android.os.Bundle import android.view.View -import android.widget.Toast -import androidx.core.view.isVisible -import androidx.recyclerview.widget.LinearLayoutManager -import com.redmadrobot.debug.plugin.accounts.ui.add.AddAccountDialog -import com.redmadrobot.debug.plugin.accounts.ui.item.DebugAccountItems -import com.redmadrobot.debug.plugin.accounts.R -import com.redmadrobot.debug.plugin.accounts.data.model.DebugAccount -import com.redmadrobot.debug.plugin.accounts.databinding.FragmentAccountsBinding -import com.redmadrobot.debug.plugin.accounts.databinding.ItemAccountBinding -import com.redmadrobot.debug.plugin.accounts.AccountSelectedEvent -import com.redmadrobot.debug.plugin.accounts.AccountsPlugin -import com.redmadrobot.debug.plugin.accounts.AccountsPluginContainer +import com.redmadrobot.account_plugin.ui.AccountsScreen import com.redmadrobot.debug.common.base.PluginFragment -import com.redmadrobot.debug.common.extension.observe import com.redmadrobot.debug.common.extension.obtainShareViewModel import com.redmadrobot.debug.core.extension.getPlugin -import com.redmadrobot.debug.panel.common.databinding.ItemSectionHeaderBinding -import com.redmadrobot.itemsadapter.ItemsAdapter -import com.redmadrobot.itemsadapter.bind -import com.redmadrobot.itemsadapter.itemsAdapter -import com.redmadrobot.debug.panel.common.R as CommonR +import com.redmadrobot.debug.plugin.accounts.AccountsPlugin +import com.redmadrobot.debug.plugin.accounts.AccountsPluginContainer +import com.redmadrobot.debug.plugin.accounts.R +import com.redmadrobot.debug.plugin.accounts.databinding.FragmentAccountsComposeBinding -internal class AccountsFragment : PluginFragment(R.layout.fragment_accounts) { +internal class AccountsFragment : PluginFragment(R.layout.fragment_accounts_compose) { companion object { const val IS_EDIT_MODE_KEY = "IS_EDIT_MODE_KEY" } - private var _binding: FragmentAccountsBinding? = null - private val binding get() = checkNotNull(_binding) - private val isEditMode by lazy { requireNotNull(arguments).getBoolean(IS_EDIT_MODE_KEY) } @@ -45,88 +29,11 @@ internal class AccountsFragment : PluginFragment(R.layout.fragment_accounts) { } } - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) - observe(viewModel.state, ::render) - viewModel.loadAccounts() - } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - _binding = FragmentAccountsBinding.bind(view) - binding.setView() - } - - override fun onDestroyView() { - super.onDestroyView() - _binding = null - } - - private fun FragmentAccountsBinding.setView() { - accountList.layoutManager = LinearLayoutManager(requireContext()) - addAccount.setOnClickListener { - AddAccountDialog.show( - requireActivity().supportFragmentManager - ) + val binding = FragmentAccountsComposeBinding.bind(view) + binding.composeRoot.setContent { + AccountsScreen(viewModel, isEditMode) } - addAccount.isVisible = isEditMode - } - - private fun render(state: AccountsViewState) { - val adapter = createAdapterByState(state) - binding.accountList.adapter = adapter - } - - private fun createAdapterByState(state: AccountsViewState): ItemsAdapter { - return itemsAdapter(state.preInstalledAccounts.plus(state.addedAccounts)) { item -> - when (item) { - is DebugAccountItems.Header -> { - bind(CommonR.layout.item_section_header) { - itemSectionTitle.text = item.header - } - } - is DebugAccountItems.PreinstalledAccount -> { - bind(R.layout.item_account) { - accountLogin.text = item.account.login - if (!isEditMode) { - root.setOnClickListener { setAccountAsCurrent(item.account) } - } - } - } - is DebugAccountItems.AddedAccount -> { - bind(R.layout.item_account) { - accountLogin.text = item.account.login - accountDelete.isVisible = isEditMode - accountDelete.setOnClickListener { viewModel.removeAccount(item.account) } - root.setOnClickListener { onAddedAccountClicked(item.account) } - } - } - } - } - } - - private fun onAddedAccountClicked(account: DebugAccount) { - if (isEditMode) openAccountDialog(account) else setAccountAsCurrent(account) - } - - private fun openAccountDialog(account: DebugAccount) { - AddAccountDialog.show( - fragmentManager = requireActivity().supportFragmentManager, - account = account - ) - } - - private fun setAccountAsCurrent(account: DebugAccount) { - getPlugin().debugAuthenticator.onAccountSelected(account) - pushEvent(account) - } - - private fun pushEvent(account: DebugAccount) { - Toast.makeText( - requireActivity(), - "Account ${account.login} selected", - Toast.LENGTH_SHORT - ).show() - getPlugin().pushEvent(AccountSelectedEvent(account)) } } diff --git a/plugins/accounts/src/main/kotlin/com/redmadrobot/debug/plugin/accounts/ui/AccountsScreen.kt b/plugins/accounts/src/main/kotlin/com/redmadrobot/debug/plugin/accounts/ui/AccountsScreen.kt new file mode 100644 index 00000000..49c5cfcb --- /dev/null +++ b/plugins/accounts/src/main/kotlin/com/redmadrobot/debug/plugin/accounts/ui/AccountsScreen.kt @@ -0,0 +1,228 @@ +package com.redmadrobot.account_plugin.ui + +import android.annotation.SuppressLint +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.compose.ui.window.Dialog +import androidx.lifecycle.asFlow +import com.google.accompanist.themeadapter.material.MdcTheme +import com.redmadrobot.debug.plugin.accounts.R +import com.redmadrobot.debug.plugin.accounts.data.model.DebugAccount +import com.redmadrobot.debug.plugin.accounts.ui.AccountsViewModel +import com.redmadrobot.debug.plugin.accounts.ui.AccountsViewState + +@SuppressLint("UnusedMaterialScaffoldPaddingParameter") +@Composable +internal fun AccountsScreen( + viewModel: AccountsViewModel, + isEditMode: Boolean +) { + val state by viewModel.state.asFlow().collectAsState(AccountsViewState()) + var showDialog by remember { mutableStateOf(false) } + var editableAccount by remember { mutableStateOf(null) } + + MdcTheme { + Scaffold( + floatingActionButton = { + if (isEditMode) { + ExtendedFloatingActionButton( + onClick = { showDialog = true }, + text = { Text("Add") }, + icon = { Icon(painterResource(R.drawable.icon_add_account), contentDescription = null) } + ) + } + } + ) { + AccountScreenLayout( + state = state, + isEditMode = isEditMode, + onSelectedClick = viewModel::setAccountAsCurrent, + onOpenDialogClick = { + showDialog = true + editableAccount = it + }, + onDeleteClick = viewModel::removeAccount, + ) + if (showDialog) { + AccountDetailsDialog( + onDismiss = { + showDialog = false + editableAccount = null + }, + onSaveClick = viewModel::saveAccount, + account = editableAccount + ) + } + } + } + LaunchedEffect(viewModel) { + viewModel.loadAccounts() + } +} + +@Composable +private fun AccountScreenLayout( + state: AccountsViewState, + isEditMode: Boolean, + onSelectedClick: (DebugAccount) -> Unit, + onDeleteClick: (DebugAccount) -> Unit, + onOpenDialogClick: (DebugAccount?) -> Unit, +) { + LazyColumn( + modifier = Modifier.fillMaxSize().padding(horizontal = 16.dp), + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + state.allItems.forEach { item -> + item { + when (item) { + is DebugAccountItem.Header -> { + Text( + item.header.uppercase(), + modifier = Modifier.fillParentMaxWidth().padding(vertical = 16.dp), + fontSize = 16.sp + ) + } + + is DebugAccountItem.PreinstalledAccount -> { + AccountItem( + item.account, + false, + onItemClick = { if (!isEditMode) onSelectedClick(it) } + ) + } + + is DebugAccountItem.AddedAccount -> { + AccountItem( + account = item.account, + showDelete = isEditMode, + onItemClick = { if (isEditMode) onOpenDialogClick(it) else onSelectedClick(it) }, + onDeleteClick = onDeleteClick, + ) + } + } + } + } + } +} + +@Composable +private fun AccountItem( + account: DebugAccount, + showDelete: Boolean, + onItemClick: (DebugAccount) -> Unit, + onDeleteClick: (DebugAccount) -> Unit = {}, +) { + Card( + modifier = Modifier.fillMaxWidth() + .height(56.dp) + .clickable { onItemClick(account) } + ) { + Box(modifier = Modifier.fillMaxSize().padding(horizontal = 16.dp)) { + Row( + modifier = Modifier.fillMaxSize(), + horizontalArrangement = Arrangement.spacedBy(32.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + painter = painterResource(R.drawable.icon_account), + contentDescription = null, + tint = MaterialTheme.colors.primary + ) + Text(account.login) + } + if (showDelete) { + IconButton( + modifier = Modifier.align(Alignment.CenterEnd), + onClick = { onDeleteClick(account) }, + ) { + Icon( + painterResource(R.drawable.icon_delete), + contentDescription = null, + tint = Color.Red + ) + } + } + } + } +} + +@Composable +private fun AccountDetailsDialog( + onDismiss: () -> Unit, + onSaveClick: (DebugAccount) -> Unit, + account: DebugAccount? = null, +) { + var login by remember { mutableStateOf(account?.login.orEmpty()) } + var password by remember { mutableStateOf(account?.password.orEmpty()) } + var pin by remember { mutableStateOf(account?.pin.orEmpty()) } + + Dialog(onDismissRequest = onDismiss) { + Surface( + shape = RoundedCornerShape(16.dp), + color = Color.White + ) { + Box(modifier = Modifier.padding(16.dp)) { + Column { + OutlinedTextField( + value = login, + onValueChange = { login = it }, + label = { Text(stringResource(R.string.login_hint)) } + ) + Spacer(modifier = Modifier.height(24.dp)) + OutlinedTextField( + value = password, + onValueChange = { password = it }, + label = { Text(stringResource(R.string.password_hint)) } + ) + Spacer(modifier = Modifier.height(24.dp)) + OutlinedTextField( + value = pin, + onValueChange = { pin = it }, + label = { Text(stringResource(R.string.pin)) } + ) + Spacer(modifier = Modifier.height(24.dp)) + Button( + onClick = { + val account = DebugAccount( + id = account?.id ?: 0, + login = login, + password = password, + pin = pin + ) + onSaveClick(account) + onDismiss() + }, + modifier = Modifier.align(Alignment.End) + ) { + Text(stringResource(R.string.save_account_text).uppercase()) + } + } + } + } + } +} + +internal sealed class DebugAccountItem { + internal data class Header(val header: String) : DebugAccountItem() + internal data class PreinstalledAccount(val account: DebugAccount) : DebugAccountItem() + internal data class AddedAccount(var account: DebugAccount) : DebugAccountItem() +} + + +@Preview +@Composable +private fun DialogPreview() { + AccountDetailsDialog({}, {}) +} \ No newline at end of file diff --git a/plugins/accounts/src/main/kotlin/com/redmadrobot/debug/plugin/accounts/ui/AccountsViewModel.kt b/plugins/accounts/src/main/kotlin/com/redmadrobot/debug/plugin/accounts/ui/AccountsViewModel.kt index 8614d8ca..dd5c4775 100644 --- a/plugins/accounts/src/main/kotlin/com/redmadrobot/debug/plugin/accounts/ui/AccountsViewModel.kt +++ b/plugins/accounts/src/main/kotlin/com/redmadrobot/debug/plugin/accounts/ui/AccountsViewModel.kt @@ -3,12 +3,15 @@ package com.redmadrobot.debug.plugin.accounts.ui import android.content.Context import androidx.lifecycle.MutableLiveData import androidx.lifecycle.viewModelScope -import com.redmadrobot.debug.plugin.accounts.ui.item.DebugAccountItems -import com.redmadrobot.debug.plugin.accounts.R +import com.redmadrobot.account_plugin.ui.DebugAccountItem import com.redmadrobot.debug.plugin.accounts.data.DebugAccountRepository import com.redmadrobot.debug.plugin.accounts.data.model.DebugAccount import com.redmadrobot.debug.common.base.PluginViewModel import com.redmadrobot.debug.common.extension.safeLaunch +import com.redmadrobot.debug.core.extension.getPlugin +import com.redmadrobot.debug.plugin.accounts.AccountSelectedEvent +import com.redmadrobot.debug.plugin.accounts.AccountsPlugin +import com.redmadrobot.debug.plugin.accounts.R internal class AccountsViewModel( private val context: Context, @@ -30,14 +33,13 @@ internal class AccountsViewModel( } } - fun saveAccount(login: String, password: String, pin: String) { - val account = DebugAccount( - login = login, - password = password, - pin = pin - ) + fun saveAccount(debugAccount: DebugAccount) { viewModelScope.safeLaunch { - debugAccountsRepository.addAccount(account) + if (debugAccount.id != 0) { + debugAccountsRepository.updateAccount(debugAccount) + } else { + debugAccountsRepository.addAccount(debugAccount) + } loadAddedAccounts() } } @@ -68,14 +70,21 @@ internal class AccountsViewModel( } } + fun setAccountAsCurrent(account: DebugAccount) { + getPlugin().apply { + debugAuthenticator.onAccountSelected(account) + pushEvent(AccountSelectedEvent(account)) + } + } + private suspend fun loadPreInstalledAccounts() { val accounts = debugAccountsRepository.getPreInstalledAccounts() val preInstalledAccounts = if (accounts.isNotEmpty()) { val items = accounts.map { account -> - DebugAccountItems.PreinstalledAccount(account) + DebugAccountItem.PreinstalledAccount(account) } val header = context.getString(R.string.pre_installed_accounts) - listOf(/*Header item*/DebugAccountItems.Header(header)).plus(items) + listOf(/*Header item*/DebugAccountItem.Header(header)).plus(items) } else { emptyList() } @@ -86,10 +95,10 @@ internal class AccountsViewModel( val accounts = debugAccountsRepository.getAccounts() val addedAccountItems = if (accounts.isNotEmpty()) { val items = accounts.map { account -> - DebugAccountItems.AddedAccount(account) + DebugAccountItem.AddedAccount(account) } val header = context.getString(R.string.added_accounts) - listOf(/*Header item*/DebugAccountItems.Header(header)).plus(items) + listOf(/*Header item*/DebugAccountItem.Header(header)).plus(items) } else { emptyList() } diff --git a/plugins/accounts/src/main/kotlin/com/redmadrobot/debug/plugin/accounts/ui/AccountsViewState.kt b/plugins/accounts/src/main/kotlin/com/redmadrobot/debug/plugin/accounts/ui/AccountsViewState.kt index aca9c13a..1087aeb7 100644 --- a/plugins/accounts/src/main/kotlin/com/redmadrobot/debug/plugin/accounts/ui/AccountsViewState.kt +++ b/plugins/accounts/src/main/kotlin/com/redmadrobot/debug/plugin/accounts/ui/AccountsViewState.kt @@ -1,8 +1,12 @@ package com.redmadrobot.debug.plugin.accounts.ui -import com.redmadrobot.debug.plugin.accounts.ui.item.DebugAccountItems +import androidx.compose.runtime.Stable +import com.redmadrobot.account_plugin.ui.DebugAccountItem +@Stable internal data class AccountsViewState( - val preInstalledAccounts: List, - val addedAccounts: List -) + val preInstalledAccounts: List = emptyList(), + val addedAccounts: List = emptyList(), +) { + val allItems = preInstalledAccounts.plus(addedAccounts) +} diff --git a/plugins/accounts/src/main/kotlin/com/redmadrobot/debug/plugin/accounts/ui/item/DebugAccountItems.kt b/plugins/accounts/src/main/kotlin/com/redmadrobot/debug/plugin/accounts/ui/item/DebugAccountItems.kt deleted file mode 100644 index 47e10ce3..00000000 --- a/plugins/accounts/src/main/kotlin/com/redmadrobot/debug/plugin/accounts/ui/item/DebugAccountItems.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.redmadrobot.debug.plugin.accounts.ui.item - -import com.redmadrobot.debug.plugin.accounts.data.model.DebugAccount - -internal sealed class DebugAccountItems { - internal data class Header(val header: String) : DebugAccountItems() - internal data class PreinstalledAccount(val account: DebugAccount) : DebugAccountItems() - internal data class AddedAccount(var account: DebugAccount) : DebugAccountItems() -} - diff --git a/plugins/accounts/src/main/res/drawable/icon_account.xml b/plugins/accounts/src/main/res/drawable/icon_account.xml index 7fe80c7b..4649894e 100644 --- a/plugins/accounts/src/main/res/drawable/icon_account.xml +++ b/plugins/accounts/src/main/res/drawable/icon_account.xml @@ -1,7 +1,7 @@ - - - - - - - diff --git a/plugins/accounts/src/main/res/layout/fragment_accounts_compose.xml b/plugins/accounts/src/main/res/layout/fragment_accounts_compose.xml new file mode 100644 index 00000000..73ae2883 --- /dev/null +++ b/plugins/accounts/src/main/res/layout/fragment_accounts_compose.xml @@ -0,0 +1,6 @@ + + diff --git a/sample/src/main/kotlin/com/redmadrobot/debug_sample/MainActivity.kt b/sample/src/main/kotlin/com/redmadrobot/debug_sample/MainActivity.kt index 40523371..13303021 100644 --- a/sample/src/main/kotlin/com/redmadrobot/debug_sample/MainActivity.kt +++ b/sample/src/main/kotlin/com/redmadrobot/debug_sample/MainActivity.kt @@ -142,10 +142,6 @@ class MainActivity : AppCompatActivity() { ).show() } - private fun chooseAccount() { - DebugPanel.showPanel(supportFragmentManager) - } - private fun showSelectedAccount(account: String) { Toast.makeText( this, @@ -154,6 +150,11 @@ class MainActivity : AppCompatActivity() { ).show() } + + private fun chooseAccount() { + DebugPanel.showPanel(supportFragmentManager) + } + @OptIn(DelicateCoroutinesApi::class) private fun observeFeatureToggles() { FlipperPlugin.addSource( From cfceedbf153869075711f8e68ae0fb4632595dac Mon Sep 17 00:00:00 2001 From: "a.emogurov" Date: Thu, 18 Jul 2024 12:42:01 +0400 Subject: [PATCH 11/36] Account: fix function params --- .../accounts/ui/add/AddAccountDialog.kt | 25 +++++++++++++------ 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/plugins/accounts/src/main/kotlin/com/redmadrobot/debug/plugin/accounts/ui/add/AddAccountDialog.kt b/plugins/accounts/src/main/kotlin/com/redmadrobot/debug/plugin/accounts/ui/add/AddAccountDialog.kt index eb6ce92d..e43f7952 100644 --- a/plugins/accounts/src/main/kotlin/com/redmadrobot/debug/plugin/accounts/ui/add/AddAccountDialog.kt +++ b/plugins/accounts/src/main/kotlin/com/redmadrobot/debug/plugin/accounts/ui/add/AddAccountDialog.kt @@ -8,12 +8,12 @@ import android.widget.LinearLayout import androidx.core.os.bundleOf import androidx.fragment.app.DialogFragment import androidx.fragment.app.FragmentManager -import com.redmadrobot.debug.plugin.accounts.data.model.DebugAccount -import com.redmadrobot.debug.plugin.accounts.databinding.DialogAddAccountBinding -import com.redmadrobot.debug.plugin.accounts.AccountsPlugin -import com.redmadrobot.debug.plugin.accounts.AccountsPluginContainer import com.redmadrobot.debug.common.extension.obtainShareViewModel import com.redmadrobot.debug.core.extension.getPlugin +import com.redmadrobot.debug.plugin.accounts.AccountsPlugin +import com.redmadrobot.debug.plugin.accounts.AccountsPluginContainer +import com.redmadrobot.debug.plugin.accounts.data.model.DebugAccount +import com.redmadrobot.debug.plugin.accounts.databinding.DialogAddAccountBinding internal class AddAccountDialog : DialogFragment() { @@ -108,12 +108,21 @@ internal class AddAccountDialog : DialogFragment() { val login = binding.accountLogin.text.toString() val password = binding.accountPassword.text.toString() val pin = binding.accountPin.text.toString() - if (isEditMode) { - id?.let { id -> + + id?.let { id -> + if (isEditMode) { sharedViewModel.updateAccount(id, login, password, pin) + } else { + val debugAccount = DebugAccount( + id = id, + login = login, + password = password, + pin = pin + ) + + sharedViewModel.saveAccount(debugAccount) + } - } else { - sharedViewModel.saveAccount(login, password, pin) } dialog?.dismiss() } From f179ffa2129e63f6a2cb14b36d5730551ce3bd36 Mon Sep 17 00:00:00 2001 From: Roman Choryev Date: Thu, 8 Dec 2022 18:26:37 +0400 Subject: [PATCH 12/36] Accounts: Add the ability to open `accounts` plugin from JetpackCompose --- .../debug/plugin/accounts/AccountsPlugin.kt | 20 +++++ .../plugin/accounts/ui/AccountsFragment.kt | 30 ++++--- .../plugin/accounts/ui/AccountsScreen.kt | 85 +++++++++++-------- .../res/layout/fragment_accounts_compose.xml | 6 -- .../res/layout/fragment_container_account.xml | 7 ++ 5 files changed, 93 insertions(+), 55 deletions(-) delete mode 100644 plugins/accounts/src/main/res/layout/fragment_accounts_compose.xml create mode 100644 plugins/accounts/src/main/res/layout/fragment_container_account.xml diff --git a/plugins/accounts/src/main/kotlin/com/redmadrobot/debug/plugin/accounts/AccountsPlugin.kt b/plugins/accounts/src/main/kotlin/com/redmadrobot/debug/plugin/accounts/AccountsPlugin.kt index 35717d8f..d0428e5f 100644 --- a/plugins/accounts/src/main/kotlin/com/redmadrobot/debug/plugin/accounts/AccountsPlugin.kt +++ b/plugins/accounts/src/main/kotlin/com/redmadrobot/debug/plugin/accounts/AccountsPlugin.kt @@ -1,5 +1,7 @@ package com.redmadrobot.debug.plugin.accounts +import androidx.compose.runtime.Composable +import androidx.compose.ui.viewinterop.AndroidViewBinding import androidx.core.os.bundleOf import androidx.fragment.app.Fragment import com.redmadrobot.debug.plugin.accounts.authenticator.DebugAuthenticator @@ -40,6 +42,24 @@ public class AccountsPlugin( } } + @Composable + override fun content() { + AndroidViewBinding(factory = FragmentContainerAccountBinding::inflate) { + fragmentContainer.getFragment().apply { + arguments = bundleOf(AccountsFragment.IS_EDIT_MODE_KEY to false) + } + } + } + + @Composable + override fun settingsContent() { + AndroidViewBinding(factory = FragmentContainerAccountBinding::inflate) { + fragmentContainer.getFragment().apply { + arguments = bundleOf(AccountsFragment.IS_EDIT_MODE_KEY to true) + } + } + } + override fun getSettingFragment(): Fragment { return AccountsFragment().apply { arguments = bundleOf(AccountsFragment.IS_EDIT_MODE_KEY to true) diff --git a/plugins/accounts/src/main/kotlin/com/redmadrobot/debug/plugin/accounts/ui/AccountsFragment.kt b/plugins/accounts/src/main/kotlin/com/redmadrobot/debug/plugin/accounts/ui/AccountsFragment.kt index 02e318a0..7c6fb5c4 100644 --- a/plugins/accounts/src/main/kotlin/com/redmadrobot/debug/plugin/accounts/ui/AccountsFragment.kt +++ b/plugins/accounts/src/main/kotlin/com/redmadrobot/debug/plugin/accounts/ui/AccountsFragment.kt @@ -1,24 +1,24 @@ package com.redmadrobot.debug.plugin.accounts.ui import android.os.Bundle -import android.view.View +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.compose.ui.platform.ComposeView +import androidx.fragment.app.Fragment import com.redmadrobot.account_plugin.ui.AccountsScreen -import com.redmadrobot.debug.common.base.PluginFragment import com.redmadrobot.debug.common.extension.obtainShareViewModel import com.redmadrobot.debug.core.extension.getPlugin import com.redmadrobot.debug.plugin.accounts.AccountsPlugin import com.redmadrobot.debug.plugin.accounts.AccountsPluginContainer -import com.redmadrobot.debug.plugin.accounts.R -import com.redmadrobot.debug.plugin.accounts.databinding.FragmentAccountsComposeBinding -internal class AccountsFragment : PluginFragment(R.layout.fragment_accounts_compose) { +internal class AccountsFragment : Fragment() { - companion object { - const val IS_EDIT_MODE_KEY = "IS_EDIT_MODE_KEY" + private val isSettingMode: Boolean by lazy { + activity?.javaClass?.simpleName == "DebugActivity" } - private val isEditMode by lazy { - requireNotNull(arguments).getBoolean(IS_EDIT_MODE_KEY) + companion object { + const val IS_EDIT_MODE_KEY = "IS_EDIT_MODE_KEY" } private val viewModel by lazy { @@ -29,11 +29,13 @@ internal class AccountsFragment : PluginFragment(R.layout.fragment_accounts_comp } } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - val binding = FragmentAccountsComposeBinding.bind(view) - binding.composeRoot.setContent { - AccountsScreen(viewModel, isEditMode) + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ) = ComposeView(inflater.context).apply { + setContent { + AccountsScreen(viewModel, isSettingMode) } } } diff --git a/plugins/accounts/src/main/kotlin/com/redmadrobot/debug/plugin/accounts/ui/AccountsScreen.kt b/plugins/accounts/src/main/kotlin/com/redmadrobot/debug/plugin/accounts/ui/AccountsScreen.kt index 49c5cfcb..34ae5e0e 100644 --- a/plugins/accounts/src/main/kotlin/com/redmadrobot/debug/plugin/accounts/ui/AccountsScreen.kt +++ b/plugins/accounts/src/main/kotlin/com/redmadrobot/debug/plugin/accounts/ui/AccountsScreen.kt @@ -33,40 +33,44 @@ internal fun AccountsScreen( var showDialog by remember { mutableStateOf(false) } var editableAccount by remember { mutableStateOf(null) } - MdcTheme { - Scaffold( - floatingActionButton = { - if (isEditMode) { - ExtendedFloatingActionButton( - onClick = { showDialog = true }, - text = { Text("Add") }, - icon = { Icon(painterResource(R.drawable.icon_add_account), contentDescription = null) } - ) - } + Scaffold( + floatingActionButton = { + if (isEditMode) { + ExtendedFloatingActionButton( + onClick = { showDialog = true }, + text = { Text("Add") }, + icon = { + Icon( + painterResource(R.drawable.icon_add_account), + contentDescription = null + ) + } + ) } - ) { - AccountScreenLayout( - state = state, - isEditMode = isEditMode, - onSelectedClick = viewModel::setAccountAsCurrent, - onOpenDialogClick = { - showDialog = true - editableAccount = it + } + ) { + AccountScreenLayout( + state = state, + isEditMode = isEditMode, + onSelectedClick = viewModel::setAccountAsCurrent, + onOpenDialogClick = { + showDialog = true + editableAccount = it + }, + onDeleteClick = viewModel::removeAccount, + ) + if (showDialog) { + AccountDetailsDialog( + onDismiss = { + showDialog = false + editableAccount = null }, - onDeleteClick = viewModel::removeAccount, + onSaveClick = viewModel::saveAccount, + account = editableAccount ) - if (showDialog) { - AccountDetailsDialog( - onDismiss = { - showDialog = false - editableAccount = null - }, - onSaveClick = viewModel::saveAccount, - account = editableAccount - ) - } } } + LaunchedEffect(viewModel) { viewModel.loadAccounts() } @@ -81,7 +85,9 @@ private fun AccountScreenLayout( onOpenDialogClick: (DebugAccount?) -> Unit, ) { LazyColumn( - modifier = Modifier.fillMaxSize().padding(horizontal = 16.dp), + modifier = Modifier + .fillMaxSize() + .padding(horizontal = 16.dp), verticalArrangement = Arrangement.spacedBy(8.dp) ) { state.allItems.forEach { item -> @@ -90,7 +96,9 @@ private fun AccountScreenLayout( is DebugAccountItem.Header -> { Text( item.header.uppercase(), - modifier = Modifier.fillParentMaxWidth().padding(vertical = 16.dp), + modifier = Modifier + .fillParentMaxWidth() + .padding(vertical = 16.dp), fontSize = 16.sp ) } @@ -107,7 +115,11 @@ private fun AccountScreenLayout( AccountItem( account = item.account, showDelete = isEditMode, - onItemClick = { if (isEditMode) onOpenDialogClick(it) else onSelectedClick(it) }, + onItemClick = { + if (isEditMode) onOpenDialogClick(it) else onSelectedClick( + it + ) + }, onDeleteClick = onDeleteClick, ) } @@ -125,11 +137,14 @@ private fun AccountItem( onDeleteClick: (DebugAccount) -> Unit = {}, ) { Card( - modifier = Modifier.fillMaxWidth() + modifier = Modifier + .fillMaxWidth() .height(56.dp) .clickable { onItemClick(account) } ) { - Box(modifier = Modifier.fillMaxSize().padding(horizontal = 16.dp)) { + Box(modifier = Modifier + .fillMaxSize() + .padding(horizontal = 16.dp)) { Row( modifier = Modifier.fillMaxSize(), horizontalArrangement = Arrangement.spacedBy(32.dp), @@ -225,4 +240,4 @@ internal sealed class DebugAccountItem { @Composable private fun DialogPreview() { AccountDetailsDialog({}, {}) -} \ No newline at end of file +} diff --git a/plugins/accounts/src/main/res/layout/fragment_accounts_compose.xml b/plugins/accounts/src/main/res/layout/fragment_accounts_compose.xml deleted file mode 100644 index 73ae2883..00000000 --- a/plugins/accounts/src/main/res/layout/fragment_accounts_compose.xml +++ /dev/null @@ -1,6 +0,0 @@ - - diff --git a/plugins/accounts/src/main/res/layout/fragment_container_account.xml b/plugins/accounts/src/main/res/layout/fragment_container_account.xml new file mode 100644 index 00000000..6a6bbfa7 --- /dev/null +++ b/plugins/accounts/src/main/res/layout/fragment_container_account.xml @@ -0,0 +1,7 @@ + + + From 83788a2298538faaeffe9dcde1853035a3802f95 Mon Sep 17 00:00:00 2001 From: Roman Choryev Date: Thu, 8 Dec 2022 20:20:50 +0400 Subject: [PATCH 13/36] Variable:Add the ability to open `variable` plugin from JetpackCompose --- .../debug/plugin/variable/VariablePlugin.kt | 17 +++++++++++++++++ .../res/layout/fragment_container_variable.xml | 6 ++++++ .../src/main/res/layout/fragment_variable.xml | 4 ++-- 3 files changed, 25 insertions(+), 2 deletions(-) create mode 100644 plugins/variable/src/main/res/layout/fragment_container_variable.xml diff --git a/plugins/variable/src/main/kotlin/com/redmadrobot/debug/plugin/variable/VariablePlugin.kt b/plugins/variable/src/main/kotlin/com/redmadrobot/debug/plugin/variable/VariablePlugin.kt index 9f75c358..1e4dcb5a 100644 --- a/plugins/variable/src/main/kotlin/com/redmadrobot/debug/plugin/variable/VariablePlugin.kt +++ b/plugins/variable/src/main/kotlin/com/redmadrobot/debug/plugin/variable/VariablePlugin.kt @@ -2,6 +2,11 @@ package com.redmadrobot.debug.plugin.variable import android.view.View import android.view.ViewGroup +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.viewinterop.AndroidViewBinding import androidx.fragment.app.Fragment import com.redmadrobot.debug.core.internal.CommonContainer import com.redmadrobot.debug.core.extension.getPlugin @@ -27,9 +32,21 @@ public class VariablePlugin( ) } + @Deprecated( + "You should't use fragments for you plugins. Please use Jetpack Compose", + replaceWith = ReplaceWith("content()", "com.redmadrobot.debug.core.plugin.Plugin") + ) override fun getFragment(): Fragment { return VariableFragment() } + + @Composable + override fun content() { + AndroidViewBinding( + FragmentContainerVariableBinding::inflate, + modifier = Modifier.verticalScroll(rememberScrollState()) + ) + } } public fun T.asDebugVariable( diff --git a/plugins/variable/src/main/res/layout/fragment_container_variable.xml b/plugins/variable/src/main/res/layout/fragment_container_variable.xml new file mode 100644 index 00000000..5b94476f --- /dev/null +++ b/plugins/variable/src/main/res/layout/fragment_container_variable.xml @@ -0,0 +1,6 @@ + + diff --git a/plugins/variable/src/main/res/layout/fragment_variable.xml b/plugins/variable/src/main/res/layout/fragment_variable.xml index 1f14ffee..388f7cc9 100644 --- a/plugins/variable/src/main/res/layout/fragment_variable.xml +++ b/plugins/variable/src/main/res/layout/fragment_variable.xml @@ -1,5 +1,5 @@ - - + From 765f9e63eaaa794d7b18142c308d286c9a9dc897 Mon Sep 17 00:00:00 2001 From: Roman Choryev Date: Thu, 12 Jan 2023 13:27:34 +0400 Subject: [PATCH 14/36] Servers: Add the ability to add a stage with multiple hosts --- plugins/servers/build.gradle.kts | 1 + .../plugin/servers/ServerSelectedEvent.kt | 3 + .../debug/plugin/servers/ServersPlugin.kt | 3 +- .../plugin/servers/ServersPluginContainer.kt | 22 +++- .../servers/data/DebugServerRepository.kt | 71 ++++++++++-- .../servers/data/DebugStageRepository.kt | 78 ++++++++++++++ .../data/LocalDebugServerRepository.kt | 79 -------------- .../plugin/servers/data/model/DebugServer.kt | 9 +- .../servers/data/model/DebugServerData.kt | 10 ++ .../plugin/servers/data/model/DebugStage.kt | 25 +++++ .../servers/data/storage/DbConverters.kt | 20 ++++ .../servers/data/storage/DebugStagesDao.kt | 23 ++++ .../data/storage/ServersPluginDatabase.kt | 14 ++- .../data/storage/SharedPreferencesFactory.kt | 13 +++ .../DebugServerInterceptor.kt | 4 +- .../interceptor/DebugStageInterceptor.kt | 57 ++++++++++ .../DefaultOnServerChangedListener.kt | 7 -- .../plugin/servers/ui/ServersFragment.kt | 66 ++++++++---- .../plugin/servers/ui/ServersViewModel.kt | 101 ++++++++++++++---- .../plugin/servers/ui/ServersViewState.kt | 6 +- .../servers/ui/item/DebugServerItems.kt | 6 +- .../src/main/res/drawable/icon_router.xml | 13 ++- .../src/main/res/drawable/icon_stage.xml | 5 + .../src/main/res/layout/item_debug_stage.xml | 66 ++++++++++++ .../servers/src/main/res/values/strings.xml | 4 +- 25 files changed, 542 insertions(+), 164 deletions(-) create mode 100644 plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/data/DebugStageRepository.kt delete mode 100644 plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/data/LocalDebugServerRepository.kt create mode 100644 plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/data/model/DebugServerData.kt create mode 100644 plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/data/model/DebugStage.kt create mode 100644 plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/data/storage/DbConverters.kt create mode 100644 plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/data/storage/DebugStagesDao.kt create mode 100644 plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/data/storage/SharedPreferencesFactory.kt rename plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/{util => interceptor}/DebugServerInterceptor.kt (93%) create mode 100644 plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/interceptor/DebugStageInterceptor.kt delete mode 100644 plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/listener/DefaultOnServerChangedListener.kt create mode 100644 plugins/servers/src/main/res/drawable/icon_stage.xml create mode 100644 plugins/servers/src/main/res/layout/item_debug_stage.xml diff --git a/plugins/servers/build.gradle.kts b/plugins/servers/build.gradle.kts index 5574ed74..d96dfab8 100644 --- a/plugins/servers/build.gradle.kts +++ b/plugins/servers/build.gradle.kts @@ -55,5 +55,6 @@ dependencies { implementation(project(":core")) implementation(project(":common")) implementation(kotlin("stdlib")) + implementation(stack.kotlinx.serialization.json) kapt(androidx.room.compiler) } diff --git a/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/ServerSelectedEvent.kt b/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/ServerSelectedEvent.kt index a35d0527..a99d706c 100644 --- a/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/ServerSelectedEvent.kt +++ b/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/ServerSelectedEvent.kt @@ -2,5 +2,8 @@ package com.redmadrobot.debug.plugin.servers import com.redmadrobot.debug.core.DebugEvent import com.redmadrobot.debug.plugin.servers.data.model.DebugServer +import com.redmadrobot.debug.plugin.servers.data.model.DebugStage public data class ServerSelectedEvent(val debugServer: DebugServer) : DebugEvent + +public data class StageSelectedEvent(val debugServer: DebugStage) : DebugEvent diff --git a/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/ServersPlugin.kt b/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/ServersPlugin.kt index 60130987..86e1a85c 100644 --- a/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/ServersPlugin.kt +++ b/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/ServersPlugin.kt @@ -12,12 +12,13 @@ import com.redmadrobot.debug.core.extension.getPlugin import com.redmadrobot.debug.core.plugin.Plugin import com.redmadrobot.debug.core.internal.PluginDependencyContainer import com.redmadrobot.debug.plugin.servers.data.model.DebugServer +import com.redmadrobot.debug.plugin.servers.data.model.DebugServerData import com.redmadrobot.debug.plugin.servers.databinding.FragmentContainerServersBinding import com.redmadrobot.debug.plugin.servers.ui.ServersFragment import kotlinx.coroutines.runBlocking public class ServersPlugin( - private val preInstalledServers: List = emptyList() + private val preInstalledServers: List = emptyList(), ) : Plugin() { init { diff --git a/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/ServersPluginContainer.kt b/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/ServersPluginContainer.kt index 781a120c..487c9106 100644 --- a/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/ServersPluginContainer.kt +++ b/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/ServersPluginContainer.kt @@ -2,30 +2,42 @@ package com.redmadrobot.debug.plugin.servers import com.redmadrobot.debug.core.internal.CommonContainer import com.redmadrobot.debug.core.internal.PluginDependencyContainer -import com.redmadrobot.debug.plugin.servers.data.LocalDebugServerRepository +import com.redmadrobot.debug.plugin.servers.data.DebugServerRepository +import com.redmadrobot.debug.plugin.servers.data.DebugStageRepository import com.redmadrobot.debug.plugin.servers.data.model.DebugServer +import com.redmadrobot.debug.plugin.servers.data.model.DebugServerData +import com.redmadrobot.debug.plugin.servers.data.model.DebugStage import com.redmadrobot.debug.plugin.servers.data.storage.ServersPluginDatabase import com.redmadrobot.debug.plugin.servers.ui.ServersViewModel internal class ServersPluginContainer( - private val preInstalledServers: List, + private val preinstalledStages: List, private val container: CommonContainer ) : PluginDependencyContainer { private val pluginStorage by lazy { ServersPluginDatabase.getInstance(container.context) } val serversRepository by lazy { - LocalDebugServerRepository( + DebugServerRepository( container.context, pluginStorage.getDebugServersDao(), - preInstalledServers + preinstalledStages.filterIsInstance(), + ) + } + + val stagesRepository by lazy { + DebugStageRepository( + container.context, + pluginStorage.getDebugStagesDao(), + preinstalledStages.filterIsInstance() ) } fun createServersViewModel(): ServersViewModel { return ServersViewModel( container.context, - serversRepository + serversRepository, + stagesRepository ) } } diff --git a/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/data/DebugServerRepository.kt b/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/data/DebugServerRepository.kt index ec3a0aa2..61230f78 100644 --- a/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/data/DebugServerRepository.kt +++ b/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/data/DebugServerRepository.kt @@ -1,23 +1,76 @@ package com.redmadrobot.debug.plugin.servers.data +import android.content.Context import com.redmadrobot.debug.plugin.servers.data.model.DebugServer +import com.redmadrobot.debug.plugin.servers.data.storage.DebugServersDao +import com.redmadrobot.debug.plugin.servers.data.storage.SharedPreferencesProvider +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext -internal interface DebugServerRepository { +internal class DebugServerRepository( + private val context: Context, + private val debugServersDao: DebugServersDao, + private val preInstalledServers: List +) { - fun getPreInstalledServers(): List + companion object { + private const val SELECTED_SERVER_URL = "SELECTED_SERVER_URL" + private const val SELECTED_SERVER_NAME = "SELECTED_SERVER_NAME" + } - fun saveSelectedServer(selectedServer: DebugServer) + private val sharedPreferences by lazy { + SharedPreferencesProvider.get(context) + } + fun getPreInstalledServers(): List { + return preInstalledServers + } - fun getDefault(): DebugServer + fun saveSelectedServer(selectedServer: DebugServer) { + sharedPreferences.edit().apply { + putString(SELECTED_SERVER_NAME, selectedServer.name) + putString(SELECTED_SERVER_URL, selectedServer.url) + }.apply() + } - suspend fun getSelectedServer(): DebugServer + suspend fun getSelectedServer(): DebugServer { + val serverName = sharedPreferences.getString(SELECTED_SERVER_NAME, null) + val serverUrl = sharedPreferences.getString(SELECTED_SERVER_URL, null) - suspend fun getServers(): List + return if (serverName != null && serverUrl != null) { + preInstalledServers.find { it.name == serverName && it.url == serverUrl } + ?: debugServersDao.getServer(serverName, serverUrl) + ?: getDefault() + } else { + getDefault() + } + } - suspend fun addServer(server: DebugServer) + fun getDefault(): DebugServer { + return preInstalledServers.first { it.isDefault } + } - suspend fun removeServer(server: DebugServer) + suspend fun addServer(server: DebugServer) { + withContext(Dispatchers.IO) { + debugServersDao.insert(server) + } + } - suspend fun updateServer(server: DebugServer) + suspend fun getServers(): List { + return withContext(Dispatchers.IO) { + debugServersDao.getAll() + } + } + + suspend fun removeServer(server: DebugServer) { + withContext(Dispatchers.IO) { + debugServersDao.remove(server) + } + } + + suspend fun updateServer(server: DebugServer) { + withContext(Dispatchers.IO) { + debugServersDao.update(server) + } + } } diff --git a/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/data/DebugStageRepository.kt b/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/data/DebugStageRepository.kt new file mode 100644 index 00000000..b3c489c8 --- /dev/null +++ b/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/data/DebugStageRepository.kt @@ -0,0 +1,78 @@ +package com.redmadrobot.debug.plugin.servers.data + +import android.content.Context +import androidx.core.content.edit +import com.redmadrobot.debug.plugin.servers.data.model.DebugStage +import com.redmadrobot.debug.plugin.servers.data.storage.DebugStagesDao +import com.redmadrobot.debug.plugin.servers.data.storage.SharedPreferencesProvider +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext + +internal class DebugStageRepository( + private val context: Context, + private val debugStagesDao: DebugStagesDao, + private val preInstalledStages: List, +) { + + companion object { + private const val SELECTED_STAGE_HOSTS_HASH = "SELECTED_STAGE_HOSTS_HASH" + private const val SELECTED_STAGE_NAME = "SELECTED_STAGE_NAME" + } + + private val sharedPreferences by lazy { + SharedPreferencesProvider.get(context) + } + + fun getPreInstalledStages(): List { + return preInstalledStages + } + + fun saveSelectedStage(selectedStage: DebugStage) { + sharedPreferences.edit { + putString(SELECTED_STAGE_NAME, selectedStage.name) + putInt(SELECTED_STAGE_HOSTS_HASH, selectedStage.hosts.hashCode()) + } + } + + fun getSelectedStage(): DebugStage { + val stageName = sharedPreferences.getString(SELECTED_STAGE_NAME, null) + val hostsHash = sharedPreferences.getInt(SELECTED_STAGE_HOSTS_HASH, -1) + + return if (stageName != null) { + preInstalledStages + .find { it.name == stageName && it.hosts.hashCode() == hostsHash } + ?: debugStagesDao.getStage(stageName) + ?: getDefault() + } else { + getDefault() + } + } + + private fun getDefault(): DebugStage { + return preInstalledStages.first { it.isDefault } + } + + suspend fun addStage(server: DebugStage) { + withContext(Dispatchers.IO) { + debugStagesDao.insert(server) + } + } + + suspend fun getStages(): List { + return withContext(Dispatchers.IO) { + debugStagesDao.getAll() + } + } + + suspend fun removeStage(server: DebugStage) { + withContext(Dispatchers.IO) { + debugStagesDao.remove(server) + } + } + + suspend fun updateStage(server: DebugStage) { + withContext(Dispatchers.IO) { + debugStagesDao.update(server) + } + } +} diff --git a/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/data/LocalDebugServerRepository.kt b/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/data/LocalDebugServerRepository.kt deleted file mode 100644 index bce6553b..00000000 --- a/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/data/LocalDebugServerRepository.kt +++ /dev/null @@ -1,79 +0,0 @@ -package com.redmadrobot.debug.plugin.servers.data - -import android.content.Context -import com.redmadrobot.debug.plugin.servers.data.model.DebugServer -import com.redmadrobot.debug.plugin.servers.data.storage.DebugServersDao -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext - -internal class LocalDebugServerRepository( - private val context: Context, - private val debugServersDao: DebugServersDao, - private val preInstalledServers: List -) : DebugServerRepository { - - companion object { - private const val NAME = ":servers" - private const val SELECTED_SERVER_URL = "SELECTED_SERVER_URL" - private const val SELECTED_SERVER_NAME = "SELECTED_SERVER_NAME" - } - - - private val sharedPreferences by lazy { - val prefFileName = "${context.packageName}$NAME" - context.getSharedPreferences(prefFileName, 0) - } - - - override fun getPreInstalledServers(): List { - return preInstalledServers - } - - override fun saveSelectedServer(selectedServer: DebugServer) { - sharedPreferences.edit().apply { - putString(SELECTED_SERVER_NAME, selectedServer.name) - putString(SELECTED_SERVER_URL, selectedServer.url) - }.apply() - } - - override suspend fun getSelectedServer(): DebugServer { - val serverName = sharedPreferences.getString(SELECTED_SERVER_NAME, null) - val serverUrl = sharedPreferences.getString(SELECTED_SERVER_URL, null) - - return if (serverName != null && serverUrl != null) { - preInstalledServers.find { it.name == serverName && it.url == serverUrl } - ?: debugServersDao.getServer(serverName, serverUrl) - ?: getDefault() - } else { - getDefault() - } - } - - override fun getDefault(): DebugServer { - return preInstalledServers.first { it.isDefault } - } - - override suspend fun addServer(server: DebugServer) { - withContext(Dispatchers.IO) { - debugServersDao.insert(server) - } - } - - override suspend fun getServers(): List { - return withContext(Dispatchers.IO) { - debugServersDao.getAll() - } - } - - override suspend fun removeServer(server: DebugServer) { - withContext(Dispatchers.IO) { - debugServersDao.remove(server) - } - } - - override suspend fun updateServer(server: DebugServer) { - withContext(Dispatchers.IO) { - debugServersDao.update(server) - } - } -} diff --git a/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/data/model/DebugServer.kt b/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/data/model/DebugServer.kt index 2a6d4972..554c77ec 100644 --- a/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/data/model/DebugServer.kt +++ b/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/data/model/DebugServer.kt @@ -6,11 +6,11 @@ import androidx.room.PrimaryKey @Entity(tableName = DebugServer.TABLE_NAME) public data class DebugServer( @PrimaryKey(autoGenerate = true) - val id: Int = 0, - val name: String, + override val id: Int = 0, + override val name: String, val url: String, - val isDefault: Boolean = false -) { + override val isDefault: Boolean = false +) : DebugServerData { internal companion object { const val TABLE_NAME = "debug_server" } @@ -19,5 +19,4 @@ public data class DebugServer( val otherServer = other as DebugServer return this.name == otherServer.name && this.url == otherServer.url } - } diff --git a/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/data/model/DebugServerData.kt b/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/data/model/DebugServerData.kt new file mode 100644 index 00000000..88cd8193 --- /dev/null +++ b/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/data/model/DebugServerData.kt @@ -0,0 +1,10 @@ +package com.redmadrobot.debug.plugin.servers.data.model + +import com.redmadrobot.debug.core.annotation.DebugPanelInternal + +@DebugPanelInternal +public interface DebugServerData { + public val id: Int + public val name: String + public val isDefault: Boolean +} diff --git a/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/data/model/DebugStage.kt b/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/data/model/DebugStage.kt new file mode 100644 index 00000000..5cec5d22 --- /dev/null +++ b/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/data/model/DebugStage.kt @@ -0,0 +1,25 @@ +package com.redmadrobot.debug.plugin.servers.data.model + +import androidx.room.Entity +import androidx.room.PrimaryKey + + +@Entity(tableName = DebugStage.TABLE_NAME) +public data class DebugStage( + @PrimaryKey(autoGenerate = true) + override val id: Int = 0, + override val name: String, + val hosts: Map, + override val isDefault: Boolean = false +) : DebugServerData { + + internal companion object { + const val TABLE_NAME = "debug_stage" + } + + override fun equals(other: Any?): Boolean { + val otherServer = other as DebugStage + return this.name == otherServer.name + && this.hosts.hashCode() == otherServer.hosts.hashCode() + } +} diff --git a/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/data/storage/DbConverters.kt b/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/data/storage/DbConverters.kt new file mode 100644 index 00000000..9cd85265 --- /dev/null +++ b/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/data/storage/DbConverters.kt @@ -0,0 +1,20 @@ +package com.redmadrobot.debug.plugin.servers.data.storage + +import androidx.room.TypeConverter +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json + + +internal class DbConverters { + + @TypeConverter + fun fromMap(map: Map?): String { + return Json.encodeToString(map ?: emptyMap()) + } + + @TypeConverter + fun toMap(data: String?): Map { + return data?.let { Json.decodeFromString(data) } ?: emptyMap() + } +} \ No newline at end of file diff --git a/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/data/storage/DebugStagesDao.kt b/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/data/storage/DebugStagesDao.kt new file mode 100644 index 00000000..417e02af --- /dev/null +++ b/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/data/storage/DebugStagesDao.kt @@ -0,0 +1,23 @@ +package com.redmadrobot.debug.plugin.servers.data.storage + +import androidx.room.* +import com.redmadrobot.debug.plugin.servers.data.model.DebugStage + +@Dao +internal interface DebugStagesDao { + + @Query("SELECT * FROM ${DebugStage.TABLE_NAME}") + suspend fun getAll(): List + + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun insert(server: DebugStage) + + @Delete + suspend fun remove(server: DebugStage) + + @Update + suspend fun update(server: DebugStage) + + @Query("SELECT * FROM ${DebugStage.TABLE_NAME} WHERE name = :name") + fun getStage(name: String): DebugStage? +} diff --git a/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/data/storage/ServersPluginDatabase.kt b/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/data/storage/ServersPluginDatabase.kt index 0094d5d4..dc168d83 100644 --- a/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/data/storage/ServersPluginDatabase.kt +++ b/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/data/storage/ServersPluginDatabase.kt @@ -4,15 +4,20 @@ import android.content.Context import androidx.room.Database import androidx.room.Room import androidx.room.RoomDatabase +import androidx.room.TypeConverters import com.redmadrobot.debug.plugin.servers.data.model.DebugServer +import com.redmadrobot.debug.plugin.servers.data.model.DebugStage @Database( - entities = [DebugServer::class], - version = 1 + entities = [DebugServer::class, DebugStage::class], + version = 2 ) +@TypeConverters(DbConverters::class) internal abstract class ServersPluginDatabase : RoomDatabase() { abstract fun getDebugServersDao(): DebugServersDao + abstract fun getDebugStagesDao(): DebugStagesDao + companion object { private const val DATABASE_NAME = "servers_plugin_db" @@ -21,7 +26,10 @@ internal abstract class ServersPluginDatabase : RoomDatabase() { context.applicationContext, ServersPluginDatabase::class.java, DATABASE_NAME - ).build() + ) + .fallbackToDestructiveMigration() + .allowMainThreadQueries() + .build() } } } diff --git a/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/data/storage/SharedPreferencesFactory.kt b/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/data/storage/SharedPreferencesFactory.kt new file mode 100644 index 00000000..22f09b88 --- /dev/null +++ b/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/data/storage/SharedPreferencesFactory.kt @@ -0,0 +1,13 @@ +package com.redmadrobot.debug.plugin.servers.data.storage + +import android.content.Context +import android.content.SharedPreferences + +internal object SharedPreferencesProvider { + private const val NAME = ":servers" + + fun get(context: Context): SharedPreferences { + val prefFileName = "${context.packageName}$NAME" + return context.getSharedPreferences(prefFileName, 0) + } +} diff --git a/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/util/DebugServerInterceptor.kt b/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/interceptor/DebugServerInterceptor.kt similarity index 93% rename from plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/util/DebugServerInterceptor.kt rename to plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/interceptor/DebugServerInterceptor.kt index a9d2fec5..dd6e8a77 100644 --- a/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/util/DebugServerInterceptor.kt +++ b/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/interceptor/DebugServerInterceptor.kt @@ -1,4 +1,4 @@ -package com.redmadrobot.debug.plugin.servers.util +package com.redmadrobot.debug.plugin.servers.interceptor import com.redmadrobot.debug.core.DebugPanel import com.redmadrobot.debug.core.extension.getPlugin @@ -23,7 +23,7 @@ public class DebugServerInterceptor : Interceptor { } /** - * Дополнительная Модификация запроса + * Additional request modification * */ public fun modifyRequest(block: (Request, DebugServer) -> Request): DebugServerInterceptor { this.requestModifier = block diff --git a/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/interceptor/DebugStageInterceptor.kt b/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/interceptor/DebugStageInterceptor.kt new file mode 100644 index 00000000..fd0b03fd --- /dev/null +++ b/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/interceptor/DebugStageInterceptor.kt @@ -0,0 +1,57 @@ +package com.redmadrobot.debug.plugin.servers.interceptor + +import com.redmadrobot.debug.core.extension.getPlugin +import com.redmadrobot.debug.core.internal.DebugPanel +import com.redmadrobot.debug.plugin.servers.ServersPlugin +import com.redmadrobot.debug.plugin.servers.ServersPluginContainer +import com.redmadrobot.debug.plugin.servers.data.model.DebugStage +import okhttp3.HttpUrl +import okhttp3.Interceptor +import okhttp3.Request +import okhttp3.Response +import java.net.URI + +public class DebugStageInterceptor(private val hostName: String) : Interceptor { + + private var requestModifier: ((Request, DebugStage) -> Request?)? = null + + private val stageRepository by lazy { + getPlugin() + .getContainer() + .stagesRepository + } + + /** + * Additional request modification + * */ + public fun modifyRequest(block: (Request, DebugStage) -> Request): DebugStageInterceptor { + this.requestModifier = block + return this + } + + override fun intercept(chain: Interceptor.Chain): Response { + var request = chain.request() + return if (DebugPanel.isInitialized) { + val debugStage = stageRepository.getSelectedStage() + val host = debugStage.hosts[hostName] + if (host != null) { + val newUrl = request.getNewUrl(host) + request = request.newBuilder() + .url(newUrl) + .build() + } + chain.proceed(requestModifier?.invoke(request, debugStage) ?: request) + } else { + chain.proceed(request) + } + } + + + private fun Request.getNewUrl(debugServer: String): HttpUrl { + val serverUri = URI(debugServer) + return this.url.newBuilder() + .scheme(serverUri.scheme) + .host(serverUri.host) + .build() + } +} diff --git a/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/listener/DefaultOnServerChangedListener.kt b/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/listener/DefaultOnServerChangedListener.kt deleted file mode 100644 index fc438885..00000000 --- a/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/listener/DefaultOnServerChangedListener.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.redmadrobot.debug.plugin.servers.listener - -import com.redmadrobot.debug.plugin.servers.data.model.DebugServer - -internal class DefaultOnServerChangedListener : OnServerChangedListener { - override fun onChanged(server: DebugServer?) {/*do nothing*/} -} diff --git a/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/ui/ServersFragment.kt b/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/ui/ServersFragment.kt index 083b858f..2b90702c 100644 --- a/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/ui/ServersFragment.kt +++ b/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/ui/ServersFragment.kt @@ -13,8 +13,10 @@ import com.redmadrobot.debug.plugin.servers.R import com.redmadrobot.debug.plugin.servers.ServersPlugin import com.redmadrobot.debug.plugin.servers.ServersPluginContainer import com.redmadrobot.debug.plugin.servers.data.model.DebugServer +import com.redmadrobot.debug.plugin.servers.data.model.DebugStage import com.redmadrobot.debug.plugin.servers.databinding.FragmentServersBinding import com.redmadrobot.debug.plugin.servers.databinding.ItemDebugServerBinding +import com.redmadrobot.debug.plugin.servers.databinding.ItemDebugStageBinding import com.redmadrobot.debug.plugin.servers.ui.item.DebugServerItems import com.redmadrobot.itemsadapter.ItemsAdapter import com.redmadrobot.itemsadapter.bind @@ -55,13 +57,12 @@ internal class ServersFragment : PluginFragment(R.layout.fragment_servers) { addServer.isVisible = isSettingMode } - private fun render(state: ServersViewState) { + private fun render(state: List) { val adapter = createAdapterByState(state) binding.serverList.adapter = adapter } - private fun createAdapterByState(state: ServersViewState): ItemsAdapter { - val items = state.preInstalledServers.plus(state.addedServers) + private fun createAdapterByState(items: List): ItemsAdapter { return itemsAdapter(items) { item -> when (item) { is DebugServerItems.Header -> { @@ -71,29 +72,54 @@ internal class ServersFragment : PluginFragment(R.layout.fragment_servers) { } is DebugServerItems.PreinstalledServer -> { - bind(R.layout.item_debug_server) { - itemServerName.text = item.debugServer.name - isSelectedIcon.isVisible = item.isSelected && !isSettingMode - if (!isSettingMode) { - root.setOnClickListener { - viewModel.onServerSelected(item.debugServer) + val server = item.debugServer + if (server is DebugServer) { + bind(R.layout.item_debug_server) { + itemServerName.text = item.debugServer.name + isSelectedIcon.isVisible = item.isSelected && !isSettingMode + if (!isSettingMode) { + root.setOnClickListener { + viewModel.onServerSelected(server) + } + } + } + } else { + bind(R.layout.item_debug_stage) { + itemStageName.text = item.debugServer.name + isSelectedIcon.isVisible = item.isSelected && !isSettingMode + if (!isSettingMode && server is DebugStage) { + root.setOnClickListener { + viewModel.onStageSelected(server) + } } } } } is DebugServerItems.AddedServer -> { - bind(R.layout.item_debug_server) { - itemServerName.text = item.debugServer.name - isSelectedIcon.isVisible = item.isSelected && !isSettingMode - itemServerDelete.isVisible = isSettingMode - val server = item.debugServer - itemServerDelete.setOnClickListener { viewModel.removeServer(server) } - root.setOnClickListener { - if (!isSettingMode) { - viewModel.onServerSelected(server) - } else { - editServerData(server) + val server = item.debugServer + if (server is DebugServer) { + bind(R.layout.item_debug_server) { + itemServerName.text = item.debugServer.name + isSelectedIcon.isVisible = item.isSelected && !isSettingMode + itemServerDelete.isVisible = isSettingMode + itemServerDelete.setOnClickListener { viewModel.removeServer(server) } + root.setOnClickListener { + if (!isSettingMode) { + viewModel.onServerSelected(server) + } else { + editServerData(server) + } + } + } + } else { + bind(R.layout.item_debug_server) { + itemServerName.text = item.debugServer.name + isSelectedIcon.isVisible = item.isSelected && !isSettingMode + root.setOnClickListener { + if (!isSettingMode && server is DebugStage) { + viewModel.onStageSelected(server) + } } } } diff --git a/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/ui/ServersViewModel.kt b/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/ui/ServersViewModel.kt index 590ff1a1..c6f0c585 100644 --- a/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/ui/ServersViewModel.kt +++ b/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/ui/ServersViewModel.kt @@ -1,16 +1,22 @@ package com.redmadrobot.debug.plugin.servers.ui import android.content.Context +import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.map import androidx.lifecycle.viewModelScope import com.redmadrobot.debug.common.base.PluginViewModel import com.redmadrobot.debug.common.extension.safeLaunch import com.redmadrobot.debug.core.extension.getPlugin import com.redmadrobot.debug.plugin.servers.R -import com.redmadrobot.debug.plugin.servers.data.DebugServerRepository -import com.redmadrobot.debug.plugin.servers.data.model.DebugServer import com.redmadrobot.debug.plugin.servers.ServerSelectedEvent import com.redmadrobot.debug.plugin.servers.ServersPlugin +import com.redmadrobot.debug.plugin.servers.StageSelectedEvent +import com.redmadrobot.debug.plugin.servers.data.DebugServerRepository +import com.redmadrobot.debug.plugin.servers.data.DebugStageRepository +import com.redmadrobot.debug.plugin.servers.data.model.DebugServer +import com.redmadrobot.debug.plugin.servers.data.model.DebugServerData +import com.redmadrobot.debug.plugin.servers.data.model.DebugStage import com.redmadrobot.debug.plugin.servers.ui.item.DebugServerItems import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -18,22 +24,27 @@ import kotlinx.coroutines.withContext internal class ServersViewModel( private val context: Context, - private val serversRepository: DebugServerRepository + private val serversRepository: DebugServerRepository, + private val stagesRepository: DebugStageRepository ) : PluginViewModel() { - val state = MutableLiveData().apply { - /*Default state*/ - value = ServersViewState( - preInstalledServers = emptyList(), - addedServers = emptyList() - ) - } + private val _state = MutableLiveData(ServersViewState()) + val state: LiveData> + get() = _state.map { + it.preinstalledStages + .plus(it.addedStages) + .plus(it.preInstalledServers) + .plus(it.addedServers) + } + fun loadServers() { viewModelScope.safeLaunch { withContext(Dispatchers.IO) { loadPreInstalledServers() loadAddedServers() + loadPreInstalledStages() + loadAddedStages() } } } @@ -54,16 +65,16 @@ internal class ServersViewModel( } fun updateServerData(id: Int, name: String, url: String) { - val itemForUpdate = state.value?.addedServers + val itemForUpdate = _state.value?.addedServers ?.find { it is DebugServerItems.AddedServer && it.debugServer.id == id } as? DebugServerItems.AddedServer val serverForUpdate = itemForUpdate?.debugServer - val updatedServer = serverForUpdate?.copy(name = name, url = url) - updatedServer?.let { server -> + (serverForUpdate as? DebugServer)?.let { + val updatedServer = serverForUpdate.copy(name = name, url = url) viewModelScope.safeLaunch { - serversRepository.updateServer(server) + serversRepository.updateServer(updatedServer) loadAddedServers() } } @@ -71,7 +82,8 @@ internal class ServersViewModel( fun onServerSelected(debugServer: DebugServer) { viewModelScope.launch { - if (debugServer != getSelectedServer()) { + val selectedServer = serversRepository.getSelectedServer() + if (debugServer != selectedServer) { getPlugin().pushEvent(ServerSelectedEvent(debugServer)) serversRepository.saveSelectedServer(debugServer) loadServers() @@ -79,47 +91,90 @@ internal class ServersViewModel( } } + fun onStageSelected(debugStage: DebugStage) { + val selectedStage = stagesRepository.getSelectedStage() + if (debugStage != selectedStage) { + stagesRepository.saveSelectedStage(debugStage) + getPlugin().pushEvent(StageSelectedEvent(debugStage)) + loadServers() + } + } + + @Deprecated("Migration to the stages system. Please use loadPreInstalledStages()") private suspend fun loadPreInstalledServers() { val servers = serversRepository.getPreInstalledServers() val headerText = context.getString(R.string.pre_installed_servers) val serverItems = mapToPreinstalledItems(headerText, servers) withContext(Dispatchers.Main) { - state.value = state.value?.copy(preInstalledServers = serverItems) + _state.value = _state.value?.copy(preInstalledServers = serverItems) } } + private suspend fun loadPreInstalledStages() { + val stages = stagesRepository.getPreInstalledStages() + val headerText = context.getString(R.string.pre_installed_stages) + val stageItems = mapToPreinstalledItems(headerText, stages) + withContext(Dispatchers.Main) { + _state.value = _state.value?.copy(preinstalledStages = stageItems) + } + } + + @Deprecated("Migration to the stages system. Please use loadAddedStages()") private suspend fun loadAddedServers() { val servers = serversRepository.getServers() val headerText = context.getString(R.string.added_servers) val serverItems = mapToAddedItems(headerText, servers) withContext(Dispatchers.Main) { - state.value = state.value?.copy(addedServers = serverItems) + _state.value = _state.value?.copy(addedServers = serverItems) + } + } + + private suspend fun loadAddedStages() { + val stages = stagesRepository.getStages() + val headerText = context.getString(R.string.added_servers) + val serverItems = mapToAddedItems(headerText, stages) + withContext(Dispatchers.Main) { + _state.value = _state.value?.copy(addedStages = serverItems) } } private suspend fun mapToPreinstalledItems( header: String, - servers: List + servers: List ): List { + val selectedServer = serversRepository.getSelectedServer() + val selectedStage = stagesRepository.getSelectedStage() + val items = servers.map { debugServer -> - val isSelected = getSelectedServer().url == debugServer.url + val isSelected = when (debugServer) { + is DebugServer -> selectedServer.url == debugServer.url + is DebugStage -> selectedStage == debugServer + else -> false + } DebugServerItems.PreinstalledServer(debugServer, isSelected) } - return listOf(/*Заголовок списка*/DebugServerItems.Header(header)).plus(items) + return listOf(DebugServerItems.Header(header)).plus(items) } private suspend fun mapToAddedItems( header: String, - servers: List + servers: List ): List { if (servers.isEmpty()) return emptyList() + val selectedServer = serversRepository.getSelectedServer() + val selectedStage = stagesRepository.getSelectedStage() + val items = servers.map { debugServer -> - val isSelected = getSelectedServer().url == debugServer.url + val isSelected = when (debugServer) { + is DebugServer -> selectedServer.url == debugServer.url + is DebugStage -> selectedStage == debugServer + else -> false + } DebugServerItems.AddedServer(debugServer, isSelected) } - return listOf(/*Заголовок списка*/DebugServerItems.Header(header)).plus(items) + return listOf(DebugServerItems.Header(header)).plus(items) } private suspend fun getSelectedServer(): DebugServer { diff --git a/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/ui/ServersViewState.kt b/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/ui/ServersViewState.kt index 1df7294c..aa3b5354 100644 --- a/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/ui/ServersViewState.kt +++ b/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/ui/ServersViewState.kt @@ -3,6 +3,8 @@ package com.redmadrobot.debug.plugin.servers.ui import com.redmadrobot.debug.plugin.servers.ui.item.DebugServerItems internal data class ServersViewState( - val preInstalledServers: List, - val addedServers: List + val preInstalledServers: List = emptyList(), + val addedServers: List = emptyList(), + val preinstalledStages: List = emptyList(), + val addedStages: List = emptyList(), ) diff --git a/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/ui/item/DebugServerItems.kt b/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/ui/item/DebugServerItems.kt index f90e9ea5..534e6aea 100644 --- a/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/ui/item/DebugServerItems.kt +++ b/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/ui/item/DebugServerItems.kt @@ -1,18 +1,18 @@ package com.redmadrobot.debug.plugin.servers.ui.item -import com.redmadrobot.debug.plugin.servers.data.model.DebugServer +import com.redmadrobot.debug.plugin.servers.data.model.DebugServerData internal sealed class DebugServerItems { data class Header(val header: String) : DebugServerItems() data class PreinstalledServer( - var debugServer: DebugServer, + var debugServer: DebugServerData, var isSelected: Boolean ) : DebugServerItems() data class AddedServer( - var debugServer: DebugServer, + var debugServer: DebugServerData, var isSelected: Boolean ) : DebugServerItems() } diff --git a/plugins/servers/src/main/res/drawable/icon_router.xml b/plugins/servers/src/main/res/drawable/icon_router.xml index af4e8e58..70ac290e 100644 --- a/plugins/servers/src/main/res/drawable/icon_router.xml +++ b/plugins/servers/src/main/res/drawable/icon_router.xml @@ -1,5 +1,10 @@ - - + + diff --git a/plugins/servers/src/main/res/drawable/icon_stage.xml b/plugins/servers/src/main/res/drawable/icon_stage.xml new file mode 100644 index 00000000..dcde3147 --- /dev/null +++ b/plugins/servers/src/main/res/drawable/icon_stage.xml @@ -0,0 +1,5 @@ + + + diff --git a/plugins/servers/src/main/res/layout/item_debug_stage.xml b/plugins/servers/src/main/res/layout/item_debug_stage.xml new file mode 100644 index 00000000..4fbc0ea5 --- /dev/null +++ b/plugins/servers/src/main/res/layout/item_debug_stage.xml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + diff --git a/plugins/servers/src/main/res/values/strings.xml b/plugins/servers/src/main/res/values/strings.xml index a5ed16f8..8e6c23fd 100644 --- a/plugins/servers/src/main/res/values/strings.xml +++ b/plugins/servers/src/main/res/values/strings.xml @@ -1,11 +1,13 @@ Pre-installed servers + Pre-installed stages https://google.com Wrong host format. Must be filled save Default server - Added + Added servers + Added stages Name From e810f08fa716e6758ef5bb646e615a96aaa1a7fd Mon Sep 17 00:00:00 2001 From: Roman Choryev Date: Thu, 12 Jan 2023 15:56:22 +0300 Subject: [PATCH 15/36] No-op: update no-op module --- .../debug/noop/plugin/servers/DebugServer.kt | 3 ++- .../plugin/servers/DebugServerInterceptor.kt | 2 +- .../debug/noop/servers/DebugStage.kt | 9 +++++++++ .../noop/servers/DebugStageInterceptor.kt | 18 ++++++++++++++++++ 4 files changed, 30 insertions(+), 2 deletions(-) create mode 100644 no-op/src/main/kotlin/com/redmadrobot/debug/noop/servers/DebugStage.kt create mode 100644 no-op/src/main/kotlin/com/redmadrobot/debug/noop/servers/DebugStageInterceptor.kt diff --git a/no-op/src/main/kotlin/com/redmadrobot/debug/noop/plugin/servers/DebugServer.kt b/no-op/src/main/kotlin/com/redmadrobot/debug/noop/plugin/servers/DebugServer.kt index c4e77d68..91d54293 100644 --- a/no-op/src/main/kotlin/com/redmadrobot/debug/noop/plugin/servers/DebugServer.kt +++ b/no-op/src/main/kotlin/com/redmadrobot/debug/noop/plugin/servers/DebugServer.kt @@ -3,5 +3,6 @@ package com.redmadrobot.debug.plugin.servers.data.model data class DebugServer( val id: Int = 0, val name: String, - val url: String + val url: String, + val isDefault: Boolean = false ) diff --git a/no-op/src/main/kotlin/com/redmadrobot/debug/noop/plugin/servers/DebugServerInterceptor.kt b/no-op/src/main/kotlin/com/redmadrobot/debug/noop/plugin/servers/DebugServerInterceptor.kt index c8670bed..1540e0bc 100644 --- a/no-op/src/main/kotlin/com/redmadrobot/debug/noop/plugin/servers/DebugServerInterceptor.kt +++ b/no-op/src/main/kotlin/com/redmadrobot/debug/noop/plugin/servers/DebugServerInterceptor.kt @@ -1,4 +1,4 @@ -package com.redmadrobot.debug.plugin.servers.util +package com.redmadrobot.debug.servers.interceptor import com.redmadrobot.debug.plugin.servers.data.model.DebugServer import okhttp3.Interceptor diff --git a/no-op/src/main/kotlin/com/redmadrobot/debug/noop/servers/DebugStage.kt b/no-op/src/main/kotlin/com/redmadrobot/debug/noop/servers/DebugStage.kt new file mode 100644 index 00000000..8ebcad11 --- /dev/null +++ b/no-op/src/main/kotlin/com/redmadrobot/debug/noop/servers/DebugStage.kt @@ -0,0 +1,9 @@ +package com.redmadrobot.debug.servers.data.model + + +data class DebugStage( + val id: Int = 0, + val name: String, + val hosts: Map, + val isDefault: Boolean = false +) diff --git a/no-op/src/main/kotlin/com/redmadrobot/debug/noop/servers/DebugStageInterceptor.kt b/no-op/src/main/kotlin/com/redmadrobot/debug/noop/servers/DebugStageInterceptor.kt new file mode 100644 index 00000000..c2dccf2c --- /dev/null +++ b/no-op/src/main/kotlin/com/redmadrobot/debug/noop/servers/DebugStageInterceptor.kt @@ -0,0 +1,18 @@ +package com.redmadrobot.debug.servers.interceptor + +import com.redmadrobot.debug.servers.data.model.DebugStage +import okhttp3.Interceptor +import okhttp3.Request +import okhttp3.Response + + +public class DebugStageInterceptor(private val tag: String) : Interceptor { + + public fun modifyRequest(block: (Request, DebugStage) -> Request): DebugStageInterceptor { + return this + } + + override fun intercept(chain: Interceptor.Chain): Response { + return chain.proceed(chain.request()) + } +} From edd8e41d4bea76fd990117f54acea38e10ba51a4 Mon Sep 17 00:00:00 2001 From: Roman Choryev Date: Mon, 16 Jan 2023 20:52:37 +0400 Subject: [PATCH 16/36] Servers: Migrate to Jetpack Compose UI --- common/build.gradle.kts | 1 + .../debug/core/extension/ComposeExt.kt | 46 +++ .../debug/plugin/servers/ServersPlugin.kt | 22 +- .../plugin/servers/ServersPluginContainer.kt | 1 - .../plugin/servers/ui/ServerHostDialog.kt | 141 ------- .../plugin/servers/ui/ServersFragment.kt | 144 +------ .../plugin/servers/ui/ServersViewModel.kt | 248 ++++++------ .../plugin/servers/ui/ServersViewState.kt | 24 +- .../servers/ui/item/DebugServerItems.kt | 18 - .../debug/servers/ui/ServersScreen.kt | 374 ++++++++++++++++++ 10 files changed, 596 insertions(+), 423 deletions(-) create mode 100644 core/src/main/kotlin/com/redmadrobot/debug/core/extension/ComposeExt.kt create mode 100644 plugins/servers/src/main/kotlin/com/redmadrobot/debug/servers/ui/ServersScreen.kt diff --git a/common/build.gradle.kts b/common/build.gradle.kts index 122a98ff..282fce4d 100644 --- a/common/build.gradle.kts +++ b/common/build.gradle.kts @@ -56,6 +56,7 @@ dependencies { api(androidx.constraintlayout.compose) api(androidx.activity.compose) api(androidx.compose.ui.tooling.preview) + api(androidx.lifecycle.viewmodel.compose) api(androidx.room.runtime) api(androidx.room) api(androidx.core) diff --git a/core/src/main/kotlin/com/redmadrobot/debug/core/extension/ComposeExt.kt b/core/src/main/kotlin/com/redmadrobot/debug/core/extension/ComposeExt.kt new file mode 100644 index 00000000..4a812474 --- /dev/null +++ b/core/src/main/kotlin/com/redmadrobot/debug/core/extension/ComposeExt.kt @@ -0,0 +1,46 @@ +package com.redmadrobot.debug.core.extension + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.rememberUpdatedState +import androidx.compose.ui.platform.LocalLifecycleOwner +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleEventObserver +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.viewmodel.compose.viewModel +import com.redmadrobot.debug.core.annotation.DebugPanelInternal + +@DebugPanelInternal +@Composable +public inline fun provideViewModel(crossinline block: () -> T): T { + return viewModel( + factory = object : ViewModelProvider.Factory { + override fun create(modelClass: Class): T { + @Suppress("UNCHECKED_CAST") + return block() as T + } + } + ) +} + + +@DebugPanelInternal +@Composable +public fun OnLifecycleEvent(onEvent: (event: Lifecycle.Event) -> Unit) { + val eventHandler by rememberUpdatedState(onEvent) + val lifecycleOwner by rememberUpdatedState(LocalLifecycleOwner.current) + + DisposableEffect(lifecycleOwner) { + val lifecycle = lifecycleOwner.lifecycle + val observer = LifecycleEventObserver { _, event -> + eventHandler(event) + } + + lifecycle.addObserver(observer) + onDispose { + lifecycle.removeObserver(observer) + } + } +} diff --git a/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/ServersPlugin.kt b/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/ServersPlugin.kt index 86e1a85c..e08e79a4 100644 --- a/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/ServersPlugin.kt +++ b/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/ServersPlugin.kt @@ -1,9 +1,6 @@ package com.redmadrobot.debug.plugin.servers -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier import androidx.compose.ui.viewinterop.AndroidViewBinding import androidx.fragment.app.Fragment import com.redmadrobot.debug.core.internal.CommonContainer @@ -15,6 +12,7 @@ import com.redmadrobot.debug.plugin.servers.data.model.DebugServer import com.redmadrobot.debug.plugin.servers.data.model.DebugServerData import com.redmadrobot.debug.plugin.servers.databinding.FragmentContainerServersBinding import com.redmadrobot.debug.plugin.servers.ui.ServersFragment +import com.redmadrobot.debug.servers.ui.ServersScreen import kotlinx.coroutines.runBlocking public class ServersPlugin( @@ -57,16 +55,16 @@ public class ServersPlugin( } @Deprecated( - "You should't use fragments for you plugins. Please use Jetpack Compose", - replaceWith = ReplaceWith("content()", "com.redmadrobot.debug.core.plugin.Plugin") + "You shouldn't use fragments for you plugins. Please use Jetpack Compose", + replaceWith = ReplaceWith("content()") ) override fun getFragment(): Fragment { return ServersFragment() } @Deprecated( - "You should't use fragments for you plugins. Please use Jetpack Compose", - replaceWith = ReplaceWith("content()", "com.redmadrobot.debug.core.plugin.Plugin") + "You shouldn't use fragments for you plugins. Please use Jetpack Compose", + replaceWith = ReplaceWith("settingsContent()") ) override fun getSettingFragment(): Fragment { return ServersFragment() @@ -74,17 +72,11 @@ public class ServersPlugin( @Composable override fun content() { - AndroidViewBinding( - FragmentContainerServersBinding::inflate, - modifier = Modifier.verticalScroll(rememberScrollState()) - ) + ServersScreen(isEditMode = false) } @Composable override fun settingsContent() { - AndroidViewBinding( - FragmentContainerServersBinding::inflate, - modifier = Modifier.verticalScroll(rememberScrollState()) - ) + AndroidViewBinding(FragmentContainerServersBinding::inflate) } } diff --git a/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/ServersPluginContainer.kt b/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/ServersPluginContainer.kt index 487c9106..80b7228f 100644 --- a/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/ServersPluginContainer.kt +++ b/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/ServersPluginContainer.kt @@ -35,7 +35,6 @@ internal class ServersPluginContainer( fun createServersViewModel(): ServersViewModel { return ServersViewModel( - container.context, serversRepository, stagesRepository ) diff --git a/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/ui/ServerHostDialog.kt b/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/ui/ServerHostDialog.kt index 2700f823..e69de29b 100644 --- a/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/ui/ServerHostDialog.kt +++ b/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/ui/ServerHostDialog.kt @@ -1,141 +0,0 @@ -package com.redmadrobot.debug.plugin.servers.ui - -import android.os.Bundle -import android.util.Patterns -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.widget.LinearLayout -import androidx.fragment.app.DialogFragment -import androidx.fragment.app.FragmentManager -import com.redmadrobot.debug.common.extension.obtainShareViewModel -import com.redmadrobot.debug.core.extension.getPlugin -import com.redmadrobot.debug.plugin.servers.R -import com.redmadrobot.debug.plugin.servers.databinding.DialogServerBinding -import com.redmadrobot.debug.plugin.servers.ServersPlugin -import com.redmadrobot.debug.plugin.servers.ServersPluginContainer - -internal class ServerHostDialog : DialogFragment() { - - companion object { - const val KEY_NAME = "NAME" - const val KEY_URL = "URL" - const val KEY_ID = "ID" - private const val TAG = "AddServerDialog" - - fun show(fragmentManager: FragmentManager, params: Bundle? = null) { - ServerHostDialog().apply { - arguments = params - }.show(fragmentManager, TAG) - } - } - - private var _binding: DialogServerBinding? = null - private val binding get() = checkNotNull(_binding) - - private val shareViewModel by lazy { - obtainShareViewModel { - getPlugin() - .getContainer() - .createServersViewModel() - } - } - - private var isEditMode = false - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View? { - _binding = DialogServerBinding.inflate(inflater, container, false) - return binding.root - } - - override fun onStart() { - super.onStart() - dialog?.window?.setLayout( - LinearLayout.LayoutParams.MATCH_PARENT, - LinearLayout.LayoutParams.WRAP_CONTENT - ) - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - binding.setViews() - obtainArguments() - } - - override fun onDestroyView() { - super.onDestroyView() - _binding = null - } - - private fun obtainArguments() { - val name = arguments?.getString(KEY_NAME) - val url = arguments?.getString(KEY_URL) - if (!name.isNullOrEmpty() && !url.isNullOrEmpty()) { - binding.serverName.setText(name) - binding.serverHost.setText(url) - isEditMode = true - } - } - - private fun DialogServerBinding.setViews() { - saveServerButton.setOnClickListener { - val name = serverName.text.toString() - val url = serverHost.text.toString() - if (isDataValid(name, url)) { - save(name, url) - } else { - showWrongHostError() - } - } - serverName.requestFocus() - } - - private fun save(name: String, url: String) { - if (isEditMode) { - update(name, url) - } else { - saveNew(name, url) - } - } - - private fun update(name: String, url: String) { - val id = arguments?.getInt(KEY_ID) - id?.let { - shareViewModel.updateServerData(id, name, url) - } - dialog?.dismiss() - } - - private fun saveNew(name: String, url: String) { - shareViewModel.addServer(name, url) - dialog?.dismiss() - } - - private fun isDataValid(name: String, url: String): Boolean { - return when { - name.isEmpty() -> { - showEmptyNameError() - false - } - !Patterns.WEB_URL.matcher(url).matches() -> { - showWrongHostError() - false - } - else -> { - true - } - } - } - - private fun showEmptyNameError() { - binding.serverNameInputLayout.error = getString(R.string.error_empty_name) - } - - private fun showWrongHostError() { - binding.serverNameInputLayout.error = getString(R.string.error_wrong_host) - } -} diff --git a/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/ui/ServersFragment.kt b/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/ui/ServersFragment.kt index 2b90702c..4d610cb6 100644 --- a/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/ui/ServersFragment.kt +++ b/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/ui/ServersFragment.kt @@ -1,139 +1,25 @@ package com.redmadrobot.debug.plugin.servers.ui import android.os.Bundle -import android.view.View -import androidx.core.view.isVisible -import androidx.recyclerview.widget.LinearLayoutManager -import com.redmadrobot.debug.common.base.PluginFragment -import com.redmadrobot.debug.common.extension.observe -import com.redmadrobot.debug.common.extension.obtainShareViewModel -import com.redmadrobot.debug.core.extension.getPlugin -import com.redmadrobot.debug.panel.common.databinding.ItemSectionHeaderBinding -import com.redmadrobot.debug.plugin.servers.R -import com.redmadrobot.debug.plugin.servers.ServersPlugin -import com.redmadrobot.debug.plugin.servers.ServersPluginContainer -import com.redmadrobot.debug.plugin.servers.data.model.DebugServer -import com.redmadrobot.debug.plugin.servers.data.model.DebugStage -import com.redmadrobot.debug.plugin.servers.databinding.FragmentServersBinding -import com.redmadrobot.debug.plugin.servers.databinding.ItemDebugServerBinding -import com.redmadrobot.debug.plugin.servers.databinding.ItemDebugStageBinding -import com.redmadrobot.debug.plugin.servers.ui.item.DebugServerItems -import com.redmadrobot.itemsadapter.ItemsAdapter -import com.redmadrobot.itemsadapter.bind -import com.redmadrobot.itemsadapter.itemsAdapter -import com.redmadrobot.debug.panel.common.R as CommonR +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.compose.ui.platform.ComposeView +import androidx.fragment.app.Fragment +import com.redmadrobot.debug.servers.ui.ServersScreen -internal class ServersFragment : PluginFragment(R.layout.fragment_servers) { +internal class ServersFragment : Fragment() { - private var _binding: FragmentServersBinding? = null - private val binding get() = checkNotNull(_binding) - - private val viewModel by lazy { - obtainShareViewModel { - getPlugin() - .getContainer() - .createServersViewModel() - } - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - _binding = FragmentServersBinding.bind(view) - observe(viewModel.state, ::render) - binding.setViews() - viewModel.loadServers() - } - - override fun onDestroyView() { - super.onDestroyView() - _binding = null - } - - private fun FragmentServersBinding.setViews() { - serverList.layoutManager = LinearLayoutManager(requireContext()) - addServer.setOnClickListener { - ServerHostDialog.show(childFragmentManager) - } - addServer.isVisible = isSettingMode - } - - private fun render(state: List) { - val adapter = createAdapterByState(state) - binding.serverList.adapter = adapter - } - - private fun createAdapterByState(items: List): ItemsAdapter { - return itemsAdapter(items) { item -> - when (item) { - is DebugServerItems.Header -> { - bind(CommonR.layout.item_section_header) { - itemSectionTitle.text = item.header - } - } - - is DebugServerItems.PreinstalledServer -> { - val server = item.debugServer - if (server is DebugServer) { - bind(R.layout.item_debug_server) { - itemServerName.text = item.debugServer.name - isSelectedIcon.isVisible = item.isSelected && !isSettingMode - if (!isSettingMode) { - root.setOnClickListener { - viewModel.onServerSelected(server) - } - } - } - } else { - bind(R.layout.item_debug_stage) { - itemStageName.text = item.debugServer.name - isSelectedIcon.isVisible = item.isSelected && !isSettingMode - if (!isSettingMode && server is DebugStage) { - root.setOnClickListener { - viewModel.onStageSelected(server) - } - } - } - } - } - - is DebugServerItems.AddedServer -> { - val server = item.debugServer - if (server is DebugServer) { - bind(R.layout.item_debug_server) { - itemServerName.text = item.debugServer.name - isSelectedIcon.isVisible = item.isSelected && !isSettingMode - itemServerDelete.isVisible = isSettingMode - itemServerDelete.setOnClickListener { viewModel.removeServer(server) } - root.setOnClickListener { - if (!isSettingMode) { - viewModel.onServerSelected(server) - } else { - editServerData(server) - } - } - } - } else { - bind(R.layout.item_debug_server) { - itemServerName.text = item.debugServer.name - isSelectedIcon.isVisible = item.isSelected && !isSettingMode - root.setOnClickListener { - if (!isSettingMode && server is DebugStage) { - viewModel.onStageSelected(server) - } - } - } - } - } - } - } + private val isSettingMode: Boolean by lazy { + activity?.javaClass?.simpleName == "DebugActivity" } - private fun editServerData(debugServer: DebugServer) { - val bundle = Bundle().apply { - putInt(ServerHostDialog.KEY_ID, debugServer.id) - putString(ServerHostDialog.KEY_NAME, debugServer.name) - putString(ServerHostDialog.KEY_URL, debugServer.url) + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ) = ComposeView(inflater.context).apply { + setContent { + ServersScreen(isEditMode = isSettingMode) } - ServerHostDialog.show(childFragmentManager, bundle) } } diff --git a/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/ui/ServersViewModel.kt b/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/ui/ServersViewModel.kt index c6f0c585..25332490 100644 --- a/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/ui/ServersViewModel.kt +++ b/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/ui/ServersViewModel.kt @@ -1,9 +1,6 @@ package com.redmadrobot.debug.plugin.servers.ui -import android.content.Context -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.map +import android.util.Patterns import androidx.lifecycle.viewModelScope import com.redmadrobot.debug.common.base.PluginViewModel import com.redmadrobot.debug.common.extension.safeLaunch @@ -17,168 +14,191 @@ import com.redmadrobot.debug.plugin.servers.data.DebugStageRepository import com.redmadrobot.debug.plugin.servers.data.model.DebugServer import com.redmadrobot.debug.plugin.servers.data.model.DebugServerData import com.redmadrobot.debug.plugin.servers.data.model.DebugStage -import com.redmadrobot.debug.plugin.servers.ui.item.DebugServerItems import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import kotlinx.coroutines.withContext internal class ServersViewModel( - private val context: Context, private val serversRepository: DebugServerRepository, private val stagesRepository: DebugStageRepository ) : PluginViewModel() { - private val _state = MutableLiveData(ServersViewState()) - val state: LiveData> - get() = _state.map { - it.preinstalledStages - .plus(it.addedStages) - .plus(it.preInstalledServers) - .plus(it.addedServers) - } - + private val _state = MutableStateFlow(ServersViewState()) + val state: StateFlow = _state fun loadServers() { - viewModelScope.safeLaunch { - withContext(Dispatchers.IO) { - loadPreInstalledServers() - loadAddedServers() - loadPreInstalledStages() - loadAddedStages() + viewModelScope.launch(Dispatchers.IO) { + _state.update { + it.copy( + preInstalledServers = serversRepository.getPreInstalledServers() + .mapToServerItems(), + preinstalledStages = stagesRepository.getPreInstalledStages().mapToStageItems(), + addedServers = serversRepository.getServers().mapToServerItems(), + addedStages = stagesRepository.getStages().mapToStageItems() + ) } } } - fun addServer(name: String, url: String) { - val server = DebugServer(name = name, url = url, isDefault = false) - viewModelScope.safeLaunch { - serversRepository.addServer(server) - loadAddedServers() + fun onAddClicked() { + _state.update { + it.copy(serverDialogState = it.serverDialogState.copy(show = true)) } } - fun removeServer(debugServer: DebugServer) { - viewModelScope.safeLaunch { - serversRepository.removeServer(debugServer) - loadAddedServers() + fun dismissDialogs() { + _state.update { + it.copy(serverDialogState = ServerDialogState()) } } - fun updateServerData(id: Int, name: String, url: String) { - val itemForUpdate = _state.value?.addedServers - ?.find { it is DebugServerItems.AddedServer && it.debugServer.id == id } - as? DebugServerItems.AddedServer - - val serverForUpdate = itemForUpdate?.debugServer - - (serverForUpdate as? DebugServer)?.let { - val updatedServer = serverForUpdate.copy(name = name, url = url) - viewModelScope.safeLaunch { - serversRepository.updateServer(updatedServer) - loadAddedServers() - } + fun onServerNameChanged(name: String) { + _state.update { + it.copy(serverDialogState = it.serverDialogState.copy(serverName = name)) } } - fun onServerSelected(debugServer: DebugServer) { - viewModelScope.launch { - val selectedServer = serversRepository.getSelectedServer() - if (debugServer != selectedServer) { - getPlugin().pushEvent(ServerSelectedEvent(debugServer)) - serversRepository.saveSelectedServer(debugServer) - loadServers() - } + fun onServerUrlChanged(url: String) { + _state.update { + it.copy(serverDialogState = it.serverDialogState.copy(serverUrl = url)) } } - fun onStageSelected(debugStage: DebugStage) { - val selectedStage = stagesRepository.getSelectedStage() - if (debugStage != selectedStage) { - stagesRepository.saveSelectedStage(debugStage) - getPlugin().pushEvent(StageSelectedEvent(debugStage)) - loadServers() + fun onSaveServerClicked() { + if (allServerFieldValid()) { + val dialogState = _state.value.serverDialogState + + if (dialogState.editableServerId == null) { + addServer(dialogState.serverName, dialogState.serverUrl) + } else { + updateServerData( + dialogState.editableServerId, + dialogState.serverName, + dialogState.serverUrl + ) + } + + _state.update { + it.copy(serverDialogState = ServerDialogState()) + } } } - @Deprecated("Migration to the stages system. Please use loadPreInstalledStages()") - private suspend fun loadPreInstalledServers() { - val servers = serversRepository.getPreInstalledServers() - val headerText = context.getString(R.string.pre_installed_servers) - val serverItems = mapToPreinstalledItems(headerText, servers) - withContext(Dispatchers.Main) { - _state.value = _state.value?.copy(preInstalledServers = serverItems) + private fun allServerFieldValid(): Boolean { + val dialogState = _state.value.serverDialogState + val nameError = if (dialogState.serverName.isEmpty()) { + R.string.error_empty_name + } else { + null + } + val urlError = if (!Patterns.WEB_URL.matcher(dialogState.serverUrl).matches()) { + R.string.error_wrong_host + } else { + null } - } - private suspend fun loadPreInstalledStages() { - val stages = stagesRepository.getPreInstalledStages() - val headerText = context.getString(R.string.pre_installed_stages) - val stageItems = mapToPreinstalledItems(headerText, stages) - withContext(Dispatchers.Main) { - _state.value = _state.value?.copy(preinstalledStages = stageItems) + _state.update { + it.copy( + serverDialogState = dialogState.copy( + nameError = nameError, + urlError = urlError + ) + ) } + + return nameError == null && urlError == null } - @Deprecated("Migration to the stages system. Please use loadAddedStages()") - private suspend fun loadAddedServers() { - val servers = serversRepository.getServers() - val headerText = context.getString(R.string.added_servers) - val serverItems = mapToAddedItems(headerText, servers) - withContext(Dispatchers.Main) { - _state.value = _state.value?.copy(addedServers = serverItems) + fun onRemoveServerClicked(debugServer: DebugServer) { + viewModelScope.safeLaunch { + serversRepository.removeServer(debugServer) + _state.update { + it.copy( + addedServers = serversRepository.getServers().mapToServerItems() + ) + } } } - private suspend fun loadAddedStages() { - val stages = stagesRepository.getStages() - val headerText = context.getString(R.string.added_servers) - val serverItems = mapToAddedItems(headerText, stages) - withContext(Dispatchers.Main) { - _state.value = _state.value?.copy(addedStages = serverItems) + private fun addServer(name: String, url: String) { + val server = DebugServer(name = name, url = url, isDefault = false) + viewModelScope.safeLaunch { + serversRepository.addServer(server) + _state.update { + it.copy( + addedServers = serversRepository.getServers().mapToServerItems() + ) + } } } - private suspend fun mapToPreinstalledItems( - header: String, - servers: List - ): List { - val selectedServer = serversRepository.getSelectedServer() - val selectedStage = stagesRepository.getSelectedStage() + private fun updateServerData(id: Int, name: String, url: String) { + val serverForUpdate = _state.value.addedServers + .find { it.server.id == id } + ?.server - val items = servers.map { debugServer -> - val isSelected = when (debugServer) { - is DebugServer -> selectedServer.url == debugServer.url - is DebugStage -> selectedStage == debugServer - else -> false + serverForUpdate?.let { + val updatedServer = serverForUpdate.copy(name = name, url = url) + viewModelScope.safeLaunch { + serversRepository.updateServer(updatedServer) + _state.update { + it.copy(addedServers = serversRepository.getServers().mapToServerItems()) + } } - DebugServerItems.PreinstalledServer(debugServer, isSelected) } - - return listOf(DebugServerItems.Header(header)).plus(items) } - private suspend fun mapToAddedItems( - header: String, - servers: List - ): List { - if (servers.isEmpty()) return emptyList() - val selectedServer = serversRepository.getSelectedServer() - val selectedStage = stagesRepository.getSelectedStage() + fun onEditServerClicked(debugServer: DebugServer) { + _state.update { + it.copy( + serverDialogState = ServerDialogState( + editableServerId = debugServer.id, + serverName = debugServer.name, + serverUrl = debugServer.url, + show = true + ) + ) + } + } - val items = servers.map { debugServer -> - val isSelected = when (debugServer) { - is DebugServer -> selectedServer.url == debugServer.url - is DebugStage -> selectedStage == debugServer - else -> false + fun onServerClicked(debugServer: DebugServer) { + viewModelScope.launch { + val selectedServer = serversRepository.getSelectedServer() + if (debugServer != selectedServer) { + getPlugin().pushEvent(ServerSelectedEvent(debugServer)) + serversRepository.saveSelectedServer(debugServer) + loadServers() } - DebugServerItems.AddedServer(debugServer, isSelected) } + } - return listOf(DebugServerItems.Header(header)).plus(items) + fun onStageClicked(debugStage: DebugStage) { + viewModelScope.launch { + val selectedStage = stagesRepository.getSelectedStage() + if (debugStage != selectedStage) { + stagesRepository.saveSelectedStage(debugStage) + getPlugin().pushEvent(StageSelectedEvent(debugStage)) + loadServers() + } + } } - private suspend fun getSelectedServer(): DebugServer { - return serversRepository.getSelectedServer() + private suspend fun List.mapToServerItems(): List { + val selectedServer = serversRepository.getSelectedServer() + return map { debugServer -> + val isSelected = debugServer == selectedServer + ServerItemData(debugServer, isSelected) + } } + private fun List.mapToStageItems(): List { + val selectedStage = stagesRepository.getSelectedStage() + return map { debugStage -> + val isSelected = debugStage == selectedStage + StageItemData(debugStage, isSelected) + } + } } diff --git a/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/ui/ServersViewState.kt b/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/ui/ServersViewState.kt index aa3b5354..00dd4336 100644 --- a/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/ui/ServersViewState.kt +++ b/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/ui/ServersViewState.kt @@ -1,10 +1,24 @@ package com.redmadrobot.debug.plugin.servers.ui -import com.redmadrobot.debug.plugin.servers.ui.item.DebugServerItems +import com.redmadrobot.debug.plugin.servers.data.model.DebugServer +import com.redmadrobot.debug.plugin.servers.data.model.DebugStage internal data class ServersViewState( - val preInstalledServers: List = emptyList(), - val addedServers: List = emptyList(), - val preinstalledStages: List = emptyList(), - val addedStages: List = emptyList(), + val preInstalledServers: List = emptyList(), + val addedServers: List = emptyList(), + val preinstalledStages: List = emptyList(), + val addedStages: List = emptyList(), + val serverDialogState: ServerDialogState = ServerDialogState() ) + +internal data class ServerDialogState( + val show: Boolean = false, + val serverName: String = "", + val serverUrl: String = "", + val editableServerId: Int? = null, + val nameError: Int? = null, + val urlError: Int? = null, +) + +internal data class ServerItemData(val server: DebugServer, val isSelected: Boolean) +internal data class StageItemData(val server: DebugStage, val isSelected: Boolean) diff --git a/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/ui/item/DebugServerItems.kt b/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/ui/item/DebugServerItems.kt index 534e6aea..e69de29b 100644 --- a/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/ui/item/DebugServerItems.kt +++ b/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/ui/item/DebugServerItems.kt @@ -1,18 +0,0 @@ -package com.redmadrobot.debug.plugin.servers.ui.item - -import com.redmadrobot.debug.plugin.servers.data.model.DebugServerData - -internal sealed class DebugServerItems { - - data class Header(val header: String) : DebugServerItems() - - data class PreinstalledServer( - var debugServer: DebugServerData, - var isSelected: Boolean - ) : DebugServerItems() - - data class AddedServer( - var debugServer: DebugServerData, - var isSelected: Boolean - ) : DebugServerItems() -} diff --git a/plugins/servers/src/main/kotlin/com/redmadrobot/debug/servers/ui/ServersScreen.kt b/plugins/servers/src/main/kotlin/com/redmadrobot/debug/servers/ui/ServersScreen.kt new file mode 100644 index 00000000..fd02d60e --- /dev/null +++ b/plugins/servers/src/main/kotlin/com/redmadrobot/debug/servers/ui/ServersScreen.kt @@ -0,0 +1,374 @@ +package com.redmadrobot.debug.servers.ui + +import android.annotation.SuppressLint +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.defaultMinSize +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Button +import androidx.compose.material.Card +import androidx.compose.material.ExtendedFloatingActionButton +import androidx.compose.material.Icon +import androidx.compose.material.IconButton +import androidx.compose.material.MaterialTheme +import androidx.compose.material.OutlinedTextField +import androidx.compose.material.Scaffold +import androidx.compose.material.Surface +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.compose.ui.window.Dialog +import androidx.compose.ui.window.DialogProperties +import androidx.lifecycle.Lifecycle +import com.redmadrobot.debug.core.extension.OnLifecycleEvent +import com.redmadrobot.debug.core.extension.getPlugin +import com.redmadrobot.debug.core.extension.provideViewModel +import com.redmadrobot.debug.plugin.servers.R +import com.redmadrobot.debug.plugin.servers.ServersPlugin +import com.redmadrobot.debug.plugin.servers.ServersPluginContainer +import com.redmadrobot.debug.plugin.servers.data.model.DebugServer +import com.redmadrobot.debug.plugin.servers.data.model.DebugStage +import com.redmadrobot.debug.plugin.servers.ui.ServerDialogState +import com.redmadrobot.debug.plugin.servers.ui.ServersViewModel +import com.redmadrobot.debug.plugin.servers.ui.ServersViewState + +@SuppressLint("UnusedMaterialScaffoldPaddingParameter") +@Composable +internal fun ServersScreen( + viewModel: ServersViewModel = provideViewModel { + getPlugin() + .getContainer() + .createServersViewModel() + }, + isEditMode: Boolean +) { + val state by viewModel.state.collectAsState() + Scaffold( + floatingActionButton = { + if (isEditMode) { + ExtendedFloatingActionButton( + onClick = { viewModel.onAddClicked() }, + text = { Text("Add") }, + icon = { + Icon( + painterResource(R.drawable.icon_add_server), + contentDescription = null + ) + } + ) + } + } + ) { + ServersScreenLayout( + state = state, + isEditMode = isEditMode, + onServerClick = if (!isEditMode) viewModel::onServerClicked else viewModel::onEditServerClicked, + onStageClick = viewModel::onStageClicked, + onServerDeleteClick = viewModel::onRemoveServerClicked + ) + } + if (state.serverDialogState.show) { + ServerDialog( + state = state.serverDialogState, + onNameChange = viewModel::onServerNameChanged, + onUrlChange = viewModel::onServerUrlChanged, + onDismiss = viewModel::dismissDialogs, + onSaveClick = viewModel::onSaveServerClicked, + ) + } + OnLifecycleEvent { event -> + if (event == Lifecycle.Event.ON_RESUME) { + viewModel.loadServers() + } + } +} + +@Composable +private fun ServersScreenLayout( + state: ServersViewState, + isEditMode: Boolean, + onServerClick: (DebugServer) -> Unit, + onServerDeleteClick: (DebugServer) -> Unit, + onStageClick: (DebugStage) -> Unit, +) { + LazyColumn( + modifier = Modifier + .fillMaxSize(), + verticalArrangement = Arrangement.spacedBy(8.dp), + contentPadding = PaddingValues(start = 16.dp, end = 16.dp, bottom = 64.dp), + ) { + if (state.preinstalledStages.isNotEmpty()) { + item { + Text( + stringResource(id = R.string.pre_installed_stages).uppercase(), + modifier = Modifier + .fillParentMaxWidth() + .padding(vertical = 16.dp), + fontSize = 16.sp + ) + } + items(state.preinstalledStages) { item -> + StageItem( + stage = item.server, + selected = item.isSelected && !isEditMode, + showDelete = false, + onItemClick = onStageClick.takeIf { !isEditMode }, + ) + } + } + + if (state.preInstalledServers.isNotEmpty()) { + item { + Text( + stringResource(id = R.string.pre_installed_servers).uppercase(), + modifier = Modifier + .fillParentMaxWidth() + .padding(vertical = 16.dp), + fontSize = 16.sp + ) + } + items(state.preInstalledServers) { item -> + ServerItem( + server = item.server, + selected = item.isSelected && !isEditMode, + showDelete = false, + onItemClick = onServerClick.takeIf { !isEditMode }, + onDeleteClick = onServerDeleteClick, + ) + } + } + if (state.addedStages.isNotEmpty()) { + item { + Text( + stringResource(id = R.string.added_stages).uppercase(), + modifier = Modifier + .fillParentMaxWidth() + .padding(vertical = 16.dp), + fontSize = 16.sp + ) + } + items(state.addedStages) { item -> + StageItem( + stage = item.server, + selected = item.isSelected && !isEditMode, + showDelete = isEditMode, + onItemClick = onStageClick, + ) + } + } + + if (state.addedServers.isNotEmpty()) { + item { + Text( + stringResource(id = R.string.added_servers).uppercase(), + modifier = Modifier + .fillParentMaxWidth() + .padding(vertical = 16.dp), + fontSize = 16.sp + ) + } + items(state.addedServers) { item -> + ServerItem( + server = item.server, + selected = item.isSelected && !isEditMode, + showDelete = isEditMode, + onItemClick = onServerClick, + onDeleteClick = onServerDeleteClick, + ) + } + } + } +} + +@Composable +private fun ServerItem( + server: DebugServer, + selected: Boolean, + showDelete: Boolean, + onDeleteClick: (DebugServer) -> Unit, + onItemClick: ((DebugServer) -> Unit)? = null, +) { + Card( + modifier = Modifier + .fillMaxWidth() + .defaultMinSize(minHeight = 56.dp) + .clickable { onItemClick?.invoke(server) } + ) { + Column( + modifier = Modifier + .fillMaxSize() + .padding(16.dp) + ) { + Row( + modifier = Modifier.fillMaxSize(), + horizontalArrangement = Arrangement.spacedBy(32.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + painter = painterResource(R.drawable.icon_router), + contentDescription = null, + tint = MaterialTheme.colors.primary + ) + Text(server.name, fontWeight = FontWeight.SemiBold) + Box(modifier = Modifier.weight(1f)) { + if (selected) { + Icon( + painterResource(R.drawable.icon_selected), + contentDescription = null, + modifier = Modifier.align(Alignment.CenterEnd) + ) + } else if (showDelete) { + IconButton( + modifier = Modifier.align(Alignment.CenterEnd), + onClick = { onDeleteClick(server) }, + ) { + Icon( + painterResource(R.drawable.icon_delete), + contentDescription = null, + tint = Color.Red + ) + } + } + } + } + Spacer(modifier = Modifier.height(8.dp)) + Text(text = "url: ${server.url}", color = Color.Gray, fontSize = 12.sp) + } + } +} + +@Composable +private fun StageItem( + stage: DebugStage, + showDelete: Boolean, + selected: Boolean, + onItemClick: ((DebugStage) -> Unit)? = null, + onDeleteClick: (DebugStage) -> Unit = {}, +) { + Card( + modifier = Modifier + .fillMaxWidth() + .defaultMinSize(minHeight = 56.dp) + .clickable { onItemClick?.invoke(stage) } + ) { + Column( + modifier = Modifier + .fillMaxSize() + .padding(16.dp) + ) { + Row( + modifier = Modifier.fillMaxSize(), + horizontalArrangement = Arrangement.spacedBy(32.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + painter = painterResource(R.drawable.icon_stage), + contentDescription = null, + tint = MaterialTheme.colors.primary + ) + Text(stage.name, fontWeight = FontWeight.SemiBold) + Box(modifier = Modifier.weight(1f)) { + if (selected) { + Icon( + painterResource(R.drawable.icon_selected), + contentDescription = null, + modifier = Modifier.align(Alignment.CenterEnd) + ) + } else if (showDelete) { + IconButton( + modifier = Modifier.align(Alignment.CenterEnd), + onClick = { onDeleteClick(stage) }, + ) { + Icon( + painterResource(R.drawable.icon_delete), + contentDescription = null, + tint = Color.Red + ) + } + } + } + } + Spacer(modifier = Modifier.height(8.dp)) + Column { + stage.hosts.forEach { (key, value) -> + Text(text = "$key: $value", color = Color.Gray, fontSize = 12.sp) + } + } + } + } +} + +@OptIn(ExperimentalComposeUiApi::class) +@Composable +private fun ServerDialog( + state: ServerDialogState, + onNameChange: (String) -> Unit, + onUrlChange: (String) -> Unit, + onDismiss: () -> Unit, + onSaveClick: () -> Unit, +) { + + Dialog( + onDismissRequest = onDismiss, + properties = DialogProperties(usePlatformDefaultWidth = false) + ) { + Surface( + shape = RoundedCornerShape(16.dp), + color = Color.White, + modifier = Modifier.wrapContentHeight() + ) { + Column( + modifier = Modifier.padding(16.dp) + ) { + OutlinedTextField( + value = state.serverName, + onValueChange = onNameChange, + label = { Text(stringResource(R.string.name)) }, + isError = state.nameError != null + ) + if (state.nameError != null) { + Text(text = stringResource(id = state.nameError), color = Color.Red) + } + Spacer(modifier = Modifier.height(16.dp)) + OutlinedTextField( + value = state.serverUrl, + onValueChange = onUrlChange, + label = { Text(stringResource(R.string.server_host_hint)) }, + isError = state.urlError != null + ) + if (state.urlError != null) { + Text(text = stringResource(id = state.urlError), color = Color.Red) + } + Button( + onClick = onSaveClick, + modifier = Modifier.align(Alignment.End) + ) { + Text(stringResource(R.string.save_server).uppercase()) + } + } + } + + } +} From 57dabbd049bd92bc53efa1c58cc9691702d3ecc2 Mon Sep 17 00:00:00 2001 From: Roman Choryev Date: Mon, 16 Jan 2023 20:53:14 +0400 Subject: [PATCH 17/36] Sample: Update sample project --- .../debug_data/DebugServersProvider.kt | 39 +++++++++++++++++-- .../debug_sample/network/ApiFactory.kt | 9 ++++- 2 files changed, 42 insertions(+), 6 deletions(-) diff --git a/sample/src/debug/kotlin/com/redmadrobot/debug_sample/debug_data/DebugServersProvider.kt b/sample/src/debug/kotlin/com/redmadrobot/debug_sample/debug_data/DebugServersProvider.kt index b4c65441..f493ec7c 100644 --- a/sample/src/debug/kotlin/com/redmadrobot/debug_sample/debug_data/DebugServersProvider.kt +++ b/sample/src/debug/kotlin/com/redmadrobot/debug_sample/debug_data/DebugServersProvider.kt @@ -2,13 +2,44 @@ package com.redmadrobot.debug_sample.debug_data import com.redmadrobot.debug.core.data.DebugDataProvider import com.redmadrobot.debug.plugin.servers.data.model.DebugServer +import com.redmadrobot.debug.plugin.servers.data.model.DebugServerData +import com.redmadrobot.debug.plugin.servers.data.model.DebugStage -class DebugServersProvider : DebugDataProvider> { +class DebugServersProvider { - override fun provideData(): List { + fun provideData(): List { return listOf( - DebugServer(name = "debug 1", url = "https://testserver1.com", isDefault = true), - DebugServer(name = "debug 2", url = "https://testserver2.com") + DebugServer( + name = "debug 1", + url = "https://testserver1.com", + isDefault = true + ), + DebugServer( + name = "debug 2", url = "https://testserver2.com" + ), + DebugServer( + name = "debug 3", url = "https://testserver3.com" + ), + DebugServer( + name = "debug 4", url = "https://testserver4.com" + ), + DebugStage( + name = "debug stage 1", + hosts = mapOf( + "main" to "https://testserver1main.com", + "s3" to "https://testserver1s3.com", + "wss" to "https://testserver1wss.com" + ), + isDefault = true + ), + DebugStage( + name = "debug stage 2", + hosts = mapOf( + "main" to "https://testserver2main.com", + "s3" to "https://testserver2s3.com", + "wss" to "https://testserver2wss.com" + ) + ), ) } } diff --git a/sample/src/main/kotlin/com/redmadrobot/debug_sample/network/ApiFactory.kt b/sample/src/main/kotlin/com/redmadrobot/debug_sample/network/ApiFactory.kt index 83c50645..b127dc7f 100644 --- a/sample/src/main/kotlin/com/redmadrobot/debug_sample/network/ApiFactory.kt +++ b/sample/src/main/kotlin/com/redmadrobot/debug_sample/network/ApiFactory.kt @@ -1,10 +1,14 @@ package com.redmadrobot.debug_sample.network -import com.redmadrobot.debug.plugin.servers.util.DebugServerInterceptor +import com.redmadrobot.debug.plugin.servers.interceptor.DebugStageInterceptor import okhttp3.Interceptor import okhttp3.OkHttpClient import okhttp3.Response import retrofit2.Retrofit +import java.net.HttpURLConnection +import java.net.URL +import java.net.URLConnection +import java.net.URLStreamHandler object ApiFactory { @@ -18,9 +22,10 @@ object ApiFactory { } private fun getSampleClient(onCalled: (String) -> Unit): OkHttpClient { + return OkHttpClient.Builder() .addInterceptor( - DebugServerInterceptor().modifyRequest { request, server -> + DebugStageInterceptor("s3").modifyRequest { request, server -> if (server.name == "Test") { request.newBuilder() .addHeader("Authorization", "testToken") From 9435e324b44502fb1a92fd3a007f9459182d4639 Mon Sep 17 00:00:00 2001 From: Roman Choryev Date: Mon, 16 Jan 2023 21:02:10 +0400 Subject: [PATCH 18/36] Servers: Add methods to get the selected stage and the default stage --- .../debug/plugin/servers/ServersPlugin.kt | 15 +++++++++++++++ .../plugin/servers/data/DebugStageRepository.kt | 14 +------------- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/ServersPlugin.kt b/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/ServersPlugin.kt index e08e79a4..5e90472a 100644 --- a/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/ServersPlugin.kt +++ b/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/ServersPlugin.kt @@ -10,6 +10,7 @@ import com.redmadrobot.debug.core.plugin.Plugin import com.redmadrobot.debug.core.internal.PluginDependencyContainer import com.redmadrobot.debug.plugin.servers.data.model.DebugServer import com.redmadrobot.debug.plugin.servers.data.model.DebugServerData +import com.redmadrobot.debug.plugin.servers.data.model.DebugStage import com.redmadrobot.debug.plugin.servers.databinding.FragmentContainerServersBinding import com.redmadrobot.debug.plugin.servers.ui.ServersFragment import com.redmadrobot.debug.servers.ui.ServersScreen @@ -42,6 +43,20 @@ public class ServersPlugin( .serversRepository .getDefault() } + + public fun getSelectedStage(): DebugStage { + return getPlugin() + .getContainer() + .stagesRepository + .getSelectedStage() + } + + public fun getDefaultStage(): DebugStage { + return getPlugin() + .getContainer() + .stagesRepository + .getDefault() + } } public constructor(preInstalledServers: DebugDataProvider>) : this( diff --git a/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/data/DebugStageRepository.kt b/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/data/DebugStageRepository.kt index b3c489c8..20161c41 100644 --- a/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/data/DebugStageRepository.kt +++ b/plugins/servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/data/DebugStageRepository.kt @@ -48,28 +48,16 @@ internal class DebugStageRepository( } } - private fun getDefault(): DebugStage { + fun getDefault(): DebugStage { return preInstalledStages.first { it.isDefault } } - suspend fun addStage(server: DebugStage) { - withContext(Dispatchers.IO) { - debugStagesDao.insert(server) - } - } - suspend fun getStages(): List { return withContext(Dispatchers.IO) { debugStagesDao.getAll() } } - suspend fun removeStage(server: DebugStage) { - withContext(Dispatchers.IO) { - debugStagesDao.remove(server) - } - } - suspend fun updateStage(server: DebugStage) { withContext(Dispatchers.IO) { debugStagesDao.update(server) From 3c45a475a3065d1ad6074f96fc779001bc76a848 Mon Sep 17 00:00:00 2001 From: Roman Choryev Date: Sat, 21 Jan 2023 21:41:04 +0400 Subject: [PATCH 19/36] AppSettings: Migrate App-settings plugin to the Jetpack Compose --- .../ui/ApplicationSettingsScreen.kt | 176 ++++++++++++++++++ .../appsettings/ui/SettingItemUiModel.kt | 22 +++ .../plugin/appsettings/AppSettingsPlugin.kt | 24 +-- .../data/AppSettingsRepositoryImpl.kt | 5 +- .../ui/ApplicationSettingsFragment.kt | 48 ----- .../ui/ApplicationSettingsViewModel.kt | 34 ++-- .../appsettings/ui/item/AppSettingItems.kt | 97 ---------- .../main/res/layout/fragment_app_settings.xml | 17 -- .../fragment_container_app_settings.xml | 6 - .../src/main/res/layout/item_header.xml | 21 --- .../res/layout/item_preference_boolean.xml | 43 ----- .../layout/item_preference_unsupported.xml | 33 ---- .../main/res/layout/item_preference_value.xml | 66 ------- .../src/main/res/values/strings.xml | 1 + 14 files changed, 222 insertions(+), 371 deletions(-) create mode 100644 plugins/app-settings/src/main/kotlin/com/redmadrobot/debug/appsettings/ui/ApplicationSettingsScreen.kt create mode 100644 plugins/app-settings/src/main/kotlin/com/redmadrobot/debug/appsettings/ui/SettingItemUiModel.kt delete mode 100644 plugins/app-settings/src/main/kotlin/com/redmadrobot/debug/plugin/appsettings/ui/ApplicationSettingsFragment.kt delete mode 100644 plugins/app-settings/src/main/kotlin/com/redmadrobot/debug/plugin/appsettings/ui/item/AppSettingItems.kt delete mode 100644 plugins/app-settings/src/main/res/layout/fragment_app_settings.xml delete mode 100644 plugins/app-settings/src/main/res/layout/fragment_container_app_settings.xml delete mode 100644 plugins/app-settings/src/main/res/layout/item_header.xml delete mode 100644 plugins/app-settings/src/main/res/layout/item_preference_boolean.xml delete mode 100644 plugins/app-settings/src/main/res/layout/item_preference_unsupported.xml delete mode 100644 plugins/app-settings/src/main/res/layout/item_preference_value.xml diff --git a/plugins/app-settings/src/main/kotlin/com/redmadrobot/debug/appsettings/ui/ApplicationSettingsScreen.kt b/plugins/app-settings/src/main/kotlin/com/redmadrobot/debug/appsettings/ui/ApplicationSettingsScreen.kt new file mode 100644 index 00000000..59f71a15 --- /dev/null +++ b/plugins/app-settings/src/main/kotlin/com/redmadrobot/debug/appsettings/ui/ApplicationSettingsScreen.kt @@ -0,0 +1,176 @@ +package com.redmadrobot.debug.appsettings.ui + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material.Button +import androidx.compose.material.Card +import androidx.compose.material.OutlinedTextField +import androidx.compose.material.Switch +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.onFocusChanged +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalFocusManager +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.lifecycle.Lifecycle +import com.redmadrobot.debug.core.extension.OnLifecycleEvent +import com.redmadrobot.debug.core.extension.getPlugin +import com.redmadrobot.debug.core.extension.provideViewModel +import com.redmadrobot.debug.plugin.appsettings.AppSettingsPlugin +import com.redmadrobot.debug.plugin.appsettings.AppSettingsPluginContainer +import com.redmadrobot.debug.plugin.appsettings.R +import com.redmadrobot.debug.plugin.appsettings.ui.ApplicationSettingsViewModel + +@Composable +internal fun ApplicationSettingsScreen( + viewModel: ApplicationSettingsViewModel = provideViewModel { + getPlugin() + .getContainer() + .createApplicationSettingsViewModel() + } +) { + val state by viewModel.state.collectAsState() + ApplicationSettingsLayout(state, onValueChanged = viewModel::updateSetting) + OnLifecycleEvent { event -> + if (event == Lifecycle.Event.ON_RESUME) { + viewModel.loadSettings() + } + } +} + +@Composable +private fun ApplicationSettingsLayout( + state: List, + onValueChanged: (String, Any) -> Unit +) { + LazyColumn( + modifier = Modifier.fillMaxSize(), + contentPadding = PaddingValues(horizontal = 16.dp, vertical = 16.dp), + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + items(state) { itemUiModel -> + when (itemUiModel) { + is SettingItemUiModel.Header -> { + Text( + text = itemUiModel.header, + modifier = Modifier.padding(vertical = 16.dp) + ) + } + + is SettingItemUiModel.ValueItem -> { + ValueItem(itemUiModel, onValueChanged) + } + + is SettingItemUiModel.BooleanItem -> { + BooleanItem(itemUiModel, onValueChanged) + } + } + } + } +} + +@Composable +private fun ValueItem( + itemUiModel: SettingItemUiModel.ValueItem, + onValueChanged: (String, Any) -> Unit +) { + var value by remember { mutableStateOf(itemUiModel.value.toString()) } + var hasFocus by remember { mutableStateOf(false) } + var error by remember(value) { mutableStateOf("") } + val focusManager = LocalFocusManager.current + + Card(modifier = Modifier.fillMaxWidth()) { + Column(modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp)) { + Row(horizontalArrangement = Arrangement.SpaceBetween) { + Text( + text = itemUiModel.key, + modifier = Modifier + .fillMaxWidth() + .weight(1f), + fontWeight = FontWeight.SemiBold, + ) + if (hasFocus) { + Button( + onClick = { + try { + val newValue = itemUiModel.castToNeededType(value) + onValueChanged(itemUiModel.key, newValue) + focusManager.clearFocus(force = true) + } catch (e: Throwable) { + error = "Wrong data type" + } + } + ) { + Text(text = stringResource(id = R.string.save)) + } + } + } + Spacer(modifier = Modifier.height(8.dp)) + Text( + text = itemUiModel.value?.let { it::class.java.simpleName } + ?: stringResource(id = R.string.unknown_type), + color = Color.Gray, + fontSize = 12.sp + ) + Spacer(modifier = Modifier.height(8.dp)) + OutlinedTextField( + modifier = Modifier + .fillMaxWidth() + .onFocusChanged { hasFocus = it.hasFocus }, + value = value, + onValueChange = { value = it }, + isError = error.isNotEmpty(), + label = { Text(text = stringResource(id = R.string.value)) }, + ) + if (error.isNotEmpty()) { + Text(text = error, color = Color.Red) + } + } + } +} + +@Composable +private fun BooleanItem( + itemUiModel: SettingItemUiModel.BooleanItem, + onValueChanged: (String, Boolean) -> Unit +) { + Card(modifier = Modifier.fillMaxWidth()) { + Column(modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp)) { + Text( + text = itemUiModel.key, + modifier = Modifier + .fillMaxWidth(), + fontWeight = FontWeight.SemiBold, + ) + Spacer(modifier = Modifier.height(8.dp)) + Text( + text = "Boolean", + color = Color.Gray, + fontSize = 12.sp + ) + Spacer(modifier = Modifier.height(8.dp)) + Switch( + checked = itemUiModel.value, + onCheckedChange = { onValueChanged(itemUiModel.key, it) } + ) + } + } +} diff --git a/plugins/app-settings/src/main/kotlin/com/redmadrobot/debug/appsettings/ui/SettingItemUiModel.kt b/plugins/app-settings/src/main/kotlin/com/redmadrobot/debug/appsettings/ui/SettingItemUiModel.kt new file mode 100644 index 00000000..5d0a369c --- /dev/null +++ b/plugins/app-settings/src/main/kotlin/com/redmadrobot/debug/appsettings/ui/SettingItemUiModel.kt @@ -0,0 +1,22 @@ +package com.redmadrobot.debug.appsettings.ui + +internal sealed class SettingItemUiModel { + + data class Header(val header: String) : SettingItemUiModel() + data class ValueItem( + val key: String, + var value: Any?, + ) : SettingItemUiModel() { + fun castToNeededType(newValue: String): Any { + return when (value) { + is Long -> newValue.toLong() + is String -> newValue + is Float -> newValue.toFloat() + is Int -> newValue.toInt() + else -> error("Unexpected type") + } + } + } + + data class BooleanItem(val key: String, var value: Boolean) : SettingItemUiModel() +} diff --git a/plugins/app-settings/src/main/kotlin/com/redmadrobot/debug/plugin/appsettings/AppSettingsPlugin.kt b/plugins/app-settings/src/main/kotlin/com/redmadrobot/debug/plugin/appsettings/AppSettingsPlugin.kt index 87d86dbf..0da3540d 100644 --- a/plugins/app-settings/src/main/kotlin/com/redmadrobot/debug/plugin/appsettings/AppSettingsPlugin.kt +++ b/plugins/app-settings/src/main/kotlin/com/redmadrobot/debug/plugin/appsettings/AppSettingsPlugin.kt @@ -1,17 +1,12 @@ package com.redmadrobot.debug.plugin.appsettings import android.content.SharedPreferences -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.viewinterop.AndroidViewBinding -import androidx.fragment.app.Fragment -import com.redmadrobot.debug.plugin.appsettings.data.DefaultSharedPreferences -import com.redmadrobot.debug.plugin.appsettings.ui.ApplicationSettingsFragment -import com.redmadrobot.debug.core.internal.CommonContainer +import com.redmadrobot.debug.appsettings.ui.ApplicationSettingsScreen import com.redmadrobot.debug.core.plugin.Plugin +import com.redmadrobot.debug.core.internal.CommonContainer import com.redmadrobot.debug.core.internal.PluginDependencyContainer +import com.redmadrobot.debug.plugin.appsettings.data.DefaultSharedPreferences public class AppSettingsPlugin( private val sharedPreferences: List = listOf(DefaultSharedPreferences()) @@ -27,19 +22,8 @@ public class AppSettingsPlugin( return AppSettingsPluginContainer(sharedPreferences) } - @Deprecated( - "You should't use fragments for you plugins. Please use Jetpack Compose", - replaceWith = ReplaceWith("content()", "com.redmadrobot.debug.core.plugin.Plugin") - ) - override fun getFragment(): Fragment { - return ApplicationSettingsFragment() - } - @Composable override fun content() { - AndroidViewBinding( - FragmentContainerAppSettingsBinding::inflate, - modifier = Modifier.verticalScroll(rememberScrollState()) - ) + ApplicationSettingsScreen() } } diff --git a/plugins/app-settings/src/main/kotlin/com/redmadrobot/debug/plugin/appsettings/data/AppSettingsRepositoryImpl.kt b/plugins/app-settings/src/main/kotlin/com/redmadrobot/debug/plugin/appsettings/data/AppSettingsRepositoryImpl.kt index 19ab546a..4ace4fbd 100644 --- a/plugins/app-settings/src/main/kotlin/com/redmadrobot/debug/plugin/appsettings/data/AppSettingsRepositoryImpl.kt +++ b/plugins/app-settings/src/main/kotlin/com/redmadrobot/debug/plugin/appsettings/data/AppSettingsRepositoryImpl.kt @@ -1,6 +1,7 @@ package com.redmadrobot.debug.plugin.appsettings.data import android.content.SharedPreferences +import androidx.core.content.edit internal class AppSettingsRepositoryImpl( private val sharedPreferencesList: List @@ -12,7 +13,7 @@ internal class AppSettingsRepositoryImpl( override fun updateSetting(key: String, value: Any) { val sharedPreferences = sharedPreferencesList.find { it.contains(key) } - sharedPreferences?.edit()?.apply { + sharedPreferences?.edit { when (value) { is Boolean -> putBoolean(key, value) is Int -> putInt(key, value) @@ -20,6 +21,6 @@ internal class AppSettingsRepositoryImpl( is Float -> putFloat(key, value) is String -> putString(key, value) } - }?.apply() + } } } diff --git a/plugins/app-settings/src/main/kotlin/com/redmadrobot/debug/plugin/appsettings/ui/ApplicationSettingsFragment.kt b/plugins/app-settings/src/main/kotlin/com/redmadrobot/debug/plugin/appsettings/ui/ApplicationSettingsFragment.kt deleted file mode 100644 index 4a420118..00000000 --- a/plugins/app-settings/src/main/kotlin/com/redmadrobot/debug/plugin/appsettings/ui/ApplicationSettingsFragment.kt +++ /dev/null @@ -1,48 +0,0 @@ -package com.redmadrobot.debug.plugin.appsettings.ui - -import android.os.Bundle -import android.view.View -import androidx.recyclerview.widget.LinearLayoutManager -import com.redmadrobot.debug.plugin.appsettings.R -import com.redmadrobot.debug.plugin.appsettings.databinding.FragmentAppSettingsBinding -import com.redmadrobot.debug.plugin.appsettings.AppSettingsPlugin -import com.redmadrobot.debug.plugin.appsettings.AppSettingsPluginContainer -import com.redmadrobot.debug.plugin.appsettings.ui.item.AppSettingItems -import com.redmadrobot.debug.common.base.PluginFragment -import com.redmadrobot.debug.common.extension.observe -import com.redmadrobot.debug.common.extension.obtainViewModel -import com.redmadrobot.debug.core.extension.getPlugin -import com.redmadrobot.itemsadapter.itemsAdapter - -internal class ApplicationSettingsFragment : PluginFragment(R.layout.fragment_app_settings) { - - private var binding: FragmentAppSettingsBinding? = null - - private val settingsViewModel by lazy { - obtainViewModel { - getPlugin() - .getContainer() - .createApplicationSettingsViewModel() - } - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - observe(settingsViewModel.settingsLiveData, ::setSettingList) - binding = FragmentAppSettingsBinding.bind(view).also { - it.setViews() - } - settingsViewModel.loadSettings() - } - - private fun FragmentAppSettingsBinding.setViews() { - appSettings.layoutManager = LinearLayoutManager(context) - } - - private fun setSettingList(settings: List) { - val binding = binding ?: return - binding.appSettings.adapter = itemsAdapter(settings) { item -> - item.getItem(this) - } - } -} diff --git a/plugins/app-settings/src/main/kotlin/com/redmadrobot/debug/plugin/appsettings/ui/ApplicationSettingsViewModel.kt b/plugins/app-settings/src/main/kotlin/com/redmadrobot/debug/plugin/appsettings/ui/ApplicationSettingsViewModel.kt index 140b0e26..6b3dd33d 100644 --- a/plugins/app-settings/src/main/kotlin/com/redmadrobot/debug/plugin/appsettings/ui/ApplicationSettingsViewModel.kt +++ b/plugins/app-settings/src/main/kotlin/com/redmadrobot/debug/plugin/appsettings/ui/ApplicationSettingsViewModel.kt @@ -2,49 +2,47 @@ package com.redmadrobot.debug.plugin.appsettings.ui import android.content.SharedPreferences import androidx.lifecycle.MutableLiveData +import com.redmadrobot.debug.appsettings.ui.SettingItemUiModel import com.redmadrobot.debug.plugin.appsettings.data.AppSettingsRepository -import com.redmadrobot.debug.plugin.appsettings.ui.item.AppSettingItems import com.redmadrobot.debug.common.base.PluginViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.update internal class ApplicationSettingsViewModel( private val appSettingsRepository: AppSettingsRepository ) : PluginViewModel() { - val settingsLiveData = MutableLiveData>() + val state = MutableStateFlow>(emptyList()) fun loadSettings() { val settings = appSettingsRepository.getSettings() - val settingItems = mapToItems(settings) - settingsLiveData.value = settingItems + val newSettingItems = mapToItemsNew(settings) + state.update { newSettingItems } } - @Suppress("NewApi") - private fun mapToItems(settings: List): List { - val items = mutableListOf() + fun updateSetting(settingKey: String, newValue: Any) { + appSettingsRepository.updateSetting(settingKey, newValue) + loadSettings() + } + + private fun mapToItemsNew(settings: List): MutableList { + val items = mutableListOf() settings.forEach { sharedPreferences -> /*Settings header*/ items.add( - AppSettingItems.Header(sharedPreferences.toString()) + SettingItemUiModel.Header(sharedPreferences.toString()) ) /*Map SharedPreferences to Items*/ sharedPreferences.all.forEach { (key, value) -> val item = if (value is Boolean) { - AppSettingItems.BooleanValueItem(key, value) { settingKey, newValue -> - updateSetting(settingKey, newValue) - } + SettingItemUiModel.BooleanItem(key, value) } else { - AppSettingItems.ValueItem(key, value) { settingKey, newValue -> - updateSetting(settingKey, newValue) - } + SettingItemUiModel.ValueItem(key, value) } items.add(item) } } return items } - - private fun updateSetting(settingKey: String, newValue: Any) { - appSettingsRepository.updateSetting(settingKey, newValue) - } } diff --git a/plugins/app-settings/src/main/kotlin/com/redmadrobot/debug/plugin/appsettings/ui/item/AppSettingItems.kt b/plugins/app-settings/src/main/kotlin/com/redmadrobot/debug/plugin/appsettings/ui/item/AppSettingItems.kt deleted file mode 100644 index 4746f8e0..00000000 --- a/plugins/app-settings/src/main/kotlin/com/redmadrobot/debug/plugin/appsettings/ui/item/AppSettingItems.kt +++ /dev/null @@ -1,97 +0,0 @@ -package com.redmadrobot.debug.plugin.appsettings.ui.item - -import androidx.core.view.isVisible -import com.redmadrobot.debug.plugin.appsettings.R -import com.redmadrobot.debug.plugin.appsettings.databinding.ItemHeaderBinding -import com.redmadrobot.debug.plugin.appsettings.databinding.ItemPreferenceBooleanBinding -import com.redmadrobot.debug.plugin.appsettings.databinding.ItemPreferenceValueBinding -import com.redmadrobot.itemsadapter.ItemsAdapter -import com.redmadrobot.itemsadapter.bind -import timber.log.Timber - -internal sealed class AppSettingItems { - - abstract fun getItem(bindingContext: ItemsAdapter.BindingContext): ItemsAdapter.Item - - internal class Header(private val header: String) : AppSettingItems() { - override fun getItem(bindingContext: ItemsAdapter.BindingContext): ItemsAdapter.Item { - return bindingContext.bind(R.layout.item_header) { - itemHeaderText.text = header - } - } - } - - class ValueItem( - private val key: String, - var value: Any?, - private val onChanged: (key: String, newValue: Any) -> Unit - ) : AppSettingItems() { - override fun getItem(bindingContext: ItemsAdapter.BindingContext): ItemsAdapter.Item { - return bindingContext.bind(R.layout.item_preference_value) { - settingLabel.text = key - settingType.text = value?.let { it::class.java.simpleName } - settingType.isVisible = value != null - - setSettingValue() - saveValue.setOnClickListener { saveNewValue() } - } - } - - private fun ItemPreferenceValueBinding.setSettingValue() = with(settingValue) { - setText(value.toString()) - clearFocus() - settingValueContainer.error = null - - setOnFocusChangeListener { _, hasFocus -> - saveValue.isVisible = hasFocus && value != null - } - } - - private fun ItemPreferenceValueBinding.saveNewValue() { - val fieldValue = settingValue.text.toString() - try { - val newValue = castToNeededType(fieldValue) - onChanged.invoke(key, newValue) - - value = newValue - setSettingValue() - } catch (e: Exception) { - Timber.e(e) - settingValueContainer.error = root.context.getString(R.string.wrong_type) - } - } - - private fun castToNeededType(newValue: String): Any { - return when (value) { - is Long -> newValue.toLong() - is String -> newValue - is Float -> newValue.toFloat() - is Int -> newValue.toInt() - else -> throw Throwable("Unexpected type") - } - } - } - - class BooleanValueItem( - private val key: String, - var value: Boolean, - private val onChanged: (key: String, newValue: Any) -> Unit - ) : AppSettingItems() { - - override fun getItem(bindingContext: ItemsAdapter.BindingContext): ItemsAdapter.Item { - return bindingContext.bind(R.layout.item_preference_boolean) { - settingLabel.text = key - settingType.text = value::class.java.simpleName - - with(settingSwitch) { - setOnCheckedChangeListener(null) - isChecked = value - setOnCheckedChangeListener { _, isChecked -> - onChanged.invoke(key, isChecked) - value = isChecked - } - } - } - } - } -} diff --git a/plugins/app-settings/src/main/res/layout/fragment_app_settings.xml b/plugins/app-settings/src/main/res/layout/fragment_app_settings.xml deleted file mode 100644 index 7dc0e7a7..00000000 --- a/plugins/app-settings/src/main/res/layout/fragment_app_settings.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - diff --git a/plugins/app-settings/src/main/res/layout/fragment_container_app_settings.xml b/plugins/app-settings/src/main/res/layout/fragment_container_app_settings.xml deleted file mode 100644 index 2695a975..00000000 --- a/plugins/app-settings/src/main/res/layout/fragment_container_app_settings.xml +++ /dev/null @@ -1,6 +0,0 @@ - - diff --git a/plugins/app-settings/src/main/res/layout/item_header.xml b/plugins/app-settings/src/main/res/layout/item_header.xml deleted file mode 100644 index 0c14cf96..00000000 --- a/plugins/app-settings/src/main/res/layout/item_header.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - diff --git a/plugins/app-settings/src/main/res/layout/item_preference_boolean.xml b/plugins/app-settings/src/main/res/layout/item_preference_boolean.xml deleted file mode 100644 index def246fc..00000000 --- a/plugins/app-settings/src/main/res/layout/item_preference_boolean.xml +++ /dev/null @@ -1,43 +0,0 @@ - - - - - - - - - - - - - diff --git a/plugins/app-settings/src/main/res/layout/item_preference_unsupported.xml b/plugins/app-settings/src/main/res/layout/item_preference_unsupported.xml deleted file mode 100644 index 74ed1890..00000000 --- a/plugins/app-settings/src/main/res/layout/item_preference_unsupported.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - - - diff --git a/plugins/app-settings/src/main/res/layout/item_preference_value.xml b/plugins/app-settings/src/main/res/layout/item_preference_value.xml deleted file mode 100644 index a85965a4..00000000 --- a/plugins/app-settings/src/main/res/layout/item_preference_value.xml +++ /dev/null @@ -1,66 +0,0 @@ - - - - - - - - - - - - - - - - -