diff --git a/common/build.gradle.kts b/common/build.gradle.kts index bfcbfaf2..1430970b 100644 --- a/common/build.gradle.kts +++ b/common/build.gradle.kts @@ -44,23 +44,37 @@ android { } dependencies { - api(kotlin("stdlib")) + api(androidx.activity.compose) + api(androidx.compose.animation) + api(androidx.compose.foundation) + api(androidx.compose.material) + api(androidx.compose.runtime.livedata) + api(androidx.compose.ui) + api(androidx.compose.ui.tooling) + api(androidx.compose.ui.tooling.preview) + api(androidx.compose.ui.viewbinding) + api(androidx.constraintlayout.compose) + api(androidx.core) + api(androidx.fragment) + api(androidx.lifecycle.viewmodel.compose) + api(androidx.room) + api(androidx.room.runtime) + api(rmr.flipper) + api(rmr.itemsadapter.viewbinding) + api(stack.accompanist.themeadapter.core) + api(stack.accompanist.themeadapter.material) + api(stack.kotlinx.coroutines.android) api(stack.okhttp) + api(stack.timber) + kapt(androidx.room.compiler) + // legacy api(androidx.appcompat) - api(stack.material) api(androidx.constraintlayout) - api(stack.kotlinx.coroutines.android) - api(androidx.lifecycle.runtime) api(androidx.lifecycle.livedata) api(androidx.lifecycle.livedata.core) + api(androidx.lifecycle.runtime) 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) + api(stack.material) } tasks.register("prepareKotlinBuildScriptModel") {} 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 deleted file mode 100644 index a9a046cc..00000000 --- a/common/src/main/kotlin/com/redmadrobot/debug/common/base/PluginFragment.kt +++ /dev/null @@ -1,22 +0,0 @@ -package com.redmadrobot.debug.common.base - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.appcompat.view.ContextThemeWrapper -import androidx.fragment.app.Fragment -import com.redmadrobot.debug.panel.common.R - -public open class PluginFragment(private val layoutId: Int) : Fragment() { - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View? { - val contextThemeWrapper = ContextThemeWrapper(activity, R.style.DebugPanelTheme) - val localInflater = inflater.cloneInContext(contextThemeWrapper) - return localInflater.inflate(layoutId, container, false) - } -} diff --git a/common/src/main/kotlin/com/redmadrobot/debug/common/base/PluginViewModel.kt b/common/src/main/kotlin/com/redmadrobot/debug/common/base/PluginViewModel.kt deleted file mode 100644 index b0a8e2a0..00000000 --- a/common/src/main/kotlin/com/redmadrobot/debug/common/base/PluginViewModel.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.redmadrobot.debug.common.base - -import androidx.lifecycle.ViewModel - -public open class PluginViewModel : ViewModel() diff --git a/common/src/main/res/layout/item_section_header.xml b/common/src/main/res/layout/item_section_header.xml deleted file mode 100644 index 8e32a980..00000000 --- a/common/src/main/res/layout/item_section_header.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/common/src/main/res/values/dimens.xml b/common/src/main/res/values/dimens.xml deleted file mode 100644 index 166e2e12..00000000 --- a/common/src/main/res/values/dimens.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - 56dp - diff --git a/common/src/main/res/values/styles.xml b/common/src/main/res/values/styles.xml deleted file mode 100644 index 7b624ca6..00000000 --- a/common/src/main/res/values/styles.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - \ No newline at end of file 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/core/src/main/AndroidManifest.xml b/core/src/main/AndroidManifest.xml index d5c6ef5d..eb87ba58 100644 --- a/core/src/main/AndroidManifest.xml +++ b/core/src/main/AndroidManifest.xml @@ -9,6 +9,13 @@ android:taskAffinity="com.redmadrobot.debug_panel.DebugActivity" android:theme="@style/DebugPanelTheme" /> + + ) { instance = DebugPanelInstance(application, plugins) } 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/common/src/main/kotlin/com/redmadrobot/debug/common/extension/CoroutinesExtension.kt b/core/src/main/kotlin/com/redmadrobot/debug/core/extension/CoroutinesExtension.kt similarity index 94% rename from common/src/main/kotlin/com/redmadrobot/debug/common/extension/CoroutinesExtension.kt rename to core/src/main/kotlin/com/redmadrobot/debug/core/extension/CoroutinesExtension.kt index a4a4b86f..1f419d82 100644 --- a/common/src/main/kotlin/com/redmadrobot/debug/common/extension/CoroutinesExtension.kt +++ b/core/src/main/kotlin/com/redmadrobot/debug/core/extension/CoroutinesExtension.kt @@ -1,4 +1,4 @@ -package com.redmadrobot.debug.common.extension +package com.redmadrobot.debug.core.extension import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CoroutineScope diff --git a/common/src/main/kotlin/com/redmadrobot/debug/common/extension/LifecicleExt.kt b/core/src/main/kotlin/com/redmadrobot/debug/core/extension/LifecicleExt.kt similarity index 96% rename from common/src/main/kotlin/com/redmadrobot/debug/common/extension/LifecicleExt.kt rename to core/src/main/kotlin/com/redmadrobot/debug/core/extension/LifecicleExt.kt index 843b5390..bb674f91 100644 --- a/common/src/main/kotlin/com/redmadrobot/debug/common/extension/LifecicleExt.kt +++ b/core/src/main/kotlin/com/redmadrobot/debug/core/extension/LifecicleExt.kt @@ -1,4 +1,4 @@ -package com.redmadrobot.debug.common.extension +package com.redmadrobot.debug.core.extension import androidx.annotation.MainThread import androidx.fragment.app.Fragment diff --git a/core/src/main/kotlin/com/redmadrobot/debug/core/extension/PluginsExt.kt b/core/src/main/kotlin/com/redmadrobot/debug/core/extension/PluginsExt.kt index f6573243..10e7a398 100644 --- a/core/src/main/kotlin/com/redmadrobot/debug/core/extension/PluginsExt.kt +++ b/core/src/main/kotlin/com/redmadrobot/debug/core/extension/PluginsExt.kt @@ -1,8 +1,10 @@ package com.redmadrobot.debug.core.extension +import androidx.fragment.app.Fragment import com.redmadrobot.debug.core.DebugPanelInstance import com.redmadrobot.debug.core.annotation.DebugPanelInternal import com.redmadrobot.debug.core.plugin.Plugin +import com.redmadrobot.debug.core.ui.debugpanel.DebugActivity @PublishedApi internal fun getPlugin(pluginName: String): Plugin { @@ -20,3 +22,7 @@ public inline fun getPlugin(): T { return plugin as T } +@DebugPanelInternal +public fun Fragment.isSettingMode(): Boolean { + return activity?.javaClass == DebugActivity::class.java +} \ No newline at end of file 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 deleted file mode 100644 index 8978ce42..00000000 --- a/core/src/main/kotlin/com/redmadrobot/debug/core/inapp/DebugBottomSheet.kt +++ /dev/null @@ -1,89 +0,0 @@ -package com.redmadrobot.debug.core.inapp - -import android.app.Dialog -import android.os.Bundle -import android.view.ViewGroup -import android.widget.FrameLayout -import androidx.appcompat.view.ContextThemeWrapper -import androidx.fragment.app.FragmentManager -import com.google.android.material.bottomsheet.BottomSheetBehavior -import com.google.android.material.bottomsheet.BottomSheetDialog -import com.google.android.material.bottomsheet.BottomSheetDialogFragment -import com.google.android.material.tabs.TabLayoutMediator -import com.redmadrobot.debug.core.extension.getAllPlugins -import com.redmadrobot.debug.core.databinding.BottomSheetDebugPanelBinding -import com.redmadrobot.debug.panel.common.R as CommonR - -internal class DebugBottomSheet : BottomSheetDialogFragment() { - - companion object { - private const val TAG = "DebugBottomSheet" - - fun show(fragmentManager: FragmentManager) { - if (fragmentManager.findFragmentByTag(TAG) == null) { - DebugBottomSheet().show(fragmentManager, TAG) - } - } - } - - private var _binding: BottomSheetDebugPanelBinding? = null - private val binding get() = checkNotNull(_binding) - - override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { - /*set DebugPanelTheme*/ - val dialog = super.onCreateDialog(savedInstanceState) - val contextThemeWrapper = ContextThemeWrapper(activity, CommonR.style.DebugPanelTheme) - val localInflater = dialog.layoutInflater.cloneInContext(contextThemeWrapper) - - _binding = BottomSheetDebugPanelBinding.inflate(localInflater) - dialog.setOnShowListener { - setBottomSheetSize() - } - dialog.setContentView(binding.root) - setViews(binding) - return dialog - } - - override fun onDestroyView() { - super.onDestroyView() - _binding = null - } - - private fun setViews(binding: BottomSheetDebugPanelBinding) { - val plugins = getAllPlugins() - /*Only Plugins with Fragment*/ - .filter { it.getFragment() != null } - - binding.debugSheetViewpager.adapter = DebugSheetViewPagerAdapter( - requireActivity(), - plugins - ) - - val tabConfigurationStrategy = TabLayoutMediator.TabConfigurationStrategy { tab, position -> - tab.text = plugins[position].getName() - } - - TabLayoutMediator( - binding.debugSheetTabLayout, - binding.debugSheetViewpager, - tabConfigurationStrategy - ).attach() - } - - private fun setBottomSheetSize() { - val dialogContainer = binding.root.parent as? FrameLayout - dialogContainer?.apply { - layoutParams?.height = ViewGroup.LayoutParams.MATCH_PARENT - requestLayout() - } - - with(getBehavior()) { - peekHeight = resources.displayMetrics.heightPixels / 2 - state = BottomSheetBehavior.STATE_HALF_EXPANDED - } - } - - private fun getBehavior(): BottomSheetBehavior { - return (dialog as BottomSheetDialog).behavior - } -} diff --git a/core/src/main/kotlin/com/redmadrobot/debug/core/inapp/DebugSheetViewPagerAdapter.kt b/core/src/main/kotlin/com/redmadrobot/debug/core/inapp/DebugSheetViewPagerAdapter.kt deleted file mode 100644 index 59736bf7..00000000 --- a/core/src/main/kotlin/com/redmadrobot/debug/core/inapp/DebugSheetViewPagerAdapter.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.redmadrobot.debug.core.inapp - -import androidx.fragment.app.Fragment -import androidx.fragment.app.FragmentActivity -import androidx.viewpager2.adapter.FragmentStateAdapter -import com.redmadrobot.debug.core.plugin.Plugin - -internal class DebugSheetViewPagerAdapter( - fragmentActivity: FragmentActivity, - private val plugins: List -) : FragmentStateAdapter(fragmentActivity) { - - - override fun getItemCount() = plugins.size - - override fun createFragment(position: Int): Fragment { - return requireNotNull(plugins[position].getFragment()) - } -} diff --git a/core/src/main/kotlin/com/redmadrobot/debug/core/inapp/ModalLayout.kt b/core/src/main/kotlin/com/redmadrobot/debug/core/inapp/ModalLayout.kt deleted file mode 100644 index e5cdbdb6..00000000 --- a/core/src/main/kotlin/com/redmadrobot/debug/core/inapp/ModalLayout.kt +++ /dev/null @@ -1,59 +0,0 @@ -package com.redmadrobot.debug.core.inapp - -import android.content.Context -import android.graphics.Canvas -import android.graphics.Paint -import android.graphics.RectF -import android.util.AttributeSet -import androidx.coordinatorlayout.widget.CoordinatorLayout -import androidx.core.content.ContextCompat -import com.redmadrobot.debug.core.R -import com.redmadrobot.debug.panel.common.R as CommonR - -internal class ModalLayout @JvmOverloads constructor( - context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 -) : CoordinatorLayout(context, attrs, defStyleAttr) { - - /** - * Paint для отрисовки "ручки" модального окна - */ - private val handlePaint = Paint().apply { - color = ContextCompat.getColor(context, CommonR.color.gray) - style = Paint.Style.FILL - } - - /** - * Координаты для отрисовки "ручки" модального окна - */ - private val handleRect by lazy { - val height = resources.getDimensionPixelSize(R.dimen.debug_panel_handle_height).toFloat() - val width = resources.getDimensionPixelSize(R.dimen.debug_panel_handle_width).toFloat() - val displayWidthCenter = resources.displayMetrics.widthPixels / 2 - val topMargin = resources.getDimensionPixelSize(R.dimen.debug_panel_handle_top_margin).toFloat() - - RectF( - (displayWidthCenter - width / 2), - topMargin, - (displayWidthCenter + width / 2), - height + topMargin - ) - } - - override fun onAttachedToWindow() { - super.onAttachedToWindow() - if (parent is CoordinatorLayout) { - background = ContextCompat.getDrawable(context, R.drawable.shape_scrollable_sheet) - } - } - - override fun dispatchDraw(canvas: Canvas) { - val radius = resources.getDimensionPixelSize(R.dimen.debug_panel_handle_height) / 2 - canvas.drawRoundRect( - handleRect, - radius.toFloat(), - radius.toFloat(), - handlePaint - ) - super.dispatchDraw(canvas) - } -} diff --git a/core/src/main/kotlin/com/redmadrobot/debug/core/inapp/compose/DebugBottomSheet.kt b/core/src/main/kotlin/com/redmadrobot/debug/core/inapp/compose/DebugBottomSheet.kt new file mode 100644 index 00000000..85ad128c --- /dev/null +++ b/core/src/main/kotlin/com/redmadrobot/debug/core/inapp/compose/DebugBottomSheet.kt @@ -0,0 +1,131 @@ +@file:OptIn(ExperimentalFoundationApi::class) + +package com.redmadrobot.debug.core.inapp.compose + +import androidx.appcompat.view.ContextThemeWrapper +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.pager.HorizontalPager +import androidx.compose.foundation.pager.PagerState +import androidx.compose.foundation.pager.rememberPagerState +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material.MaterialTheme +import androidx.compose.material.ModalBottomSheetLayout +import androidx.compose.material.ModalBottomSheetValue +import androidx.compose.material.ScrollableTabRow +import androidx.compose.material.Tab +import androidx.compose.material.TabRowDefaults +import androidx.compose.material.TabRowDefaults.tabIndicatorOffset +import androidx.compose.material.Text +import androidx.compose.material.rememberModalBottomSheetState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.unit.dp +import com.google.accompanist.themeadapter.material.MdcTheme +import com.redmadrobot.debug.core.R +import com.redmadrobot.debug.core.extension.getAllPlugins +import com.redmadrobot.debug.core.plugin.Plugin +import kotlinx.coroutines.launch + +@OptIn(ExperimentalMaterialApi::class) +@Composable +public fun DebugBottomSheet(onClose: () -> Unit) { + val state = rememberModalBottomSheetState( + initialValue = ModalBottomSheetValue.HalfExpanded, + confirmValueChange = { sheetState -> + if (sheetState == ModalBottomSheetValue.Hidden) onClose() + true + }, + skipHalfExpanded = false + ) + val context = LocalContext.current + val themeWrapper by remember { + mutableStateOf( + value = ContextThemeWrapper(context, R.style.DebugPanelTheme) + ) + } + + MdcTheme(context = themeWrapper) { + ModalBottomSheetLayout( + sheetContent = { BottomSheetContent() }, + sheetState = state, + scrimColor = Color.Transparent, + content = {} + ) + } +} + +@OptIn(ExperimentalFoundationApi::class) +@Composable +private fun BottomSheetContent() { + val plugins = remember { getAllPlugins() } + val pluginsName = remember { plugins.map { it.getName() } } + val pagerState = rememberPagerState(initialPage = 0, pageCount = { plugins.size }) + Column(modifier = Modifier.fillMaxSize()) { + Spacer(modifier = Modifier.height(8.dp)) + + Box( + modifier = Modifier + .height(4.dp) + .width(50.dp) + .clip(CircleShape) + .background(Color.Gray) + .align(Alignment.CenterHorizontally) + ) + + Spacer(modifier = Modifier.height(8.dp)) + + PluginsTabLayout(pluginsName, pagerState = pagerState) + + PluginsPager(plugins, pagerState = pagerState) + } +} + +@Composable +private fun PluginsTabLayout(pluginsName: List, pagerState: PagerState) { + val scope = rememberCoroutineScope() + ScrollableTabRow( + selectedTabIndex = pagerState.currentPage, + backgroundColor = MaterialTheme.colors.background, + indicator = { tabPositions -> + TabRowDefaults.Indicator( + modifier = Modifier.tabIndicatorOffset(tabPositions[pagerState.currentPage]), + height = 2.dp, + color = MaterialTheme.colors.secondary + ) + } + ) { + pluginsName.forEachIndexed { index, _ -> + Tab( + text = { Text(pluginsName[index]) }, + selected = pagerState.currentPage == index, + onClick = { + scope.launch { pagerState.animateScrollToPage(index) } + } + ) + } + } +} + +@Composable +private fun PluginsPager(plugins: List, pagerState: PagerState) { + plugins[pagerState.currentPage].content() + HorizontalPager(state = pagerState) { + // no impl. Temporary solution + } +} diff --git a/core/src/main/kotlin/com/redmadrobot/debug/core/inapp/shake/ShakeController.kt b/core/src/main/kotlin/com/redmadrobot/debug/core/inapp/shake/ShakeController.kt index 95fe40fd..051dba4f 100644 --- a/core/src/main/kotlin/com/redmadrobot/debug/core/inapp/shake/ShakeController.kt +++ b/core/src/main/kotlin/com/redmadrobot/debug/core/inapp/shake/ShakeController.kt @@ -1,10 +1,10 @@ package com.redmadrobot.debug.core.inapp.shake +import android.app.Activity import android.content.Context import android.hardware.Sensor import android.hardware.SensorManager -import androidx.fragment.app.FragmentManager -import com.redmadrobot.debug.core.inapp.DebugBottomSheet +import com.redmadrobot.debug.core.DebugPanel internal class ShakeController(context: Context) { private val sensorManager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager @@ -13,9 +13,9 @@ internal class ShakeController(context: Context) { private var openDebugPanelAction: (() -> Unit)? = null - fun register(fragmentManager: FragmentManager) { + fun register(activity: Activity) { unregister() - openDebugPanelAction = { DebugBottomSheet.show(fragmentManager) } + openDebugPanelAction = { DebugPanel.showPanel(activity) } sensorManager.registerListener(shakeDetector, accelerometer, SensorManager.SENSOR_DELAY_UI) } diff --git a/core/src/main/kotlin/com/redmadrobot/debug/core/internal/PluginViewModel.kt b/core/src/main/kotlin/com/redmadrobot/debug/core/internal/PluginViewModel.kt new file mode 100644 index 00000000..880556e4 --- /dev/null +++ b/core/src/main/kotlin/com/redmadrobot/debug/core/internal/PluginViewModel.kt @@ -0,0 +1,7 @@ +package com.redmadrobot.debug.core.internal + +import androidx.lifecycle.ViewModel +import com.redmadrobot.debug.core.annotation.DebugPanelInternal + +@DebugPanelInternal +public open class PluginViewModel : ViewModel() 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..9e1c4eba 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,6 @@ package com.redmadrobot.debug.core.plugin +import androidx.compose.runtime.Composable import androidx.fragment.app.Fragment import com.redmadrobot.debug.core.DebugEvent import com.redmadrobot.debug.core.DebugPanelInstance @@ -21,10 +22,21 @@ public abstract class Plugin { public fun getContainer(): T = pluginContainer as T - public open fun getFragment(): Fragment? = null - + @Deprecated( + message = "You shouldn't use fragments for you plugins. Please use Jetpack Compose", + replaceWith = ReplaceWith("content()", "com.redmadrobot.debug.core.plugin.Plugin"), + level = DeprecationLevel.WARNING, + ) 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 diff --git a/core/src/main/kotlin/com/redmadrobot/debug/core/ui/debugpanel/DebugActivity.kt b/core/src/main/kotlin/com/redmadrobot/debug/core/ui/debugpanel/DebugActivity.kt index d27e61af..ab4ef4a4 100644 --- a/core/src/main/kotlin/com/redmadrobot/debug/core/ui/debugpanel/DebugActivity.kt +++ b/core/src/main/kotlin/com/redmadrobot/debug/core/ui/debugpanel/DebugActivity.kt @@ -4,12 +4,12 @@ import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import androidx.fragment.app.Fragment import androidx.recyclerview.widget.LinearLayoutManager -import com.redmadrobot.debug.core.extension.getAllPlugins -import com.redmadrobot.itemsadapter.bind -import com.redmadrobot.itemsadapter.itemsAdapter import com.redmadrobot.debug.core.R import com.redmadrobot.debug.core.databinding.ActivityDebugBinding import com.redmadrobot.debug.core.databinding.ItemPluginSettingBinding +import com.redmadrobot.debug.core.extension.getAllPlugins +import com.redmadrobot.itemsadapter.bind +import com.redmadrobot.itemsadapter.itemsAdapter internal class DebugActivity : AppCompatActivity() { diff --git a/core/src/main/kotlin/com/redmadrobot/debug/core/ui/debugpanel/DebugBottomSheetActivity.kt b/core/src/main/kotlin/com/redmadrobot/debug/core/ui/debugpanel/DebugBottomSheetActivity.kt new file mode 100644 index 00000000..5bd42712 --- /dev/null +++ b/core/src/main/kotlin/com/redmadrobot/debug/core/ui/debugpanel/DebugBottomSheetActivity.kt @@ -0,0 +1,30 @@ +package com.redmadrobot.debug.core.ui.debugpanel + +import android.os.Bundle +import android.view.WindowManager +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.compose.material.MaterialTheme +import androidx.core.view.WindowCompat +import com.redmadrobot.debug.core.inapp.compose.DebugBottomSheet + +internal class DebugBottomSheetActivity : ComponentActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + configureWindow() + + setContent { + MaterialTheme { + DebugBottomSheet(onClose = { this.finish() }) + } + } + } + + private fun configureWindow() = with(window) { + WindowCompat.setDecorFitsSystemWindows(this, false) + this.setFlags( + WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS, + WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS + ) + } +} \ No newline at end of file diff --git a/core/src/main/kotlin/com/redmadrobot/debug/core/util/ApplicationLifecycleHandler.kt b/core/src/main/kotlin/com/redmadrobot/debug/core/util/ApplicationLifecycleHandler.kt index e9444742..a383ffc7 100644 --- a/core/src/main/kotlin/com/redmadrobot/debug/core/util/ApplicationLifecycleHandler.kt +++ b/core/src/main/kotlin/com/redmadrobot/debug/core/util/ApplicationLifecycleHandler.kt @@ -35,24 +35,18 @@ internal class ApplicationLifecycleHandler( if (openActivityCount == 0) onAppResumed() ++openActivityCount - (activity as? FragmentActivity)?.let { fragmentActivity -> - shakeController?.register(fragmentActivity.supportFragmentManager) + shakeController?.register(activity) - /*register BroadcastReceiver for debug panel inner actions*/ - debugPanelBroadcastReceiver = DebugPanelBroadcastReceiver( - fragmentActivity.supportFragmentManager - ) - val filter = IntentFilter( - DebugPanelBroadcastReceiver.ACTION_OPEN_DEBUG_PANEL - ) + /*register BroadcastReceiver for debug panel inner actions*/ + debugPanelBroadcastReceiver = DebugPanelBroadcastReceiver(activity) + val filter = IntentFilter(DebugPanelBroadcastReceiver.ACTION_OPEN_DEBUG_PANEL) - ContextCompat.registerReceiver( - activity, - debugPanelBroadcastReceiver, - filter, - ContextCompat.RECEIVER_NOT_EXPORTED - ) - } + ContextCompat.registerReceiver( + activity, + debugPanelBroadcastReceiver, + filter, + ContextCompat.RECEIVER_NOT_EXPORTED + ) } override fun onActivityPaused(activity: Activity) { 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..51b2a631 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 @@ -1,23 +1,22 @@ package com.redmadrobot.debug.core.util +import android.app.Activity import android.content.BroadcastReceiver import android.content.Context import android.content.Intent -import androidx.fragment.app.FragmentManager import com.redmadrobot.debug.core.DebugPanel internal class DebugPanelBroadcastReceiver( - private val supportFragmentManager: FragmentManager + private val activity: Activity ) : 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) { if (intent.action == ACTION_OPEN_DEBUG_PANEL) { - DebugPanel.showPanel(supportFragmentManager) + DebugPanel.showPanel(activity) } } } diff --git a/core/src/main/res/layout/bottom_sheet_debug_panel.xml b/core/src/main/res/layout/bottom_sheet_debug_panel.xml deleted file mode 100644 index ee1d02d0..00000000 --- a/core/src/main/res/layout/bottom_sheet_debug_panel.xml +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - - - - - - diff --git a/core/src/main/res/layout/fragment_stub.xml b/core/src/main/res/layout/fragment_stub.xml deleted file mode 100644 index 8724baa7..00000000 --- a/core/src/main/res/layout/fragment_stub.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - diff --git a/common/src/main/res/values/colors.xml b/core/src/main/res/values/colors.xml similarity index 91% rename from common/src/main/res/values/colors.xml rename to core/src/main/res/values/colors.xml index a7e6499e..10c3b0e8 100644 --- a/common/src/main/res/values/colors.xml +++ b/core/src/main/res/values/colors.xml @@ -11,5 +11,5 @@ #000000 #EF0000 #009688 - #E1E5EA + #E1E5EA diff --git a/core/src/main/res/values/dimens.xml b/core/src/main/res/values/dimens.xml index 400f019e..c63ae9ac 100644 --- a/core/src/main/res/values/dimens.xml +++ b/core/src/main/res/values/dimens.xml @@ -3,4 +3,5 @@ 8dp 2dp 48dp + 56dp diff --git a/core/src/main/res/values/styles.xml b/core/src/main/res/values/styles.xml new file mode 100644 index 00000000..68809c4c --- /dev/null +++ b/core/src/main/res/values/styles.xml @@ -0,0 +1,22 @@ + + + + + + + diff --git a/no-op/src/main/kotlin/com/redmadrobot/debug/noop/core/DebugPanel.kt b/no-op/src/main/kotlin/com/redmadrobot/debug/noop/core/DebugPanel.kt index bd684124..500ceb95 100644 --- a/no-op/src/main/kotlin/com/redmadrobot/debug/noop/core/DebugPanel.kt +++ b/no-op/src/main/kotlin/com/redmadrobot/debug/noop/core/DebugPanel.kt @@ -1,5 +1,6 @@ package com.redmadrobot.debug.core +import android.app.Activity import android.app.Application import androidx.fragment.app.FragmentManager import androidx.lifecycle.LifecycleOwner @@ -16,7 +17,9 @@ object DebugPanel { fun subscribeToEvents(lifecycleOwner: LifecycleOwner, onEvent: (DebugEvent) -> Unit) = Unit - fun observeEvents(): Flow? = emptyFlow() + fun observeEvents(): Flow = emptyFlow() fun showPanel(fragmentManager: FragmentManager) = Unit + + fun showPanel(activity: Activity) = Unit } 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/DebugStage.kt b/no-op/src/main/kotlin/com/redmadrobot/debug/noop/plugin/servers/DebugStage.kt new file mode 100644 index 00000000..118038e7 --- /dev/null +++ b/no-op/src/main/kotlin/com/redmadrobot/debug/noop/plugin/servers/DebugStage.kt @@ -0,0 +1,8 @@ +package com.redmadrobot.debug.plugin.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/plugin/servers/DebugServerInterceptor.kt b/no-op/src/main/kotlin/com/redmadrobot/debug/noop/plugin/servers/interceptor/DebugServerInterceptor.kt similarity index 88% rename from no-op/src/main/kotlin/com/redmadrobot/debug/noop/plugin/servers/DebugServerInterceptor.kt rename to no-op/src/main/kotlin/com/redmadrobot/debug/noop/plugin/servers/interceptor/DebugServerInterceptor.kt index c8670bed..761ac6d2 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/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.plugin.servers.data.model.DebugServer import okhttp3.Interceptor diff --git a/no-op/src/main/kotlin/com/redmadrobot/debug/noop/plugin/servers/interceptor/DebugStageInterceptor.kt b/no-op/src/main/kotlin/com/redmadrobot/debug/noop/plugin/servers/interceptor/DebugStageInterceptor.kt new file mode 100644 index 00000000..2c225b07 --- /dev/null +++ b/no-op/src/main/kotlin/com/redmadrobot/debug/noop/plugin/servers/interceptor/DebugStageInterceptor.kt @@ -0,0 +1,17 @@ +package com.redmadrobot.debug.plugin.servers.interceptor + +import com.redmadrobot.debug.plugin.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()) + } +} diff --git a/plugins/accounts/build.gradle.kts b/plugins/accounts/build.gradle.kts index 79d1457d..a63aafc5 100644 --- a/plugins/accounts/build.gradle.kts +++ b/plugins/accounts/build.gradle.kts @@ -43,7 +43,11 @@ android { } buildFeatures { - viewBinding = true + compose = true + } + + composeOptions { + kotlinCompilerExtensionVersion = androidx.versions.compose.compiler.get() } namespace = "com.redmadrobot.debug.plugin.accounts" } 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..d3d4e9ff 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,15 +1,14 @@ package com.redmadrobot.debug.plugin.accounts -import androidx.core.os.bundleOf -import androidx.fragment.app.Fragment +import androidx.compose.runtime.Composable +import com.redmadrobot.debug.core.data.DebugDataProvider +import com.redmadrobot.debug.core.internal.CommonContainer +import com.redmadrobot.debug.core.internal.PluginDependencyContainer +import com.redmadrobot.debug.core.plugin.Plugin import com.redmadrobot.debug.plugin.accounts.authenticator.DebugAuthenticator import com.redmadrobot.debug.plugin.accounts.authenticator.DefaultAuthenticator import com.redmadrobot.debug.plugin.accounts.data.model.DebugAccount -import com.redmadrobot.debug.plugin.accounts.ui.AccountsFragment -import com.redmadrobot.debug.core.internal.CommonContainer -import com.redmadrobot.debug.core.data.DebugDataProvider -import com.redmadrobot.debug.core.plugin.Plugin -import com.redmadrobot.debug.core.internal.PluginDependencyContainer +import com.redmadrobot.debug.plugin.accounts.ui.AccountsScreen public class AccountsPlugin( private val preInstalledAccounts: List = emptyList(), @@ -34,15 +33,13 @@ public class AccountsPlugin( return AccountsPluginContainer(preInstalledAccounts, commonContainer) } - override fun getFragment(): Fragment { - return AccountsFragment().apply { - arguments = bundleOf(AccountsFragment.IS_EDIT_MODE_KEY to false) - } + @Composable + override fun content() { + AccountsScreen(isEditMode = false) } - override fun getSettingFragment(): Fragment { - return AccountsFragment().apply { - arguments = bundleOf(AccountsFragment.IS_EDIT_MODE_KEY to true) - } + @Composable + override fun settingsContent() { + AccountsScreen(isEditMode = true) } } diff --git a/plugins/accounts/src/main/kotlin/com/redmadrobot/debug/plugin/accounts/AccountsPluginContainer.kt b/plugins/accounts/src/main/kotlin/com/redmadrobot/debug/plugin/accounts/AccountsPluginContainer.kt index 2c66eb01..56c7bf24 100644 --- a/plugins/accounts/src/main/kotlin/com/redmadrobot/debug/plugin/accounts/AccountsPluginContainer.kt +++ b/plugins/accounts/src/main/kotlin/com/redmadrobot/debug/plugin/accounts/AccountsPluginContainer.kt @@ -1,11 +1,11 @@ package com.redmadrobot.debug.plugin.accounts +import com.redmadrobot.debug.core.internal.CommonContainer +import com.redmadrobot.debug.core.internal.PluginDependencyContainer import com.redmadrobot.debug.plugin.accounts.data.LocalDebugAccountRepository import com.redmadrobot.debug.plugin.accounts.data.model.DebugAccount import com.redmadrobot.debug.plugin.accounts.data.storage.AccountsPluginDatabase import com.redmadrobot.debug.plugin.accounts.ui.AccountsViewModel -import com.redmadrobot.debug.core.internal.CommonContainer -import com.redmadrobot.debug.core.internal.PluginDependencyContainer internal class AccountsPluginContainer( private val preInstalledAccounts: List, 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 deleted file mode 100644 index d4efbbd2..00000000 --- a/plugins/accounts/src/main/kotlin/com/redmadrobot/debug/plugin/accounts/ui/AccountsFragment.kt +++ /dev/null @@ -1,132 +0,0 @@ -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.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 - -internal class AccountsFragment : PluginFragment(R.layout.fragment_accounts) { - - 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) - } - - private val viewModel by lazy { - obtainShareViewModel { - getPlugin() - .getContainer() - .createAccountsViewModel() - } - } - - 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 - ) - } - 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..c5cb44d8 --- /dev/null +++ b/plugins/accounts/src/main/kotlin/com/redmadrobot/debug/plugin/accounts/ui/AccountsScreen.kt @@ -0,0 +1,270 @@ +package com.redmadrobot.debug.plugin.accounts.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.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.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.LaunchedEffect +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.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 com.redmadrobot.debug.core.extension.getPlugin +import com.redmadrobot.debug.core.extension.provideViewModel +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.data.model.DebugAccount + +@SuppressLint("UnusedMaterialScaffoldPaddingParameter") +@Composable +internal fun AccountsScreen( + viewModel: AccountsViewModel = provideViewModel { + getPlugin() + .getContainer() + .createAccountsViewModel() + }, + isEditMode: Boolean +) { + val state by viewModel.state.collectAsState() + var showDialog by remember { mutableStateOf(false) } + var editableAccount by remember { mutableStateOf(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 + }, + 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 debugAccount = DebugAccount( + id = account?.id ?: 0, + login = login, + password = password, + pin = pin + ) + onSaveClick(debugAccount) + 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({}, {}) +} 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..2a580578 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 @@ -1,27 +1,29 @@ 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.core.extension.getPlugin +import com.redmadrobot.debug.core.extension.safeLaunch +import com.redmadrobot.debug.core.internal.PluginViewModel +import com.redmadrobot.debug.plugin.accounts.AccountSelectedEvent +import com.redmadrobot.debug.plugin.accounts.AccountsPlugin import com.redmadrobot.debug.plugin.accounts.R 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 kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.update internal class AccountsViewModel( private val context: Context, private val debugAccountsRepository: DebugAccountRepository ) : PluginViewModel() { - val state = MutableLiveData().apply { - /*Default state*/ + val state = MutableStateFlow( value = AccountsViewState( preInstalledAccounts = emptyList(), addedAccounts = emptyList() ) - } + ) fun loadAccounts() { viewModelScope.safeLaunch { @@ -30,14 +32,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,31 +69,40 @@ 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() } - state.value = state.value?.copy(preInstalledAccounts = preInstalledAccounts) + + state.update { state.value.copy(preInstalledAccounts = preInstalledAccounts) } } private suspend fun loadAddedAccounts() { 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() } - state.value = state.value?.copy(addedAccounts = addedAccountItems) + + state.update { state.value.copy(addedAccounts = addedAccountItems) } } } 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..58af1d20 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,11 @@ package com.redmadrobot.debug.plugin.accounts.ui -import com.redmadrobot.debug.plugin.accounts.ui.item.DebugAccountItems +import androidx.compose.runtime.Stable +@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/add/AddAccountDialog.kt b/plugins/accounts/src/main/kotlin/com/redmadrobot/debug/plugin/accounts/ui/add/AddAccountDialog.kt deleted file mode 100644 index eb6ce92d..00000000 --- a/plugins/accounts/src/main/kotlin/com/redmadrobot/debug/plugin/accounts/ui/add/AddAccountDialog.kt +++ /dev/null @@ -1,126 +0,0 @@ -package com.redmadrobot.debug.plugin.accounts.ui.add - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -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 - -internal class AddAccountDialog : DialogFragment() { - - companion object { - private const val KEY_ID = "ID" - private const val KEY_LOGIN = "LOGIN" - private const val KEY_PASSWORD = "PASSWORD" - private const val KEY_PIN = "PIN" - - private const val TAG = "AddAccountDialog" - - fun show( - fragmentManager: FragmentManager, - account: DebugAccount? = null - ) { - AddAccountDialog().apply { - if (account != null) { - this.arguments = bundleOf( - KEY_ID to account.id, - KEY_LOGIN to account.login, - KEY_PASSWORD to account.password, - KEY_PIN to account.pin - ) - } - }.show(fragmentManager, TAG) - } - } - - private var _binding: DialogAddAccountBinding? = null - private val binding get() = checkNotNull(_binding) - - private val id by lazy { arguments?.getInt(KEY_ID) } - private val login by lazy { arguments?.getString(KEY_LOGIN) } - private val password by lazy { arguments?.getString(KEY_PASSWORD) } - private val pin by lazy { arguments?.getString(KEY_PIN) } - - private val isEditMode: Boolean - get() = login != null && password != null - - private val sharedViewModel by lazy { - obtainShareViewModel { - getPlugin() - .getContainer() - .createAccountsViewModel() - } - } - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View? { - _binding = DialogAddAccountBinding.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.setView() - binding.setData() - binding.accountLogin.requestFocus() - } - - override fun onDestroyView() { - super.onDestroyView() - _binding = null - } - - private fun DialogAddAccountBinding.setData() { - if (!login.isNullOrEmpty() && password != null) { - accountLogin.setText(login) - accountPassword.setText(password) - accountPin.setText(pin) - } - } - - private fun DialogAddAccountBinding.setView() { - saveAccountButton.setOnClickListener { - if (dataIsValid()) save() - } - } - - private fun save() { - val login = binding.accountLogin.text.toString() - val password = binding.accountPassword.text.toString() - val pin = binding.accountPin.text.toString() - if (isEditMode) { - id?.let { id -> - sharedViewModel.updateAccount(id, login, password, pin) - } - } else { - sharedViewModel.saveAccount(login, password, pin) - } - dialog?.dismiss() - } - - - private fun dataIsValid(): Boolean { - //TODO Добавить валидацию данных - return true - } -} 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/dialog_add_pin.xml b/plugins/accounts/src/main/res/layout/dialog_add_pin.xml deleted file mode 100644 index f0a63dcb..00000000 --- a/plugins/accounts/src/main/res/layout/dialog_add_pin.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - -