From 89f541f8fcf9a3bc109e708e4181b29893709852 Mon Sep 17 00:00:00 2001 From: Kirill Nayduik Date: Tue, 21 Sep 2021 01:06:56 +0300 Subject: [PATCH 001/133] Fix keyboard change detection: separate StatefulKeyboardResizeableFragment and KeyboardResizeableFragment, add ability to handle keyboard detection inside nested fragments --- .../core/MviKeyboardResizableFragment.kt | 38 ++++---- .../KeyboardBehaviorDetector.kt | 22 +++-- .../KeyboardResizeableFragment.kt | 53 +++++------ .../StatefulKeyboardResizeableFragment.kt | 93 +++++++++++++++++++ 4 files changed, 155 insertions(+), 51 deletions(-) create mode 100644 navigation-base/src/main/java/ru/touchin/roboswag/navigation_base/keyboard_resizeable/StatefulKeyboardResizeableFragment.kt diff --git a/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/core/MviKeyboardResizableFragment.kt b/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/core/MviKeyboardResizableFragment.kt index c41e850..1c30f88 100644 --- a/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/core/MviKeyboardResizableFragment.kt +++ b/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/core/MviKeyboardResizableFragment.kt @@ -9,10 +9,12 @@ import ru.touchin.roboswag.components.utils.hideSoftInput import ru.touchin.roboswag.mvi_arch.marker.ViewAction import ru.touchin.roboswag.mvi_arch.marker.ViewState import ru.touchin.roboswag.navigation_base.activities.OnBackPressedListener +import ru.touchin.roboswag.navigation_base.keyboard_resizeable.OnHideListener +import ru.touchin.roboswag.navigation_base.keyboard_resizeable.OnShowListener // CPD-OFF /** - * Same code as in [ru.touchin.roboswag.navigation_base.keyboard_resizeable.KeyboardResizeableFragment] but inherited from MviFragment + * Same code as in [ru.touchin.roboswag.navigation_base.keyboard_resizeable.StatefulKeyboardResizeableFragment] but inherited from MviFragment * Used to detect IME events (show, hide) */ @@ -26,7 +28,7 @@ abstract class MviKeyboardResizableFragment( private var isKeyboardVisible: Boolean = false - private val keyboardHideListener = OnBackPressedListener { + private val onBackPressedListener = OnBackPressedListener { if (isKeyboardVisible) { activity.hideSoftInput() true @@ -35,6 +37,18 @@ abstract class MviKeyboardResizableFragment( } } + private val keyboardHideListener: OnHideListener = { + if (isKeyboardVisible) { + onKeyboardHide() + } + isKeyboardVisible = false + } + + private val keyboardShowListener: OnShowListener = { diff -> + onKeyboardShow(diff) + isKeyboardVisible = true + } + private var isHideKeyboardOnBackEnabled = false protected open fun onKeyboardShow(diff: Int = 0) {} @@ -54,36 +68,28 @@ abstract class MviKeyboardResizableFragment( override fun onResume() { super.onResume() - if (isHideKeyboardOnBackEnabled) activity.addOnBackPressedListener(keyboardHideListener) + if (isHideKeyboardOnBackEnabled) activity.addOnBackPressedListener(onBackPressedListener) } override fun onPause() { super.onPause() if (isKeyboardVisible) activity.hideSoftInput() - if (isHideKeyboardOnBackEnabled) activity.removeOnBackPressedListener(keyboardHideListener) + if (isHideKeyboardOnBackEnabled) activity.removeOnBackPressedListener(onBackPressedListener) } override fun onStart() { super.onStart() activity.keyboardBehaviorDetector?.apply { - keyboardHideListener = { - if (isKeyboardVisible) { - onKeyboardHide() - } - isKeyboardVisible = false - } - keyboardShowListener = { diff -> - onKeyboardShow(diff) - isKeyboardVisible = true - } + addOnHideListener(keyboardHideListener) + addOnShowListener(keyboardShowListener) } } override fun onStop() { super.onStop() activity.keyboardBehaviorDetector?.apply { - keyboardHideListener = null - keyboardShowListener = null + removeOnHideListener(keyboardHideListener) + removeOnShowListener(keyboardShowListener) } } diff --git a/navigation-base/src/main/java/ru/touchin/roboswag/navigation_base/keyboard_resizeable/KeyboardBehaviorDetector.kt b/navigation-base/src/main/java/ru/touchin/roboswag/navigation_base/keyboard_resizeable/KeyboardBehaviorDetector.kt index ca79892..13e6088 100644 --- a/navigation-base/src/main/java/ru/touchin/roboswag/navigation_base/keyboard_resizeable/KeyboardBehaviorDetector.kt +++ b/navigation-base/src/main/java/ru/touchin/roboswag/navigation_base/keyboard_resizeable/KeyboardBehaviorDetector.kt @@ -12,14 +12,26 @@ import ru.touchin.roboswag.navigation_base.activities.BaseActivity * * Your activity must have android:windowSoftInputMode="adjustResize" at least, otherwise listeners won't be called */ + +typealias OnHideListener = () -> Unit +typealias OnShowListener = (Int) -> Unit + class KeyboardBehaviorDetector( activity: BaseActivity ) : LifecycleObserver { private val view = activity.window.decorView - var keyboardHideListener: (() -> Unit)? = null - var keyboardShowListener: ((Int) -> Unit)? = null + private val keyboardHideListeners: MutableList = mutableListOf() + private val keyboardShowListeners: MutableList = mutableListOf() + + fun addOnHideListener(listener: OnHideListener) { keyboardHideListeners.add(listener) } + + fun addOnShowListener(listener: OnShowListener) { keyboardShowListeners.add(listener) } + + fun removeOnHideListener(listener: OnHideListener) { keyboardHideListeners.remove(listener) } + + fun removeOnShowListener(listener: OnShowListener) { keyboardShowListeners.remove(listener) } // -1 when we never measure insets yet var startNavigationBarHeight = -1 @@ -27,11 +39,9 @@ class KeyboardBehaviorDetector( private val listener = { isKeyboardOpen: Boolean, windowInsets: WindowInsetsCompat -> if (isKeyboardOpen) { - keyboardShowListener?.invoke( - windowInsets.systemWindowInsetBottom - startNavigationBarHeight - ) + keyboardShowListeners.forEach { it.invoke(windowInsets.systemWindowInsetBottom - startNavigationBarHeight) } } else { - keyboardHideListener?.invoke() + keyboardHideListeners.forEach { it.invoke()} } } diff --git a/navigation-base/src/main/java/ru/touchin/roboswag/navigation_base/keyboard_resizeable/KeyboardResizeableFragment.kt b/navigation-base/src/main/java/ru/touchin/roboswag/navigation_base/keyboard_resizeable/KeyboardResizeableFragment.kt index e43d405..7043d13 100644 --- a/navigation-base/src/main/java/ru/touchin/roboswag/navigation_base/keyboard_resizeable/KeyboardResizeableFragment.kt +++ b/navigation-base/src/main/java/ru/touchin/roboswag/navigation_base/keyboard_resizeable/KeyboardResizeableFragment.kt @@ -1,25 +1,23 @@ package ru.touchin.roboswag.navigation_base.keyboard_resizeable -import android.os.Build import android.os.Bundle -import android.os.Parcelable import android.view.View import androidx.annotation.LayoutRes import androidx.lifecycle.LifecycleObserver import ru.touchin.roboswag.components.utils.hideSoftInput import ru.touchin.roboswag.navigation_base.activities.BaseActivity import ru.touchin.roboswag.navigation_base.activities.OnBackPressedListener -import ru.touchin.roboswag.navigation_base.fragments.StatefulFragment +import ru.touchin.roboswag.navigation_base.fragments.BaseFragment -abstract class KeyboardResizeableFragment( +abstract class KeyboardResizeableFragment( @LayoutRes layoutRes: Int -) : StatefulFragment( +) : BaseFragment( layoutRes ) { private var isKeyboardVisible: Boolean = false - private val keyboardHideListener = OnBackPressedListener { + private val onBackPressedListener = OnBackPressedListener { if (isKeyboardVisible) { activity.hideSoftInput() true @@ -28,6 +26,18 @@ abstract class KeyboardResizeableFragment + onKeyboardShow(diff) + isKeyboardVisible = true + } + private var isHideKeyboardOnBackEnabled = false protected open fun onKeyboardShow(diff: Int = 0) {} @@ -41,50 +51,35 @@ abstract class KeyboardResizeableFragment= Build.VERSION_CODES.KITKAT_WATCH) { - view.requestApplyInsets() - } + view.requestApplyInsets() lifecycle.addObserver(activity.keyboardBehaviorDetector as LifecycleObserver) } override fun onResume() { super.onResume() - if (isHideKeyboardOnBackEnabled) activity.addOnBackPressedListener(keyboardHideListener) + if (isHideKeyboardOnBackEnabled) activity.addOnBackPressedListener(onBackPressedListener) } override fun onPause() { super.onPause() - notifyKeyboardHidden() - if (isHideKeyboardOnBackEnabled) activity.removeOnBackPressedListener(keyboardHideListener) + if (isKeyboardVisible) activity.hideSoftInput() + if (isHideKeyboardOnBackEnabled) activity.removeOnBackPressedListener(onBackPressedListener) } override fun onStart() { super.onStart() activity.keyboardBehaviorDetector?.apply { - keyboardHideListener = { - if (isKeyboardVisible) { - onKeyboardHide() - } - isKeyboardVisible = false - } - keyboardShowListener = { diff -> - onKeyboardShow(diff) - isKeyboardVisible = true - } + addOnHideListener(keyboardHideListener) + addOnShowListener(keyboardShowListener) } } override fun onStop() { super.onStop() activity.keyboardBehaviorDetector?.apply { - keyboardHideListener = null - keyboardShowListener = null + removeOnHideListener(keyboardHideListener) + removeOnShowListener(keyboardShowListener) } } - private fun notifyKeyboardHidden() { - if (isKeyboardVisible) onKeyboardHide() - isKeyboardVisible = false - } - } diff --git a/navigation-base/src/main/java/ru/touchin/roboswag/navigation_base/keyboard_resizeable/StatefulKeyboardResizeableFragment.kt b/navigation-base/src/main/java/ru/touchin/roboswag/navigation_base/keyboard_resizeable/StatefulKeyboardResizeableFragment.kt new file mode 100644 index 0000000..d14ec8e --- /dev/null +++ b/navigation-base/src/main/java/ru/touchin/roboswag/navigation_base/keyboard_resizeable/StatefulKeyboardResizeableFragment.kt @@ -0,0 +1,93 @@ +package ru.touchin.roboswag.navigation_base.keyboard_resizeable + +import android.os.Bundle +import android.os.Parcelable +import android.view.View +import androidx.annotation.LayoutRes +import androidx.lifecycle.LifecycleObserver +import ru.touchin.roboswag.components.utils.hideSoftInput +import ru.touchin.roboswag.navigation_base.activities.BaseActivity +import ru.touchin.roboswag.navigation_base.activities.OnBackPressedListener +import ru.touchin.roboswag.navigation_base.fragments.StatefulFragment + +// CPD-OFF +/** + * Same code as in [KeyboardResizeableFragment] but inherited from StatefulFragment + * Used to detect IME events (show, hide) + */ + +abstract class StatefulKeyboardResizeableFragment( + @LayoutRes layoutRes: Int +) : StatefulFragment( + layoutRes +) { + + private var isKeyboardVisible: Boolean = false + + private val onBackPressedListener = OnBackPressedListener { + if (isKeyboardVisible) { + activity.hideSoftInput() + true + } else { + false + } + } + + private val keyboardHideListener: OnHideListener = { + if (isKeyboardVisible) { + onKeyboardHide() + } + isKeyboardVisible = false + } + + private val keyboardShowListener: OnShowListener = { diff -> + onKeyboardShow(diff) + isKeyboardVisible = true + } + + private var isHideKeyboardOnBackEnabled = false + + protected open fun onKeyboardShow(diff: Int = 0) {} + + protected open fun onKeyboardHide() {} + + protected fun hideKeyboardOnBackPressed() { + isHideKeyboardOnBackEnabled = true + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + view.requestApplyInsets() + lifecycle.addObserver(activity.keyboardBehaviorDetector as LifecycleObserver) + } + + override fun onResume() { + super.onResume() + if (isHideKeyboardOnBackEnabled) activity.addOnBackPressedListener(onBackPressedListener) + } + + override fun onPause() { + super.onPause() + if (isKeyboardVisible) activity.hideSoftInput() + if (isHideKeyboardOnBackEnabled) activity.removeOnBackPressedListener(onBackPressedListener) + } + + override fun onStart() { + super.onStart() + activity.keyboardBehaviorDetector?.apply { + addOnHideListener(keyboardHideListener) + addOnShowListener(keyboardShowListener) + } + } + + override fun onStop() { + super.onStop() + activity.keyboardBehaviorDetector?.apply { + removeOnHideListener(keyboardHideListener) + removeOnShowListener(keyboardShowListener) + } + } + +} +// CPD-ON From 7c20ea2ce47ef46b5fb70e7f61b6172786e15e31 Mon Sep 17 00:00:00 2001 From: Kirill Nayduik Date: Wed, 22 Sep 2021 03:52:14 +0300 Subject: [PATCH 002/133] Fix navigating back inside nested features --- .../roboswag/navigation_cicerone/flow/FlowFragment.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/navigation-cicerone/src/main/java/ru/touchin/roboswag/navigation_cicerone/flow/FlowFragment.kt b/navigation-cicerone/src/main/java/ru/touchin/roboswag/navigation_cicerone/flow/FlowFragment.kt index 9849a3a..d646c9d 100644 --- a/navigation-cicerone/src/main/java/ru/touchin/roboswag/navigation_cicerone/flow/FlowFragment.kt +++ b/navigation-cicerone/src/main/java/ru/touchin/roboswag/navigation_cicerone/flow/FlowFragment.kt @@ -59,7 +59,11 @@ abstract class FlowFragment : Fragment(R.layout.fragment_flow) { private val exitRouterOnBackPressed = object : OnBackPressedCallback(true) { override fun handleOnBackPressed() { - router.exit() + if (childFragmentManager.backStackEntryCount == 0 && parentFragmentManager.backStackEntryCount != 0) { + parentFragmentManager.popBackStack() + } else { + router.exit() + } } } From aaa65d4e6eb3922bbb571b1eac769d1f8f7e9646 Mon Sep 17 00:00:00 2001 From: Kirill Nayduik Date: Wed, 22 Sep 2021 04:00:35 +0300 Subject: [PATCH 003/133] Add variables for readability --- .../roboswag/navigation_cicerone/flow/FlowFragment.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/navigation-cicerone/src/main/java/ru/touchin/roboswag/navigation_cicerone/flow/FlowFragment.kt b/navigation-cicerone/src/main/java/ru/touchin/roboswag/navigation_cicerone/flow/FlowFragment.kt index d646c9d..5d20819 100644 --- a/navigation-cicerone/src/main/java/ru/touchin/roboswag/navigation_cicerone/flow/FlowFragment.kt +++ b/navigation-cicerone/src/main/java/ru/touchin/roboswag/navigation_cicerone/flow/FlowFragment.kt @@ -59,7 +59,10 @@ abstract class FlowFragment : Fragment(R.layout.fragment_flow) { private val exitRouterOnBackPressed = object : OnBackPressedCallback(true) { override fun handleOnBackPressed() { - if (childFragmentManager.backStackEntryCount == 0 && parentFragmentManager.backStackEntryCount != 0) { + val isFragmentOnTop = childFragmentManager.backStackEntryCount == 0 + val hasParentFragment = parentFragmentManager.backStackEntryCount != 0 + + if (isFragmentOnTop && hasParentFragment) { parentFragmentManager.popBackStack() } else { router.exit() From e920a7ce340cee123cb76d4e9839f41cb029c3fc Mon Sep 17 00:00:00 2001 From: Rinat Nurmukhametov Date: Thu, 30 Sep 2021 10:41:52 +0300 Subject: [PATCH 004/133] add top padding to fullsreen bottom sheet --- .../roboswag/mvi_arch/core/FullscreenBottomSheetDialog.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/core/FullscreenBottomSheetDialog.kt b/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/core/FullscreenBottomSheetDialog.kt index 5cc70cd..6500628 100644 --- a/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/core/FullscreenBottomSheetDialog.kt +++ b/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/core/FullscreenBottomSheetDialog.kt @@ -20,6 +20,7 @@ import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.bottomsheet.BottomSheetDialogFragment import ru.touchin.mvi_arch.R +import ru.touchin.roboswag.components.utils.px import ru.touchin.roboswag.mvi_arch.di.ViewModelAssistedFactory import ru.touchin.roboswag.mvi_arch.di.ViewModelFactory import ru.touchin.roboswag.mvi_arch.marker.ViewAction @@ -27,7 +28,8 @@ import ru.touchin.roboswag.mvi_arch.marker.ViewState import javax.inject.Inject abstract class FullscreenBottomSheetDialog( - @LayoutRes private val layoutId: Int + @LayoutRes private val layoutId: Int, + private val topPadding: Int = 0 ) : BottomSheetDialogFragment(), IMvi where NavArgs : Parcelable, Action : ViewAction, @@ -89,6 +91,7 @@ abstract class FullscreenBottomSheetDialog( val bottomSheet = dialog?.findViewById(com.google.android.material.R.id.design_bottom_sheet) bottomSheet?.layoutParams?.height = ViewGroup.LayoutParams.MATCH_PARENT + bottomSheet?.setPadding(0, topPadding.px, 0, 0) } override fun addOnBackPressedCallback(action: Action) { From 6e209492ae75dee57cf09d7e5dd0f7bc9f662c17 Mon Sep 17 00:00:00 2001 From: Rinat Nurmukhametov Date: Thu, 30 Sep 2021 17:13:37 +0300 Subject: [PATCH 005/133] change bottomSheet on var --- .../mvi_arch/core/FullscreenBottomSheetDialog.kt | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/core/FullscreenBottomSheetDialog.kt b/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/core/FullscreenBottomSheetDialog.kt index 6500628..068c5ca 100644 --- a/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/core/FullscreenBottomSheetDialog.kt +++ b/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/core/FullscreenBottomSheetDialog.kt @@ -20,7 +20,6 @@ import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.bottomsheet.BottomSheetDialogFragment import ru.touchin.mvi_arch.R -import ru.touchin.roboswag.components.utils.px import ru.touchin.roboswag.mvi_arch.di.ViewModelAssistedFactory import ru.touchin.roboswag.mvi_arch.di.ViewModelFactory import ru.touchin.roboswag.mvi_arch.marker.ViewAction @@ -28,8 +27,7 @@ import ru.touchin.roboswag.mvi_arch.marker.ViewState import javax.inject.Inject abstract class FullscreenBottomSheetDialog( - @LayoutRes private val layoutId: Int, - private val topPadding: Int = 0 + @LayoutRes private val layoutId: Int ) : BottomSheetDialogFragment(), IMvi where NavArgs : Parcelable, Action : ViewAction, @@ -41,6 +39,8 @@ abstract class FullscreenBottomSheetDialog( protected lateinit var state: NavArgs + protected var bottomSheet: FrameLayout? = null + protected abstract fun injectDependencies() @MainThread @@ -89,9 +89,8 @@ abstract class FullscreenBottomSheetDialog( override fun onStart() { super.onStart() - val bottomSheet = dialog?.findViewById(com.google.android.material.R.id.design_bottom_sheet) + bottomSheet = dialog?.findViewById(com.google.android.material.R.id.design_bottom_sheet) bottomSheet?.layoutParams?.height = ViewGroup.LayoutParams.MATCH_PARENT - bottomSheet?.setPadding(0, topPadding.px, 0, 0) } override fun addOnBackPressedCallback(action: Action) { From 4a8eeba10a379224af0e6146576ea3c86df4176a Mon Sep 17 00:00:00 2001 From: Rinat Nurmukhametov Date: Thu, 7 Oct 2021 17:35:18 +0300 Subject: [PATCH 006/133] moved the method to extension --- .../core/FullscreenBottomSheetDialog.kt | 17 ++--------------- utils/build.gradle | 7 +++++++ .../components/utils/ViewExtensions.kt | 18 ++++++++++++++++++ 3 files changed, 27 insertions(+), 15 deletions(-) diff --git a/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/core/FullscreenBottomSheetDialog.kt b/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/core/FullscreenBottomSheetDialog.kt index 068c5ca..a9388c2 100644 --- a/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/core/FullscreenBottomSheetDialog.kt +++ b/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/core/FullscreenBottomSheetDialog.kt @@ -2,7 +2,6 @@ package ru.touchin.roboswag.mvi_arch.core import android.app.Dialog import android.content.Context -import android.content.DialogInterface import android.os.Bundle import android.os.Parcelable import android.view.LayoutInflater @@ -16,10 +15,9 @@ import androidx.core.os.bundleOf import androidx.lifecycle.Observer import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider -import com.google.android.material.bottomsheet.BottomSheetBehavior -import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.bottomsheet.BottomSheetDialogFragment import ru.touchin.mvi_arch.R +import ru.touchin.roboswag.components.utils.getResizableShowListener import ru.touchin.roboswag.mvi_arch.di.ViewModelAssistedFactory import ru.touchin.roboswag.mvi_arch.di.ViewModelFactory import ru.touchin.roboswag.mvi_arch.marker.ViewAction @@ -54,17 +52,6 @@ abstract class FullscreenBottomSheetDialog( ).get(ViewModel::class.java) } - private val onShowListener by lazy { - DialogInterface.OnShowListener { dialog -> - (dialog as BottomSheetDialog).findViewById(com.google.android.material.R.id.design_bottom_sheet) - ?.let { BottomSheetBehavior.from(it) } - ?.apply { - state = BottomSheetBehavior.STATE_EXPANDED - skipCollapsed = true - } - } - } - override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setStyle(STYLE_NORMAL, R.style.RoundedCornersBottomSheetDialogTheme) @@ -102,7 +89,7 @@ abstract class FullscreenBottomSheetDialog( } override fun setupDialog(dialog: Dialog, style: Int) { - dialog.setOnShowListener(onShowListener) + dialog.setOnShowListener(getResizableShowListener()) } override fun onSaveInstanceState(outState: Bundle) { diff --git a/utils/build.gradle b/utils/build.gradle index 2a490af..fe6fd6a 100644 --- a/utils/build.gradle +++ b/utils/build.gradle @@ -4,6 +4,7 @@ dependencies { implementation project(':kotlin-extensions') implementation "androidx.core:core" implementation "androidx.annotation:annotation" + implementation "com.google.android.material:material" constraints { implementation("androidx.core:core") { @@ -17,5 +18,11 @@ dependencies { require '1.1.0' } } + + implementation("com.google.android.material:material") { + version { + require '1.2.0-rc01' + } + } } } diff --git a/utils/src/main/java/ru/touchin/roboswag/components/utils/ViewExtensions.kt b/utils/src/main/java/ru/touchin/roboswag/components/utils/ViewExtensions.kt index 9c65ac7..ac3b31a 100644 --- a/utils/src/main/java/ru/touchin/roboswag/components/utils/ViewExtensions.kt +++ b/utils/src/main/java/ru/touchin/roboswag/components/utils/ViewExtensions.kt @@ -2,9 +2,14 @@ package ru.touchin.roboswag.components.utils import android.app.Activity import android.content.Context +import android.content.DialogInterface import android.content.res.Resources import android.view.View import android.view.inputmethod.InputMethodManager +import android.widget.FrameLayout +import com.google.android.material.bottomsheet.BottomSheetBehavior +import com.google.android.material.bottomsheet.BottomSheetDialog +import com.google.android.material.bottomsheet.BottomSheetDialogFragment /** * Returns string representation of [View]'s ID. @@ -46,3 +51,16 @@ fun View.showSoftInput() { val inputManager = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager inputManager.showSoftInput(this, InputMethodManager.SHOW_IMPLICIT) } + +/** + * Returns listener for BottomSheetDialogFragment so that dialog is shifted when the keyboard is opened + */ + +fun BottomSheetDialogFragment.getResizableShowListener() = DialogInterface.OnShowListener { dialog -> + (dialog as BottomSheetDialog).findViewById(com.google.android.material.R.id.design_bottom_sheet) + ?.let { BottomSheetBehavior.from(it) } + ?.apply { + state = BottomSheetBehavior.STATE_EXPANDED + skipCollapsed = true + } +} From 862ab1545078daf215821cf2375a8c04114c741a Mon Sep 17 00:00:00 2001 From: Rinat Nurmukhametov Date: Thu, 7 Oct 2021 18:08:42 +0300 Subject: [PATCH 007/133] refactor extension --- .../core/FullscreenBottomSheetDialog.kt | 4 ++-- .../components/utils/ViewExtensions.kt | 18 ++++++++++-------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/core/FullscreenBottomSheetDialog.kt b/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/core/FullscreenBottomSheetDialog.kt index a9388c2..4735f76 100644 --- a/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/core/FullscreenBottomSheetDialog.kt +++ b/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/core/FullscreenBottomSheetDialog.kt @@ -17,7 +17,7 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import com.google.android.material.bottomsheet.BottomSheetDialogFragment import ru.touchin.mvi_arch.R -import ru.touchin.roboswag.components.utils.getResizableShowListener +import ru.touchin.roboswag.components.utils.setResizableListener import ru.touchin.roboswag.mvi_arch.di.ViewModelAssistedFactory import ru.touchin.roboswag.mvi_arch.di.ViewModelFactory import ru.touchin.roboswag.mvi_arch.marker.ViewAction @@ -89,7 +89,7 @@ abstract class FullscreenBottomSheetDialog( } override fun setupDialog(dialog: Dialog, style: Int) { - dialog.setOnShowListener(getResizableShowListener()) + dialog.setResizableListener() } override fun onSaveInstanceState(outState: Bundle) { diff --git a/utils/src/main/java/ru/touchin/roboswag/components/utils/ViewExtensions.kt b/utils/src/main/java/ru/touchin/roboswag/components/utils/ViewExtensions.kt index ac3b31a..942facf 100644 --- a/utils/src/main/java/ru/touchin/roboswag/components/utils/ViewExtensions.kt +++ b/utils/src/main/java/ru/touchin/roboswag/components/utils/ViewExtensions.kt @@ -1,6 +1,7 @@ package ru.touchin.roboswag.components.utils import android.app.Activity +import android.app.Dialog import android.content.Context import android.content.DialogInterface import android.content.res.Resources @@ -55,12 +56,13 @@ fun View.showSoftInput() { /** * Returns listener for BottomSheetDialogFragment so that dialog is shifted when the keyboard is opened */ - -fun BottomSheetDialogFragment.getResizableShowListener() = DialogInterface.OnShowListener { dialog -> - (dialog as BottomSheetDialog).findViewById(com.google.android.material.R.id.design_bottom_sheet) - ?.let { BottomSheetBehavior.from(it) } - ?.apply { - state = BottomSheetBehavior.STATE_EXPANDED - skipCollapsed = true - } +fun Dialog.setResizableListener() { + setOnShowListener { dialog -> + (dialog as BottomSheetDialog).findViewById(com.google.android.material.R.id.design_bottom_sheet) + ?.let { BottomSheetBehavior.from(it) } + ?.apply { + state = BottomSheetBehavior.STATE_EXPANDED + skipCollapsed = true + } + } } From 4a5e77a4ef574e03321075b9fdcf757e96b231e8 Mon Sep 17 00:00:00 2001 From: Rinat Nurmukhametov Date: Thu, 7 Oct 2021 18:24:00 +0300 Subject: [PATCH 008/133] delete imports --- .../java/ru/touchin/roboswag/components/utils/ViewExtensions.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/utils/src/main/java/ru/touchin/roboswag/components/utils/ViewExtensions.kt b/utils/src/main/java/ru/touchin/roboswag/components/utils/ViewExtensions.kt index 942facf..4bdca75 100644 --- a/utils/src/main/java/ru/touchin/roboswag/components/utils/ViewExtensions.kt +++ b/utils/src/main/java/ru/touchin/roboswag/components/utils/ViewExtensions.kt @@ -3,14 +3,12 @@ package ru.touchin.roboswag.components.utils import android.app.Activity import android.app.Dialog import android.content.Context -import android.content.DialogInterface import android.content.res.Resources import android.view.View import android.view.inputmethod.InputMethodManager import android.widget.FrameLayout import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetDialog -import com.google.android.material.bottomsheet.BottomSheetDialogFragment /** * Returns string representation of [View]'s ID. From 2890cb96ef883595976481e0556bdbc6700a8e75 Mon Sep 17 00:00:00 2001 From: Rinat Nurmukhametov Date: Mon, 11 Oct 2021 15:57:38 +0300 Subject: [PATCH 009/133] add MviBottomSheet --- .../core/FullscreenBottomSheetDialog.kt | 71 +--------------- .../roboswag/mvi_arch/core/MviBottomSheet.kt | 84 +++++++++++++++++++ 2 files changed, 86 insertions(+), 69 deletions(-) create mode 100644 mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/core/MviBottomSheet.kt diff --git a/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/core/FullscreenBottomSheetDialog.kt b/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/core/FullscreenBottomSheetDialog.kt index 4735f76..bd6f66d 100644 --- a/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/core/FullscreenBottomSheetDialog.kt +++ b/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/core/FullscreenBottomSheetDialog.kt @@ -1,78 +1,24 @@ package ru.touchin.roboswag.mvi_arch.core import android.app.Dialog -import android.content.Context -import android.os.Bundle import android.os.Parcelable -import android.view.LayoutInflater -import android.view.View import android.view.ViewGroup import android.widget.FrameLayout -import androidx.activity.OnBackPressedCallback import androidx.annotation.LayoutRes -import androidx.annotation.MainThread -import androidx.core.os.bundleOf -import androidx.lifecycle.Observer -import androidx.lifecycle.ViewModel -import androidx.lifecycle.ViewModelProvider -import com.google.android.material.bottomsheet.BottomSheetDialogFragment -import ru.touchin.mvi_arch.R import ru.touchin.roboswag.components.utils.setResizableListener -import ru.touchin.roboswag.mvi_arch.di.ViewModelAssistedFactory -import ru.touchin.roboswag.mvi_arch.di.ViewModelFactory import ru.touchin.roboswag.mvi_arch.marker.ViewAction import ru.touchin.roboswag.mvi_arch.marker.ViewState -import javax.inject.Inject abstract class FullscreenBottomSheetDialog( - @LayoutRes private val layoutId: Int -) : BottomSheetDialogFragment(), IMvi + @LayoutRes layoutId: Int +) : MviBottomSheet(layoutId) where NavArgs : Parcelable, Action : ViewAction, State : ViewState, VM : MviViewModel { - @Inject - lateinit var viewModelMap: MutableMap, ViewModelAssistedFactory> - - protected lateinit var state: NavArgs - protected var bottomSheet: FrameLayout? = null - protected abstract fun injectDependencies() - - @MainThread - protected inline fun viewModel(): Lazy = - lazy { - val fragmentArguments = arguments ?: bundleOf() - - ViewModelProvider( - viewModelStore, - ViewModelFactory(viewModelMap, this, fragmentArguments) - ).get(ViewModel::class.java) - } - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setStyle(STYLE_NORMAL, R.style.RoundedCornersBottomSheetDialogTheme) - (savedInstanceState ?: arguments)?.getParcelable(MviFragment.INIT_ARGS_KEY)?.let { savedState -> - state = savedState - } - } - - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View = - inflater.inflate(layoutId, container, false) - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - viewModel.state.observe(viewLifecycleOwner, Observer(this::renderState)) - } - - override fun onAttach(context: Context) { - super.onAttach(context) - injectDependencies() - } - override fun onStart() { super.onStart() @@ -80,21 +26,8 @@ abstract class FullscreenBottomSheetDialog( bottomSheet?.layoutParams?.height = ViewGroup.LayoutParams.MATCH_PARENT } - override fun addOnBackPressedCallback(action: Action) { - requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner, object : OnBackPressedCallback(true) { - override fun handleOnBackPressed() { - dispatchAction(action) - } - }) - } - override fun setupDialog(dialog: Dialog, style: Int) { dialog.setResizableListener() } - override fun onSaveInstanceState(outState: Bundle) { - super.onSaveInstanceState(outState) - outState.putParcelable(MviFragment.INIT_ARGS_KEY, state) - } - } diff --git a/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/core/MviBottomSheet.kt b/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/core/MviBottomSheet.kt new file mode 100644 index 0000000..b1ec18b --- /dev/null +++ b/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/core/MviBottomSheet.kt @@ -0,0 +1,84 @@ +package ru.touchin.roboswag.mvi_arch.core + +import android.content.Context +import android.os.Bundle +import android.os.Parcelable +import android.view.LayoutInflater +import android.view.View +import androidx.lifecycle.Observer +import android.view.ViewGroup +import androidx.activity.OnBackPressedCallback +import androidx.annotation.LayoutRes +import androidx.annotation.MainThread +import androidx.core.os.bundleOf +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import com.google.android.material.bottomsheet.BottomSheetDialogFragment +import ru.touchin.mvi_arch.R +import ru.touchin.roboswag.mvi_arch.di.ViewModelAssistedFactory +import ru.touchin.roboswag.mvi_arch.di.ViewModelFactory +import ru.touchin.roboswag.mvi_arch.marker.ViewAction +import ru.touchin.roboswag.mvi_arch.marker.ViewState +import javax.inject.Inject + +abstract class MviBottomSheet( + @LayoutRes private val layoutId: Int +) : BottomSheetDialogFragment(), IMvi + where NavArgs : Parcelable, + Action : ViewAction, + State : ViewState, + VM : MviViewModel { + + @Inject + lateinit var viewModelMap: MutableMap, ViewModelAssistedFactory> + + protected lateinit var state: NavArgs + + protected abstract fun injectDependencies() + + @MainThread + protected inline fun viewModel(): Lazy = + lazy { + val fragmentArguments = arguments ?: bundleOf() + + ViewModelProvider( + viewModelStore, + ViewModelFactory(viewModelMap, this, fragmentArguments) + ).get(ViewModel::class.java) + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setStyle(STYLE_NORMAL, R.style.RoundedCornersBottomSheetDialogTheme) + (savedInstanceState ?: arguments)?.getParcelable(MviFragment.INIT_ARGS_KEY)?.let { savedState -> + state = savedState + } + } + + override fun onAttach(context: Context) { + super.onAttach(context) + injectDependencies() + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View = + inflater.inflate(layoutId, container, false) + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + viewModel.state.observe(viewLifecycleOwner, Observer(this::renderState)) + } + + override fun addOnBackPressedCallback(action: Action) { + requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner, object : OnBackPressedCallback(true) { + override fun handleOnBackPressed() { + dispatchAction(action) + } + }) + } + + override fun onSaveInstanceState(outState: Bundle) { + super.onSaveInstanceState(outState) + outState.putParcelable(MviFragment.INIT_ARGS_KEY, state) + } + +} From 9c6979ffa2e1a9430c16e2c7d00861b1b8280282 Mon Sep 17 00:00:00 2001 From: Rinat Nurmukhametov Date: Mon, 11 Oct 2021 16:30:00 +0300 Subject: [PATCH 010/133] move to method --- .../java/ru/touchin/roboswag/mvi_arch/core/MviBottomSheet.kt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/core/MviBottomSheet.kt b/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/core/MviBottomSheet.kt index b1ec18b..8acddbc 100644 --- a/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/core/MviBottomSheet.kt +++ b/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/core/MviBottomSheet.kt @@ -65,6 +65,10 @@ abstract class MviBottomSheet( override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + setupRenderState() + } + + private fun setupRenderState() { viewModel.state.observe(viewLifecycleOwner, Observer(this::renderState)) } From 9e8b04e4a05e1bd56d5e2089b2017c84989ff5ac Mon Sep 17 00:00:00 2001 From: Rinat Nurmukhametov Date: Tue, 12 Oct 2021 18:11:53 +0300 Subject: [PATCH 011/133] fix crash with null --- .../java/ru/touchin/roboswag/mvi_arch/core/MviBottomSheet.kt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/core/MviBottomSheet.kt b/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/core/MviBottomSheet.kt index 8acddbc..f68e7ee 100644 --- a/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/core/MviBottomSheet.kt +++ b/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/core/MviBottomSheet.kt @@ -19,6 +19,7 @@ import ru.touchin.roboswag.mvi_arch.di.ViewModelAssistedFactory import ru.touchin.roboswag.mvi_arch.di.ViewModelFactory import ru.touchin.roboswag.mvi_arch.marker.ViewAction import ru.touchin.roboswag.mvi_arch.marker.ViewState +import ru.touchin.roboswag.navigation_base.fragments.EmptyState import javax.inject.Inject abstract class MviBottomSheet( @@ -29,6 +30,10 @@ abstract class MviBottomSheet( State : ViewState, VM : MviViewModel { + init { + arguments = bundleOf(MviFragment.INIT_ARGS_KEY to EmptyState) + } + @Inject lateinit var viewModelMap: MutableMap, ViewModelAssistedFactory> From 8f76718cc54053756aced9f8fe5d1593820ef62d Mon Sep 17 00:00:00 2001 From: Rinat Nurmukhametov Date: Tue, 12 Oct 2021 18:54:17 +0300 Subject: [PATCH 012/133] add getting args by fragment --- .../java/ru/touchin/roboswag/mvi_arch/core/MviBottomSheet.kt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/core/MviBottomSheet.kt b/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/core/MviBottomSheet.kt index f68e7ee..347625e 100644 --- a/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/core/MviBottomSheet.kt +++ b/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/core/MviBottomSheet.kt @@ -34,6 +34,10 @@ abstract class MviBottomSheet( arguments = bundleOf(MviFragment.INIT_ARGS_KEY to EmptyState) } + protected val navArgs: NavArgs by lazy(mode = LazyThreadSafetyMode.NONE) { + arguments?.getParcelable(MviFragment.INIT_ARGS_KEY) as NavArgs + } + @Inject lateinit var viewModelMap: MutableMap, ViewModelAssistedFactory> From 919445de07bee2db31f6877bcfcf863fb8c01040 Mon Sep 17 00:00:00 2001 From: Konstantin Kuzhim Date: Mon, 8 Nov 2021 22:30:32 +0700 Subject: [PATCH 013/133] add logging if non-null field is null --- .../touchin/templates/logansquare/LoganSquareJsonModel.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/api-logansquare/src/main/java/ru/touchin/templates/logansquare/LoganSquareJsonModel.java b/api-logansquare/src/main/java/ru/touchin/templates/logansquare/LoganSquareJsonModel.java index 3dbd536..b989c31 100644 --- a/api-logansquare/src/main/java/ru/touchin/templates/logansquare/LoganSquareJsonModel.java +++ b/api-logansquare/src/main/java/ru/touchin/templates/logansquare/LoganSquareJsonModel.java @@ -22,6 +22,7 @@ package ru.touchin.templates.logansquare; import androidx.annotation.Nullable; import ru.touchin.roboswag.core.log.Lc; +import ru.touchin.roboswag.core.log.LcGroup; import ru.touchin.templates.ApiModel; /** @@ -38,7 +39,9 @@ public abstract class LoganSquareJsonModel extends ApiModel { */ protected static void validateNotNull(@Nullable final Object object) throws ValidationException { if (object == null) { - throw new ValidationException("Not nullable object is null or missed at " + Lc.getCodePoint(null, 1)); + ValidationException exception = new ValidationException("Not nullable object is null or missed at " + Lc.getCodePoint(null, 1)); + LcGroup.API_VALIDATION.e(exception, "Invalid item"); + throw exception; } } From 0f890cbd1ea0775bc01f58a9590bc973c9b7a2d5 Mon Sep 17 00:00:00 2001 From: Konstantin Kuzhim Date: Mon, 8 Nov 2021 22:31:45 +0700 Subject: [PATCH 014/133] replace multi-log stacktrace printing with one-statement logging --- .../roboswag/core/log/ConsoleLogProcessor.java | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/logging/src/main/java/ru/touchin/roboswag/core/log/ConsoleLogProcessor.java b/logging/src/main/java/ru/touchin/roboswag/core/log/ConsoleLogProcessor.java index 8e00787..4f60198 100644 --- a/logging/src/main/java/ru/touchin/roboswag/core/log/ConsoleLogProcessor.java +++ b/logging/src/main/java/ru/touchin/roboswag/core/log/ConsoleLogProcessor.java @@ -29,8 +29,6 @@ import android.util.Log; */ public class ConsoleLogProcessor extends LogProcessor { - private static final int MAX_LOG_LENGTH = 4000; - public ConsoleLogProcessor(@NonNull final LcLevel lclevel) { super(lclevel); } @@ -46,18 +44,8 @@ public class ConsoleLogProcessor extends LogProcessor { public void processLogMessage(@NonNull final LcGroup group, @NonNull final LcLevel level, @NonNull final String tag, @NonNull final String message, @Nullable final Throwable throwable) { final String messageToLog = normalize(message + (throwable != null ? '\n' + Log.getStackTraceString(throwable) : "")); - final int length = messageToLog.length(); - for (int i = 0; i < length; i++) { - int newline = messageToLog.indexOf('\n', i); - newline = newline != -1 ? newline : length; - do { - final int end = Math.min(newline, i + MAX_LOG_LENGTH); - Log.println(level.getPriority(), tag, messageToLog.substring(i, end)); - i = end; - } - while (i < newline); - } + Log.println(level.getPriority(), tag, messageToLog); } } From ad29d573770cef8b8d0bb19f93fa844b7cea11ea Mon Sep 17 00:00:00 2001 From: Rinat Nurmukhametov Date: Thu, 18 Nov 2021 03:35:33 +0300 Subject: [PATCH 015/133] delete staples --- .../recyclerview_decorators/decorators/DividerItemDecoration.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/recyclerview-decorators/src/main/java/ru/touchin/roboswag/recyclerview_decorators/decorators/DividerItemDecoration.kt b/recyclerview-decorators/src/main/java/ru/touchin/roboswag/recyclerview_decorators/decorators/DividerItemDecoration.kt index 386dd77..0a20bdd 100644 --- a/recyclerview-decorators/src/main/java/ru/touchin/roboswag/recyclerview_decorators/decorators/DividerItemDecoration.kt +++ b/recyclerview-decorators/src/main/java/ru/touchin/roboswag/recyclerview_decorators/decorators/DividerItemDecoration.kt @@ -58,7 +58,7 @@ abstract class DividerItemDecoration( divider.setBounds( bounds.left + startMargin, top, - bounds.right - (endMargin.toFloat().px).toInt(), + bounds.right - endMargin.toFloat().px.toInt(), bottom ) divider.draw(canvas) From a6107e2e7a1fc820cca15f8d00a19061b719daaf Mon Sep 17 00:00:00 2001 From: Kirill Nayduik Date: Tue, 23 Nov 2021 11:31:31 +0300 Subject: [PATCH 016/133] Invoke onClick action with throttle effect --- .../src/main/java/ru/touchin/utils/ActionThrottler.kt | 6 ++++-- .../touchin/roboswag/components/utils/spans/SpanUtils.kt | 9 ++++++++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/kotlin-extensions/src/main/java/ru/touchin/utils/ActionThrottler.kt b/kotlin-extensions/src/main/java/ru/touchin/utils/ActionThrottler.kt index 8721e60..3df2787 100644 --- a/kotlin-extensions/src/main/java/ru/touchin/utils/ActionThrottler.kt +++ b/kotlin-extensions/src/main/java/ru/touchin/utils/ActionThrottler.kt @@ -9,13 +9,15 @@ object ActionThrottler { // action invoking start user may be in time to click and launch action again private const val PREVENTION_OF_CLICK_AGAIN_COEFFICIENT = 2 private const val DELAY_MS = PREVENTION_OF_CLICK_AGAIN_COEFFICIENT * RIPPLE_EFFECT_DELAY_MS + + const val DEFAULT_THROTTLE_DELAY = 500L private var lastActionTime = 0L - fun throttleAction(action: () -> Unit): Boolean { + fun throttleAction(throttleDelay: Long = DELAY_MS, action: () -> Unit): Boolean { val currentTime = SystemClock.elapsedRealtime() val diff = currentTime - lastActionTime - return if (diff >= DELAY_MS) { + return if (diff >= throttleDelay) { lastActionTime = currentTime action.invoke() true diff --git a/utils/src/main/java/ru/touchin/roboswag/components/utils/spans/SpanUtils.kt b/utils/src/main/java/ru/touchin/roboswag/components/utils/spans/SpanUtils.kt index 6d60745..e87b267 100644 --- a/utils/src/main/java/ru/touchin/roboswag/components/utils/spans/SpanUtils.kt +++ b/utils/src/main/java/ru/touchin/roboswag/components/utils/spans/SpanUtils.kt @@ -8,8 +8,13 @@ import android.text.style.URLSpan import android.text.util.Linkify import android.view.View import androidx.annotation.ColorInt +import androidx.core.os.HandlerCompat.postDelayed import androidx.core.text.HtmlCompat +import ru.touchin.extensions.RIPPLE_EFFECT_DELAY_MS import ru.touchin.extensions.indexesOf +import ru.touchin.extensions.setOnRippleClickListener +import ru.touchin.utils.ActionThrottler +import ru.touchin.utils.ActionThrottler.DEFAULT_THROTTLE_DELAY /** * Convert text with 'href' tags and raw links to spanned text with clickable URLSpan. @@ -66,7 +71,9 @@ fun CharSequence.toClickableSubstringText( indexesOf(substring)?.let { (startSpan, endSpan) -> setSpan(object : ClickableSpan() { override fun onClick(widget: View) { - clickAction.invoke() + ActionThrottler.throttleAction(DEFAULT_THROTTLE_DELAY) { + clickAction.invoke() + } } override fun updateDrawState(ds: TextPaint) { From d83ab89739e3c9bf0d58f56f36a847267eade349 Mon Sep 17 00:00:00 2001 From: Kirill Nayduik Date: Tue, 23 Nov 2021 11:34:40 +0300 Subject: [PATCH 017/133] Fix naming --- .../src/main/java/ru/touchin/utils/ActionThrottler.kt | 2 +- .../touchin/roboswag/components/utils/spans/SpanUtils.kt | 7 ++----- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/kotlin-extensions/src/main/java/ru/touchin/utils/ActionThrottler.kt b/kotlin-extensions/src/main/java/ru/touchin/utils/ActionThrottler.kt index 3df2787..da3e1b5 100644 --- a/kotlin-extensions/src/main/java/ru/touchin/utils/ActionThrottler.kt +++ b/kotlin-extensions/src/main/java/ru/touchin/utils/ActionThrottler.kt @@ -10,7 +10,7 @@ object ActionThrottler { private const val PREVENTION_OF_CLICK_AGAIN_COEFFICIENT = 2 private const val DELAY_MS = PREVENTION_OF_CLICK_AGAIN_COEFFICIENT * RIPPLE_EFFECT_DELAY_MS - const val DEFAULT_THROTTLE_DELAY = 500L + const val DEFAULT_THROTTLE_DELAY_MS = 500L private var lastActionTime = 0L fun throttleAction(throttleDelay: Long = DELAY_MS, action: () -> Unit): Boolean { diff --git a/utils/src/main/java/ru/touchin/roboswag/components/utils/spans/SpanUtils.kt b/utils/src/main/java/ru/touchin/roboswag/components/utils/spans/SpanUtils.kt index e87b267..f331be0 100644 --- a/utils/src/main/java/ru/touchin/roboswag/components/utils/spans/SpanUtils.kt +++ b/utils/src/main/java/ru/touchin/roboswag/components/utils/spans/SpanUtils.kt @@ -8,13 +8,10 @@ import android.text.style.URLSpan import android.text.util.Linkify import android.view.View import androidx.annotation.ColorInt -import androidx.core.os.HandlerCompat.postDelayed import androidx.core.text.HtmlCompat -import ru.touchin.extensions.RIPPLE_EFFECT_DELAY_MS import ru.touchin.extensions.indexesOf -import ru.touchin.extensions.setOnRippleClickListener import ru.touchin.utils.ActionThrottler -import ru.touchin.utils.ActionThrottler.DEFAULT_THROTTLE_DELAY +import ru.touchin.utils.ActionThrottler.DEFAULT_THROTTLE_DELAY_MS /** * Convert text with 'href' tags and raw links to spanned text with clickable URLSpan. @@ -71,7 +68,7 @@ fun CharSequence.toClickableSubstringText( indexesOf(substring)?.let { (startSpan, endSpan) -> setSpan(object : ClickableSpan() { override fun onClick(widget: View) { - ActionThrottler.throttleAction(DEFAULT_THROTTLE_DELAY) { + ActionThrottler.throttleAction(DEFAULT_THROTTLE_DELAY_MS) { clickAction.invoke() } } From e023c57afdd5f01013bad210cfc1ca5b3302fe51 Mon Sep 17 00:00:00 2001 From: Kirill Nayduik Date: Tue, 7 Dec 2021 15:30:51 +0300 Subject: [PATCH 018/133] Change type of argument for KeyboardBehaviorDetector --- .../keyboard_resizeable/KeyboardBehaviorDetector.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/navigation-base/src/main/java/ru/touchin/roboswag/navigation_base/keyboard_resizeable/KeyboardBehaviorDetector.kt b/navigation-base/src/main/java/ru/touchin/roboswag/navigation_base/keyboard_resizeable/KeyboardBehaviorDetector.kt index 13e6088..7b711be 100644 --- a/navigation-base/src/main/java/ru/touchin/roboswag/navigation_base/keyboard_resizeable/KeyboardBehaviorDetector.kt +++ b/navigation-base/src/main/java/ru/touchin/roboswag/navigation_base/keyboard_resizeable/KeyboardBehaviorDetector.kt @@ -2,10 +2,10 @@ package ru.touchin.roboswag.navigation_base.keyboard_resizeable import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat +import androidx.fragment.app.FragmentActivity import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleObserver import androidx.lifecycle.OnLifecycleEvent -import ru.touchin.roboswag.navigation_base.activities.BaseActivity /** * This detector NOT detect landscape fullscreen keyboard @@ -17,7 +17,7 @@ typealias OnHideListener = () -> Unit typealias OnShowListener = (Int) -> Unit class KeyboardBehaviorDetector( - activity: BaseActivity + activity: FragmentActivity ) : LifecycleObserver { private val view = activity.window.decorView From 9a4068f33745ae6edaf4bc5def897b4e5eaa81f7 Mon Sep 17 00:00:00 2001 From: Kirill Nayduik Date: Tue, 14 Dec 2021 18:33:57 +0300 Subject: [PATCH 019/133] Add FragmentKeyboardListenerObserver for detecting keyboard events from Fragment --- .../core/MviKeyboardResizableFragment.kt | 4 +++ .../keyboard_resizeable/Fragment.kt | 16 +++++++++ .../FragmentKeyboardListenerObserver.kt | 33 +++++++++++++++++++ .../KeyboardResizeableFragment.kt | 4 +++ .../StatefulKeyboardResizeableFragment.kt | 4 +++ 5 files changed, 61 insertions(+) create mode 100644 navigation-base/src/main/java/ru/touchin/roboswag/navigation_base/keyboard_resizeable/Fragment.kt create mode 100644 navigation-base/src/main/java/ru/touchin/roboswag/navigation_base/keyboard_resizeable/FragmentKeyboardListenerObserver.kt diff --git a/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/core/MviKeyboardResizableFragment.kt b/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/core/MviKeyboardResizableFragment.kt index 1c30f88..dead6b4 100644 --- a/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/core/MviKeyboardResizableFragment.kt +++ b/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/core/MviKeyboardResizableFragment.kt @@ -18,6 +18,10 @@ import ru.touchin.roboswag.navigation_base.keyboard_resizeable.OnShowListener * Used to detect IME events (show, hide) */ +@Deprecated( + "You have to be inherited from this class to be able to implement keyboard detection", + replaceWith = ReplaceWith("fragment.addKeyboardListener") +) abstract class MviKeyboardResizableFragment( @LayoutRes layout: Int ) : MviFragment(layout) diff --git a/navigation-base/src/main/java/ru/touchin/roboswag/navigation_base/keyboard_resizeable/Fragment.kt b/navigation-base/src/main/java/ru/touchin/roboswag/navigation_base/keyboard_resizeable/Fragment.kt new file mode 100644 index 0000000..19077b3 --- /dev/null +++ b/navigation-base/src/main/java/ru/touchin/roboswag/navigation_base/keyboard_resizeable/Fragment.kt @@ -0,0 +1,16 @@ +package ru.touchin.roboswag.navigation_base.keyboard_resizeable + +import androidx.fragment.app.Fragment + +fun Fragment.addKeyboardListener( + onShow: OnShowListener? = null, + onHide: OnHideListener? = null +) { + lifecycle.addObserver( + FragmentKeyboardListenerObserver( + fragment = this, + onShow = onShow, + onHide = onHide + ) + ) +} diff --git a/navigation-base/src/main/java/ru/touchin/roboswag/navigation_base/keyboard_resizeable/FragmentKeyboardListenerObserver.kt b/navigation-base/src/main/java/ru/touchin/roboswag/navigation_base/keyboard_resizeable/FragmentKeyboardListenerObserver.kt new file mode 100644 index 0000000..d0e5545 --- /dev/null +++ b/navigation-base/src/main/java/ru/touchin/roboswag/navigation_base/keyboard_resizeable/FragmentKeyboardListenerObserver.kt @@ -0,0 +1,33 @@ +package ru.touchin.roboswag.navigation_base.keyboard_resizeable + +import androidx.fragment.app.Fragment +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleObserver +import androidx.lifecycle.OnLifecycleEvent +import ru.touchin.roboswag.navigation_base.activities.BaseActivity + +/** + * Observer for adding listeners for activity's keyboardBehaviorDetector with lifecycle awareness + */ +class FragmentKeyboardListenerObserver( + fragment: Fragment, + private val onShow: OnShowListener? = null, + private val onHide: OnHideListener? = null +) : LifecycleObserver { + + private val keyboardDetector = (fragment.requireActivity() as? BaseActivity) + ?.keyboardBehaviorDetector + ?: error("Fragment must be launched from BaseActivity") + + @OnLifecycleEvent(Lifecycle.Event.ON_START) + fun addListener() { + onShow?.let(keyboardDetector::addOnShowListener) + onHide?.let(keyboardDetector::addOnHideListener) + } + + @OnLifecycleEvent(Lifecycle.Event.ON_STOP) + fun removeListener() { + onShow?.let(keyboardDetector::removeOnShowListener) + onHide?.let(keyboardDetector::removeOnHideListener) + } +} diff --git a/navigation-base/src/main/java/ru/touchin/roboswag/navigation_base/keyboard_resizeable/KeyboardResizeableFragment.kt b/navigation-base/src/main/java/ru/touchin/roboswag/navigation_base/keyboard_resizeable/KeyboardResizeableFragment.kt index 7043d13..7ca7ee6 100644 --- a/navigation-base/src/main/java/ru/touchin/roboswag/navigation_base/keyboard_resizeable/KeyboardResizeableFragment.kt +++ b/navigation-base/src/main/java/ru/touchin/roboswag/navigation_base/keyboard_resizeable/KeyboardResizeableFragment.kt @@ -9,6 +9,10 @@ import ru.touchin.roboswag.navigation_base.activities.BaseActivity import ru.touchin.roboswag.navigation_base.activities.OnBackPressedListener import ru.touchin.roboswag.navigation_base.fragments.BaseFragment +@Deprecated( + "You have to be inherited from this class to be able to implement keyboard detection", + replaceWith = ReplaceWith("fragment.addKeyboardListener") +) abstract class KeyboardResizeableFragment( @LayoutRes layoutRes: Int ) : BaseFragment( diff --git a/navigation-base/src/main/java/ru/touchin/roboswag/navigation_base/keyboard_resizeable/StatefulKeyboardResizeableFragment.kt b/navigation-base/src/main/java/ru/touchin/roboswag/navigation_base/keyboard_resizeable/StatefulKeyboardResizeableFragment.kt index d14ec8e..3125fdf 100644 --- a/navigation-base/src/main/java/ru/touchin/roboswag/navigation_base/keyboard_resizeable/StatefulKeyboardResizeableFragment.kt +++ b/navigation-base/src/main/java/ru/touchin/roboswag/navigation_base/keyboard_resizeable/StatefulKeyboardResizeableFragment.kt @@ -16,6 +16,10 @@ import ru.touchin.roboswag.navigation_base.fragments.StatefulFragment * Used to detect IME events (show, hide) */ +@Deprecated( + "You have to be inherited from this class to be able to implement keyboard detection", + replaceWith = ReplaceWith("fragment.addKeyboardListener") +) abstract class StatefulKeyboardResizeableFragment( @LayoutRes layoutRes: Int ) : StatefulFragment( From 3476324b8485175ce48fd39a5f6fa16f102ad03c Mon Sep 17 00:00:00 2001 From: Kirill Nayduik Date: Mon, 17 Jan 2022 17:55:27 +0300 Subject: [PATCH 020/133] Add delegates that allows to lazily initialize value on certain lifecycle event --- .../touchin/lifecycle/LifecycleDelegates.kt | 37 +++++++++++++++++++ .../lifecycle/extensions/LifecycleOwner.kt | 31 ++++++++++++++++ 2 files changed, 68 insertions(+) create mode 100644 lifecycle/src/main/java/ru/touchin/lifecycle/LifecycleDelegates.kt create mode 100644 lifecycle/src/main/java/ru/touchin/lifecycle/extensions/LifecycleOwner.kt diff --git a/lifecycle/src/main/java/ru/touchin/lifecycle/LifecycleDelegates.kt b/lifecycle/src/main/java/ru/touchin/lifecycle/LifecycleDelegates.kt new file mode 100644 index 0000000..bd29666 --- /dev/null +++ b/lifecycle/src/main/java/ru/touchin/lifecycle/LifecycleDelegates.kt @@ -0,0 +1,37 @@ +package ru.touchin.lifecycle + +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleEventObserver +import androidx.lifecycle.LifecycleOwner +import java.lang.IllegalStateException +import kotlin.properties.ReadOnlyProperty +import kotlin.reflect.KProperty + +/** + * Delegate that allows to lazily initialize value on certain lifecycle event + * @param initializeEvent is event when value should be initialize + * @param initializer callback that handles value initialization + */ + +class OnLifecycle( + private val lifecycleOwner: R, + private val initializeEvent: Lifecycle.Event, + private val initializer: (R) -> T +) : ReadOnlyProperty { + + private var value: T? = null + + init { + lifecycleOwner.lifecycle.addObserver(object : LifecycleEventObserver { + override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) { + if (initializeEvent == event && value == null) { + value = initializer.invoke(lifecycleOwner) + } + } + }) + } + + override fun getValue(thisRef: R, property: KProperty<*>) = value + ?: throw IllegalStateException("Can't get access to value before $initializeEvent. Current is ${thisRef.lifecycle.currentState}") + +} diff --git a/lifecycle/src/main/java/ru/touchin/lifecycle/extensions/LifecycleOwner.kt b/lifecycle/src/main/java/ru/touchin/lifecycle/extensions/LifecycleOwner.kt new file mode 100644 index 0000000..8b479d7 --- /dev/null +++ b/lifecycle/src/main/java/ru/touchin/lifecycle/extensions/LifecycleOwner.kt @@ -0,0 +1,31 @@ +package ru.touchin.lifecycle.extensions + +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleOwner +import ru.touchin.lifecycle.OnLifecycle +import kotlin.properties.ReadOnlyProperty + +fun R.onCreateEvent( + initializer: (R) -> T +): ReadOnlyProperty { + return OnLifecycle(this, Lifecycle.Event.ON_CREATE, initializer) +} + +fun R.onStartEvent( + initializer: (R) -> T +): ReadOnlyProperty { + return OnLifecycle(this, Lifecycle.Event.ON_START, initializer) +} + +fun R.onResumeEvent( + initializer: (R) -> T +): ReadOnlyProperty { + return OnLifecycle(this, Lifecycle.Event.ON_RESUME, initializer) +} + +fun R.onLifecycle( + initializeEvent: Lifecycle.Event, + initializer: (R) -> T +): ReadOnlyProperty { + return OnLifecycle(this, initializeEvent, initializer) +} From f58bdd92899306b31655ec85118de21447f3147e Mon Sep 17 00:00:00 2001 From: Kirill Nayduik Date: Wed, 19 Jan 2022 13:14:47 +0300 Subject: [PATCH 021/133] Fix detekt issues --- .../{LifecycleDelegates.kt => OnLifecycle.kt} | 0 .../lifecycle/extensions/LifecycleOwner.kt | 16 ++++------------ 2 files changed, 4 insertions(+), 12 deletions(-) rename lifecycle/src/main/java/ru/touchin/lifecycle/{LifecycleDelegates.kt => OnLifecycle.kt} (100%) diff --git a/lifecycle/src/main/java/ru/touchin/lifecycle/LifecycleDelegates.kt b/lifecycle/src/main/java/ru/touchin/lifecycle/OnLifecycle.kt similarity index 100% rename from lifecycle/src/main/java/ru/touchin/lifecycle/LifecycleDelegates.kt rename to lifecycle/src/main/java/ru/touchin/lifecycle/OnLifecycle.kt diff --git a/lifecycle/src/main/java/ru/touchin/lifecycle/extensions/LifecycleOwner.kt b/lifecycle/src/main/java/ru/touchin/lifecycle/extensions/LifecycleOwner.kt index 8b479d7..c70329c 100644 --- a/lifecycle/src/main/java/ru/touchin/lifecycle/extensions/LifecycleOwner.kt +++ b/lifecycle/src/main/java/ru/touchin/lifecycle/extensions/LifecycleOwner.kt @@ -7,25 +7,17 @@ import kotlin.properties.ReadOnlyProperty fun R.onCreateEvent( initializer: (R) -> T -): ReadOnlyProperty { - return OnLifecycle(this, Lifecycle.Event.ON_CREATE, initializer) -} +): ReadOnlyProperty = OnLifecycle(this, Lifecycle.Event.ON_CREATE, initializer) fun R.onStartEvent( initializer: (R) -> T -): ReadOnlyProperty { - return OnLifecycle(this, Lifecycle.Event.ON_START, initializer) -} +): ReadOnlyProperty = OnLifecycle(this, Lifecycle.Event.ON_START, initializer) fun R.onResumeEvent( initializer: (R) -> T -): ReadOnlyProperty { - return OnLifecycle(this, Lifecycle.Event.ON_RESUME, initializer) -} +): ReadOnlyProperty = OnLifecycle(this, Lifecycle.Event.ON_RESUME, initializer) fun R.onLifecycle( initializeEvent: Lifecycle.Event, initializer: (R) -> T -): ReadOnlyProperty { - return OnLifecycle(this, initializeEvent, initializer) -} +): ReadOnlyProperty = OnLifecycle(this, initializeEvent, initializer) From a27814fca9e03f28688f3b6d2081e6bd12b993ba Mon Sep 17 00:00:00 2001 From: Kirill Nayduik Date: Tue, 25 Jan 2022 13:47:10 +0300 Subject: [PATCH 022/133] Add EllipsizeSpannableTextView and MultipleActionTextView from Petshop --- .../text_view/EllipsizeSpannableTextView.kt | 42 +++++++++++ .../views/text_view/MultipleActionTextView.kt | 71 +++++++++++++++++++ views/src/main/res/values/attrs.xml | 27 +++++-- 3 files changed, 134 insertions(+), 6 deletions(-) create mode 100644 views/src/main/java/ru/touchin/roboswag/views/text_view/EllipsizeSpannableTextView.kt create mode 100644 views/src/main/java/ru/touchin/roboswag/views/text_view/MultipleActionTextView.kt diff --git a/views/src/main/java/ru/touchin/roboswag/views/text_view/EllipsizeSpannableTextView.kt b/views/src/main/java/ru/touchin/roboswag/views/text_view/EllipsizeSpannableTextView.kt new file mode 100644 index 0000000..c13c4e8 --- /dev/null +++ b/views/src/main/java/ru/touchin/roboswag/views/text_view/EllipsizeSpannableTextView.kt @@ -0,0 +1,42 @@ +package ru.touchin.roboswag.views.text_view + +import android.content.Context +import android.text.Layout +import android.text.SpannableStringBuilder +import android.util.AttributeSet +import androidx.appcompat.widget.AppCompatTextView + +class EllipsizeSpannableTextView @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 +) : AppCompatTextView(context, attrs, defStyleAttr) { + + companion object { + private const val THREE_DOTS = "..." + private const val THREE_DOTS_LENGTH = THREE_DOTS.length + } + + private var spannableStringBuilder = SpannableStringBuilder() + + override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { + val layout: Layout = layout + + if (layout.lineCount >= maxLines) { + val charSequence = text + val lastCharDown: Int = layout.getLineVisibleEnd(maxLines - 1) + + if (lastCharDown >= THREE_DOTS_LENGTH && charSequence.length > lastCharDown) { + spannableStringBuilder.clear() + + spannableStringBuilder + .append(charSequence.subSequence(0, lastCharDown - THREE_DOTS_LENGTH)) + .append(THREE_DOTS) + + text = spannableStringBuilder + } + } + + super.onLayout(changed, left, top, right, bottom) + } +} diff --git a/views/src/main/java/ru/touchin/roboswag/views/text_view/MultipleActionTextView.kt b/views/src/main/java/ru/touchin/roboswag/views/text_view/MultipleActionTextView.kt new file mode 100644 index 0000000..873ce4f --- /dev/null +++ b/views/src/main/java/ru/touchin/roboswag/views/text_view/MultipleActionTextView.kt @@ -0,0 +1,71 @@ +package ru.touchin.roboswag.views.text_view + +import android.content.Context +import android.graphics.Color +import android.text.Spanned +import android.text.TextPaint +import android.text.style.ClickableSpan +import android.util.AttributeSet +import android.view.View +import androidx.annotation.ColorInt +import androidx.appcompat.widget.AppCompatTextView +import androidx.core.content.withStyledAttributes +import androidx.core.text.toSpannable +import ru.touchin.extensions.indexesOf +import ru.touchin.roboswag.components.utils.movementmethods.ClickableMovementMethod +import ru.touchin.roboswag.views.R + +class MultipleActionTextView @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 +) : AppCompatTextView(context, attrs, defStyleAttr) { + + private val onClickActions = mutableListOf() + + @ColorInt + private var actionColor = currentTextColor + + private var isUnderlineText = false + + init { + isClickable = false + isLongClickable = false + highlightColor = Color.TRANSPARENT + movementMethod = ClickableMovementMethod + + context.withStyledAttributes(attrs, R.styleable.MultipleActionTextView, defStyleAttr, 0) { + actionColor = getColor(R.styleable.MultipleActionTextView_actionColor, currentTextColor) + isUnderlineText = getBoolean(R.styleable.MultipleActionTextView_isUnderlineText, false) + } + } + + fun configure(builderAction: MutableList.() -> Unit) { + onClickActions.apply(builderAction) + applyActionSpans() + } + + private fun applyActionSpans() { + text = text.toSpannable().apply { + onClickActions.forEach { (substring, action) -> + + indexesOf(substring)?.let { (startSpan, endSpan) -> + + setSpan(object : ClickableSpan() { + override fun onClick(widget: View) { + action.invoke() + } + + override fun updateDrawState(ds: TextPaint) { + super.updateDrawState(ds) + ds.isUnderlineText = isUnderlineText + ds.color = actionColor + } + }, startSpan, endSpan, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) + } + } + } + } + + data class SubstringClickAction(val substring: String, val action: () -> Unit) +} diff --git a/views/src/main/res/values/attrs.xml b/views/src/main/res/values/attrs.xml index db5b1a7..f6b0762 100644 --- a/views/src/main/res/values/attrs.xml +++ b/views/src/main/res/values/attrs.xml @@ -50,14 +50,29 @@ - - - - - - + + + + + + + + + + + + + + + + + + + + + From afca6eab5f866b620a70e2c21450451431fe9b6c Mon Sep 17 00:00:00 2001 From: Kirill Nayduik Date: Tue, 25 Jan 2022 14:41:56 +0300 Subject: [PATCH 023/133] Remove redundant variable --- .../roboswag/views/text_view/EllipsizeSpannableTextView.kt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/views/src/main/java/ru/touchin/roboswag/views/text_view/EllipsizeSpannableTextView.kt b/views/src/main/java/ru/touchin/roboswag/views/text_view/EllipsizeSpannableTextView.kt index c13c4e8..9f48069 100644 --- a/views/src/main/java/ru/touchin/roboswag/views/text_view/EllipsizeSpannableTextView.kt +++ b/views/src/main/java/ru/touchin/roboswag/views/text_view/EllipsizeSpannableTextView.kt @@ -6,7 +6,7 @@ import android.text.SpannableStringBuilder import android.util.AttributeSet import androidx.appcompat.widget.AppCompatTextView -class EllipsizeSpannableTextView @JvmOverloads constructor( +open class EllipsizeSpannableTextView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 @@ -20,8 +20,6 @@ class EllipsizeSpannableTextView @JvmOverloads constructor( private var spannableStringBuilder = SpannableStringBuilder() override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { - val layout: Layout = layout - if (layout.lineCount >= maxLines) { val charSequence = text val lastCharDown: Int = layout.getLineVisibleEnd(maxLines - 1) From 4eb3683e417033faf60fa821ccc168b10fc185f2 Mon Sep 17 00:00:00 2001 From: Kirill Nayduik Date: Tue, 25 Jan 2022 15:10:57 +0300 Subject: [PATCH 024/133] Enable MultipleActionTextView to ellipsize --- .../roboswag/views/text_view/EllipsizeSpannableTextView.kt | 1 - .../touchin/roboswag/views/text_view/MultipleActionTextView.kt | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/views/src/main/java/ru/touchin/roboswag/views/text_view/EllipsizeSpannableTextView.kt b/views/src/main/java/ru/touchin/roboswag/views/text_view/EllipsizeSpannableTextView.kt index 9f48069..238712f 100644 --- a/views/src/main/java/ru/touchin/roboswag/views/text_view/EllipsizeSpannableTextView.kt +++ b/views/src/main/java/ru/touchin/roboswag/views/text_view/EllipsizeSpannableTextView.kt @@ -1,7 +1,6 @@ package ru.touchin.roboswag.views.text_view import android.content.Context -import android.text.Layout import android.text.SpannableStringBuilder import android.util.AttributeSet import androidx.appcompat.widget.AppCompatTextView diff --git a/views/src/main/java/ru/touchin/roboswag/views/text_view/MultipleActionTextView.kt b/views/src/main/java/ru/touchin/roboswag/views/text_view/MultipleActionTextView.kt index 873ce4f..f774f98 100644 --- a/views/src/main/java/ru/touchin/roboswag/views/text_view/MultipleActionTextView.kt +++ b/views/src/main/java/ru/touchin/roboswag/views/text_view/MultipleActionTextView.kt @@ -8,7 +8,6 @@ import android.text.style.ClickableSpan import android.util.AttributeSet import android.view.View import androidx.annotation.ColorInt -import androidx.appcompat.widget.AppCompatTextView import androidx.core.content.withStyledAttributes import androidx.core.text.toSpannable import ru.touchin.extensions.indexesOf @@ -19,7 +18,7 @@ class MultipleActionTextView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 -) : AppCompatTextView(context, attrs, defStyleAttr) { +) : EllipsizeSpannableTextView(context, attrs, defStyleAttr) { private val onClickActions = mutableListOf() From 66e6d9e563ede71d5f0309af820b4ffda98af3e5 Mon Sep 17 00:00:00 2001 From: Kirill Nayduik Date: Tue, 25 Jan 2022 15:22:12 +0300 Subject: [PATCH 025/133] Add comments for new text view classes --- .../roboswag/views/text_view/EllipsizeSpannableTextView.kt | 4 ++++ .../roboswag/views/text_view/MultipleActionTextView.kt | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/views/src/main/java/ru/touchin/roboswag/views/text_view/EllipsizeSpannableTextView.kt b/views/src/main/java/ru/touchin/roboswag/views/text_view/EllipsizeSpannableTextView.kt index 238712f..f1d45ee 100644 --- a/views/src/main/java/ru/touchin/roboswag/views/text_view/EllipsizeSpannableTextView.kt +++ b/views/src/main/java/ru/touchin/roboswag/views/text_view/EllipsizeSpannableTextView.kt @@ -5,6 +5,10 @@ import android.text.SpannableStringBuilder import android.util.AttributeSet import androidx.appcompat.widget.AppCompatTextView +/** + * A [android.widget.TextView] witch support text as SpannableString with ellipsize implementation + * @author Rinat Nurmukhametov + */ open class EllipsizeSpannableTextView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, diff --git a/views/src/main/java/ru/touchin/roboswag/views/text_view/MultipleActionTextView.kt b/views/src/main/java/ru/touchin/roboswag/views/text_view/MultipleActionTextView.kt index f774f98..f008cc9 100644 --- a/views/src/main/java/ru/touchin/roboswag/views/text_view/MultipleActionTextView.kt +++ b/views/src/main/java/ru/touchin/roboswag/views/text_view/MultipleActionTextView.kt @@ -14,6 +14,10 @@ import ru.touchin.extensions.indexesOf import ru.touchin.roboswag.components.utils.movementmethods.ClickableMovementMethod import ru.touchin.roboswag.views.R +/** + * A [android.widget.TextView] which support implementation of invoking actions on click of certain substrings + * @author Grigorii Leontev + */ class MultipleActionTextView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, From 3d6d2aa233741441c15c4ac9e04105a9a0c314a4 Mon Sep 17 00:00:00 2001 From: Kirill Nayduik Date: Tue, 25 Jan 2022 15:32:18 +0300 Subject: [PATCH 026/133] Comment typo fix --- .../roboswag/views/text_view/EllipsizeSpannableTextView.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/views/src/main/java/ru/touchin/roboswag/views/text_view/EllipsizeSpannableTextView.kt b/views/src/main/java/ru/touchin/roboswag/views/text_view/EllipsizeSpannableTextView.kt index f1d45ee..b4d972c 100644 --- a/views/src/main/java/ru/touchin/roboswag/views/text_view/EllipsizeSpannableTextView.kt +++ b/views/src/main/java/ru/touchin/roboswag/views/text_view/EllipsizeSpannableTextView.kt @@ -6,7 +6,7 @@ import android.util.AttributeSet import androidx.appcompat.widget.AppCompatTextView /** - * A [android.widget.TextView] witch support text as SpannableString with ellipsize implementation + * A [android.widget.TextView] which support text as SpannableString with ellipsize implementation * @author Rinat Nurmukhametov */ open class EllipsizeSpannableTextView @JvmOverloads constructor( From e9249e88a933c54967dec82d96a68439cb868e32 Mon Sep 17 00:00:00 2001 From: Kirill Nayduik Date: Tue, 25 Jan 2022 18:27:19 +0300 Subject: [PATCH 027/133] Add ability to set style instead of color in ActionTextViews --- .../java/ru/touchin/extensions/TypedArray.kt | 7 +++ .../components/utils/spans/SpanUtils.kt | 51 ++++++++++++------- .../touchin/roboswag/views/ActionTextView.kt | 5 +- .../views/text_view/MultipleActionTextView.kt | 9 ++-- 4 files changed, 49 insertions(+), 23 deletions(-) create mode 100644 kotlin-extensions/src/main/java/ru/touchin/extensions/TypedArray.kt diff --git a/kotlin-extensions/src/main/java/ru/touchin/extensions/TypedArray.kt b/kotlin-extensions/src/main/java/ru/touchin/extensions/TypedArray.kt new file mode 100644 index 0000000..de03034 --- /dev/null +++ b/kotlin-extensions/src/main/java/ru/touchin/extensions/TypedArray.kt @@ -0,0 +1,7 @@ +package ru.touchin.extensions + +import android.content.res.TypedArray +import androidx.annotation.StyleableRes +import androidx.core.content.res.getResourceIdOrThrow + +fun TypedArray.getResourceIdOrNull(@StyleableRes index: Int) = runCatching { getResourceIdOrThrow(index) }.getOrNull() diff --git a/utils/src/main/java/ru/touchin/roboswag/components/utils/spans/SpanUtils.kt b/utils/src/main/java/ru/touchin/roboswag/components/utils/spans/SpanUtils.kt index f331be0..98655e5 100644 --- a/utils/src/main/java/ru/touchin/roboswag/components/utils/spans/SpanUtils.kt +++ b/utils/src/main/java/ru/touchin/roboswag/components/utils/spans/SpanUtils.kt @@ -1,13 +1,15 @@ package ru.touchin.roboswag.components.utils.spans +import android.content.Context import android.text.SpannableString import android.text.Spanned import android.text.TextPaint import android.text.style.ClickableSpan +import android.text.style.TextAppearanceSpan import android.text.style.URLSpan import android.text.util.Linkify import android.view.View -import androidx.annotation.ColorInt +import androidx.annotation.StyleRes import androidx.core.text.HtmlCompat import ru.touchin.extensions.indexesOf import ru.touchin.utils.ActionThrottler @@ -61,23 +63,36 @@ private data class UrlSpanWithBorders(val span: URLSpan, val start: Int, val end fun CharSequence.toClickableSubstringText( substring: String, clickAction: () -> Unit, - @ColorInt color: Int? = null, - isUnderlineText: Boolean = false -) = SpannableString(this) - .apply { - indexesOf(substring)?.let { (startSpan, endSpan) -> - setSpan(object : ClickableSpan() { - override fun onClick(widget: View) { - ActionThrottler.throttleAction(DEFAULT_THROTTLE_DELAY_MS) { - clickAction.invoke() - } - } + isUnderlineText: Boolean = false, + @StyleRes styleId: Int? = null, + context: Context? = null +) = toSubstringSpannable( + substring = substring, + span = object : ClickableSpan() { + override fun onClick(widget: View) { + ActionThrottler.throttleAction(DEFAULT_THROTTLE_DELAY_MS) { + clickAction.invoke() + } + } - override fun updateDrawState(ds: TextPaint) { - super.updateDrawState(ds) - ds.isUnderlineText = isUnderlineText - if (color != null) ds.color = color - } - }, startSpan, endSpan, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) + override fun updateDrawState(ds: TextPaint) { + super.updateDrawState(ds) + ds.isUnderlineText = isUnderlineText } } +) + .apply { if (styleId != null && context != null) toStyleableSubstringText(substring, styleId, context) } + +fun CharSequence.toStyleableSubstringText( + substring: String, + @StyleRes styleId: Int, + context: Context +) = toSubstringSpannable(substring = substring, span = TextAppearanceSpan(context, styleId)) + +private fun CharSequence.toSubstringSpannable( + substring: String, + span: Any? +) = SpannableString(this) + .apply { + indexesOf(substring)?.let { (startSpan, endSpan) -> setSpan(span, startSpan, endSpan, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) } + } diff --git a/views/src/main/java/ru/touchin/roboswag/views/ActionTextView.kt b/views/src/main/java/ru/touchin/roboswag/views/ActionTextView.kt index 39d3ffd..6ff7a9b 100644 --- a/views/src/main/java/ru/touchin/roboswag/views/ActionTextView.kt +++ b/views/src/main/java/ru/touchin/roboswag/views/ActionTextView.kt @@ -5,6 +5,7 @@ import android.graphics.Color import android.util.AttributeSet import androidx.appcompat.widget.AppCompatTextView import androidx.core.content.withStyledAttributes +import ru.touchin.extensions.getResourceIdOrNull import ru.touchin.roboswag.components.utils.movementmethods.ClickableMovementMethod import ru.touchin.roboswag.components.utils.spans.toClickableSubstringText @@ -25,13 +26,13 @@ class ActionTextView @JvmOverloads constructor( context.withStyledAttributes(attrs, R.styleable.ActionTextView, defStyleAttr, 0) { val actionText = getString(R.styleable.ActionTextView_actionText).orEmpty() - val actionColor = getColor(R.styleable.ActionTextView_actionColor, currentTextColor) + val actionTextStyle = getResourceIdOrNull(R.styleable.ActionTextView_actionTextStyle) val isUnderlineText = getBoolean(R.styleable.ActionTextView_isUnderlineText, false) text = text.toClickableSubstringText( substring = actionText, clickAction = { onClickAction.invoke() }, - color = actionColor, + styleId = actionTextStyle, isUnderlineText = isUnderlineText ) } diff --git a/views/src/main/java/ru/touchin/roboswag/views/text_view/MultipleActionTextView.kt b/views/src/main/java/ru/touchin/roboswag/views/text_view/MultipleActionTextView.kt index f008cc9..e41fd3f 100644 --- a/views/src/main/java/ru/touchin/roboswag/views/text_view/MultipleActionTextView.kt +++ b/views/src/main/java/ru/touchin/roboswag/views/text_view/MultipleActionTextView.kt @@ -5,11 +5,13 @@ import android.graphics.Color import android.text.Spanned import android.text.TextPaint import android.text.style.ClickableSpan +import android.text.style.TextAppearanceSpan import android.util.AttributeSet import android.view.View import androidx.annotation.ColorInt import androidx.core.content.withStyledAttributes import androidx.core.text.toSpannable +import ru.touchin.extensions.getResourceIdOrNull import ru.touchin.extensions.indexesOf import ru.touchin.roboswag.components.utils.movementmethods.ClickableMovementMethod import ru.touchin.roboswag.views.R @@ -27,7 +29,7 @@ class MultipleActionTextView @JvmOverloads constructor( private val onClickActions = mutableListOf() @ColorInt - private var actionColor = currentTextColor + private var textStyle: Int? = null private var isUnderlineText = false @@ -38,7 +40,7 @@ class MultipleActionTextView @JvmOverloads constructor( movementMethod = ClickableMovementMethod context.withStyledAttributes(attrs, R.styleable.MultipleActionTextView, defStyleAttr, 0) { - actionColor = getColor(R.styleable.MultipleActionTextView_actionColor, currentTextColor) + textStyle = getResourceIdOrNull(R.styleable.MultipleActionTextView_actionTextStyle) isUnderlineText = getBoolean(R.styleable.MultipleActionTextView_isUnderlineText, false) } } @@ -62,9 +64,10 @@ class MultipleActionTextView @JvmOverloads constructor( override fun updateDrawState(ds: TextPaint) { super.updateDrawState(ds) ds.isUnderlineText = isUnderlineText - ds.color = actionColor } }, startSpan, endSpan, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) + + textStyle?.let { setSpan(TextAppearanceSpan(context, it), startSpan, endSpan, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) } } } } From dde53450a89852d757dfb7fa94b250beb3feb7e8 Mon Sep 17 00:00:00 2001 From: Kirill Nayduik Date: Tue, 25 Jan 2022 18:28:02 +0300 Subject: [PATCH 028/133] Fix attrs for ActionTextViews --- views/src/main/res/values/attrs.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/views/src/main/res/values/attrs.xml b/views/src/main/res/values/attrs.xml index f6b0762..60c6336 100644 --- a/views/src/main/res/values/attrs.xml +++ b/views/src/main/res/values/attrs.xml @@ -61,17 +61,17 @@ - + - + - + From ee4c4aaa5ab44168cff449b3b486755114adffd7 Mon Sep 17 00:00:00 2001 From: Kirill Nayduik Date: Tue, 25 Jan 2022 18:38:05 +0300 Subject: [PATCH 029/133] Add StyleableSubTextView to support styling substrings of text --- .../views/text_view/StyleableSubTextView.kt | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 views/src/main/java/ru/touchin/roboswag/views/text_view/StyleableSubTextView.kt diff --git a/views/src/main/java/ru/touchin/roboswag/views/text_view/StyleableSubTextView.kt b/views/src/main/java/ru/touchin/roboswag/views/text_view/StyleableSubTextView.kt new file mode 100644 index 0000000..2851042 --- /dev/null +++ b/views/src/main/java/ru/touchin/roboswag/views/text_view/StyleableSubTextView.kt @@ -0,0 +1,35 @@ +package ru.touchin.roboswag.views.text_view + +import android.content.Context +import android.util.AttributeSet +import androidx.core.content.withStyledAttributes +import ru.touchin.extensions.getResourceIdOrNull +import ru.touchin.roboswag.components.utils.spans.toStyleableSubstringText +import ru.touchin.roboswag.views.R + +/** + * A [android.widget.TextView] which support styling substrings of text + */ +class StyleableSubTextView @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 +): EllipsizeSpannableTextView(context, attrs, defStyleAttr) { + + init { + context.withStyledAttributes(attrs, R.styleable.StyleableSubTextView, defStyleAttr, 0) { + val substring = getString(R.styleable.StyleableSubTextView_subtext) + val subtextStyle = getResourceIdOrNull(R.styleable.StyleableSubTextView_subtextStyle) + + if (subtextStyle != null && substring != null) { + text = text.toStyleableSubstringText( + substring = substring, + styleId = subtextStyle, + context = context + ) + } + + } + } + +} From 3d01b780be153a9e87f0f517527074899cbf3a02 Mon Sep 17 00:00:00 2001 From: Kirill Nayduik Date: Wed, 26 Jan 2022 16:58:24 +0300 Subject: [PATCH 030/133] Action TextViews bugfix --- .../components/utils/spans/SpanUtils.kt | 5 ++-- .../views/text_view/MultipleActionTextView.kt | 30 +++---------------- 2 files changed, 7 insertions(+), 28 deletions(-) diff --git a/utils/src/main/java/ru/touchin/roboswag/components/utils/spans/SpanUtils.kt b/utils/src/main/java/ru/touchin/roboswag/components/utils/spans/SpanUtils.kt index 98655e5..095443e 100644 --- a/utils/src/main/java/ru/touchin/roboswag/components/utils/spans/SpanUtils.kt +++ b/utils/src/main/java/ru/touchin/roboswag/components/utils/spans/SpanUtils.kt @@ -76,7 +76,6 @@ fun CharSequence.toClickableSubstringText( } override fun updateDrawState(ds: TextPaint) { - super.updateDrawState(ds) ds.isUnderlineText = isUnderlineText } } @@ -92,7 +91,9 @@ fun CharSequence.toStyleableSubstringText( private fun CharSequence.toSubstringSpannable( substring: String, span: Any? -) = SpannableString(this) +) = toSpannable() .apply { indexesOf(substring)?.let { (startSpan, endSpan) -> setSpan(span, startSpan, endSpan, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) } } + +private fun CharSequence.toSpannable() = if (this is SpannableString) this else SpannableString(this) diff --git a/views/src/main/java/ru/touchin/roboswag/views/text_view/MultipleActionTextView.kt b/views/src/main/java/ru/touchin/roboswag/views/text_view/MultipleActionTextView.kt index e41fd3f..130fe56 100644 --- a/views/src/main/java/ru/touchin/roboswag/views/text_view/MultipleActionTextView.kt +++ b/views/src/main/java/ru/touchin/roboswag/views/text_view/MultipleActionTextView.kt @@ -2,18 +2,13 @@ package ru.touchin.roboswag.views.text_view import android.content.Context import android.graphics.Color -import android.text.Spanned -import android.text.TextPaint -import android.text.style.ClickableSpan -import android.text.style.TextAppearanceSpan import android.util.AttributeSet -import android.view.View -import androidx.annotation.ColorInt +import androidx.annotation.StyleRes import androidx.core.content.withStyledAttributes import androidx.core.text.toSpannable import ru.touchin.extensions.getResourceIdOrNull -import ru.touchin.extensions.indexesOf import ru.touchin.roboswag.components.utils.movementmethods.ClickableMovementMethod +import ru.touchin.roboswag.components.utils.spans.toClickableSubstringText import ru.touchin.roboswag.views.R /** @@ -28,7 +23,7 @@ class MultipleActionTextView @JvmOverloads constructor( private val onClickActions = mutableListOf() - @ColorInt + @StyleRes private var textStyle: Int? = null private var isUnderlineText = false @@ -52,24 +47,7 @@ class MultipleActionTextView @JvmOverloads constructor( private fun applyActionSpans() { text = text.toSpannable().apply { - onClickActions.forEach { (substring, action) -> - - indexesOf(substring)?.let { (startSpan, endSpan) -> - - setSpan(object : ClickableSpan() { - override fun onClick(widget: View) { - action.invoke() - } - - override fun updateDrawState(ds: TextPaint) { - super.updateDrawState(ds) - ds.isUnderlineText = isUnderlineText - } - }, startSpan, endSpan, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) - - textStyle?.let { setSpan(TextAppearanceSpan(context, it), startSpan, endSpan, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) } - } - } + onClickActions.forEach { (substring, action) -> toClickableSubstringText(substring, action, isUnderlineText, textStyle, context) } } } From b8f65571959e4d0049c0845703cfae1a59df78d9 Mon Sep 17 00:00:00 2001 From: Kirill Nayduik Date: Wed, 26 Jan 2022 18:20:08 +0300 Subject: [PATCH 031/133] Add ability to change text and subtext dynamically --- .../views/text_view/StyleableSubTextView.kt | 30 +++++++++++++------ 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/views/src/main/java/ru/touchin/roboswag/views/text_view/StyleableSubTextView.kt b/views/src/main/java/ru/touchin/roboswag/views/text_view/StyleableSubTextView.kt index 2851042..c14baa0 100644 --- a/views/src/main/java/ru/touchin/roboswag/views/text_view/StyleableSubTextView.kt +++ b/views/src/main/java/ru/touchin/roboswag/views/text_view/StyleableSubTextView.kt @@ -16,20 +16,32 @@ class StyleableSubTextView @JvmOverloads constructor( defStyleAttr: Int = 0 ): EllipsizeSpannableTextView(context, attrs, defStyleAttr) { + private var styleId = typeface.style + var substring: String? = null + set(value) { + field = value + + value?.let(this::setSubstringText) + } + init { context.withStyledAttributes(attrs, R.styleable.StyleableSubTextView, defStyleAttr, 0) { val substring = getString(R.styleable.StyleableSubTextView_subtext) - val subtextStyle = getResourceIdOrNull(R.styleable.StyleableSubTextView_subtextStyle) - - if (subtextStyle != null && substring != null) { - text = text.toStyleableSubstringText( - substring = substring, - styleId = subtextStyle, - context = context - ) - } + getResourceIdOrNull(R.styleable.StyleableSubTextView_subtextStyle)?.let { styleId = it } + substring?.let(this@StyleableSubTextView::setSubstringText) } } + override fun setText(text: CharSequence?, type: BufferType?) { + substring + ?.let { super.setText(text?.toStyleableSubstringText(it, styleId, context), type) } + ?:super.setText(text, type) + + } + + private fun setSubstringText(substring: String) { + text = text.toStyleableSubstringText(substring, styleId, context) + } + } From c844375fc1cd1627bf0d6f3539e314bd07466357 Mon Sep 17 00:00:00 2001 From: Kirill Nayduik Date: Wed, 26 Jan 2022 18:50:12 +0300 Subject: [PATCH 032/133] Fix codeStyle and optimize text setting --- .../views/text_view/StyleableSubTextView.kt | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/views/src/main/java/ru/touchin/roboswag/views/text_view/StyleableSubTextView.kt b/views/src/main/java/ru/touchin/roboswag/views/text_view/StyleableSubTextView.kt index c14baa0..4156d20 100644 --- a/views/src/main/java/ru/touchin/roboswag/views/text_view/StyleableSubTextView.kt +++ b/views/src/main/java/ru/touchin/roboswag/views/text_view/StyleableSubTextView.kt @@ -14,34 +14,31 @@ class StyleableSubTextView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 -): EllipsizeSpannableTextView(context, attrs, defStyleAttr) { +) : EllipsizeSpannableTextView(context, attrs, defStyleAttr) { private var styleId = typeface.style + var substring: String? = null set(value) { field = value - value?.let(this::setSubstringText) + text = text // call setText after setting substring to reset substring } init { context.withStyledAttributes(attrs, R.styleable.StyleableSubTextView, defStyleAttr, 0) { - val substring = getString(R.styleable.StyleableSubTextView_subtext) getResourceIdOrNull(R.styleable.StyleableSubTextView_subtextStyle)?.let { styleId = it } - - substring?.let(this@StyleableSubTextView::setSubstringText) + substring = getString(R.styleable.StyleableSubTextView_subtext) } } override fun setText(text: CharSequence?, type: BufferType?) { - substring - ?.let { super.setText(text?.toStyleableSubstringText(it, styleId, context), type) } - ?:super.setText(text, type) + val spannableText = when (substring == null) { + true -> text + false -> text?.toStyleableSubstringText(substring.orEmpty(), styleId, context) + } - } - - private fun setSubstringText(substring: String) { - text = text.toStyleableSubstringText(substring, styleId, context) + super.setText(spannableText, type) } } From 72d2f3007d7dbfe5f27495128d19f032a168ed60 Mon Sep 17 00:00:00 2001 From: Kirill Nayduik Date: Wed, 2 Feb 2022 19:48:59 +0300 Subject: [PATCH 033/133] Make StyleableSubTextView open class --- .../roboswag/views/text_view/StyleableSubTextView.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/views/src/main/java/ru/touchin/roboswag/views/text_view/StyleableSubTextView.kt b/views/src/main/java/ru/touchin/roboswag/views/text_view/StyleableSubTextView.kt index 4156d20..8577d6d 100644 --- a/views/src/main/java/ru/touchin/roboswag/views/text_view/StyleableSubTextView.kt +++ b/views/src/main/java/ru/touchin/roboswag/views/text_view/StyleableSubTextView.kt @@ -10,13 +10,13 @@ import ru.touchin.roboswag.views.R /** * A [android.widget.TextView] which support styling substrings of text */ -class StyleableSubTextView @JvmOverloads constructor( +open class StyleableSubTextView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 ) : EllipsizeSpannableTextView(context, attrs, defStyleAttr) { - private var styleId = typeface.style + protected var substringStyleId = typeface.style var substring: String? = null set(value) { @@ -27,7 +27,7 @@ class StyleableSubTextView @JvmOverloads constructor( init { context.withStyledAttributes(attrs, R.styleable.StyleableSubTextView, defStyleAttr, 0) { - getResourceIdOrNull(R.styleable.StyleableSubTextView_subtextStyle)?.let { styleId = it } + getResourceIdOrNull(R.styleable.StyleableSubTextView_subtextStyle)?.let { substringStyleId = it } substring = getString(R.styleable.StyleableSubTextView_subtext) } } @@ -35,7 +35,7 @@ class StyleableSubTextView @JvmOverloads constructor( override fun setText(text: CharSequence?, type: BufferType?) { val spannableText = when (substring == null) { true -> text - false -> text?.toStyleableSubstringText(substring.orEmpty(), styleId, context) + false -> text?.toStyleableSubstringText(substring.orEmpty(), substringStyleId, context) } super.setText(spannableText, type) From 902c029a295e7993ca159602529dc5e7cdd3838e Mon Sep 17 00:00:00 2001 From: Kirill Nayduik Date: Sun, 13 Feb 2022 21:05:39 +0300 Subject: [PATCH 034/133] Set dagger version up to 2.34 to make it compilable with kotlin 1.5+ --- mvi-arch/build.gradle | 2 +- navigation-cicerone/build.gradle | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mvi-arch/build.gradle b/mvi-arch/build.gradle index 44b9301..1532294 100644 --- a/mvi-arch/build.gradle +++ b/mvi-arch/build.gradle @@ -33,7 +33,7 @@ dependencies { def fragmentVersion = "1.2.1" def lifecycleVersion = "2.2.0" def coroutinesVersion = "1.3.7" - def daggerVersion = "2.27" + def daggerVersion = "2.34" def materialDesignVersion = "1.2.0-rc01" constraints { diff --git a/navigation-cicerone/build.gradle b/navigation-cicerone/build.gradle index d029f70..9347e7b 100644 --- a/navigation-cicerone/build.gradle +++ b/navigation-cicerone/build.gradle @@ -11,7 +11,7 @@ dependencies { implementation("com.github.valeryponomarenko.componentsmanager:androidx:2.1.0") - def daggerVersion = "2.27" + def daggerVersion = "2.34" constraints { implementation("ru.terrakok.cicerone:cicerone") { From 1b994bab0ea74c67880edb39b8dbbbc5889bfadf Mon Sep 17 00:00:00 2001 From: Kirill Nayduik Date: Sun, 13 Feb 2022 21:06:09 +0300 Subject: [PATCH 035/133] Change deprecated kotlin-android-extensions to kotlin-parcelize --- android-configs/app-config.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android-configs/app-config.gradle b/android-configs/app-config.gradle index cf6759d..52f152f 100644 --- a/android-configs/app-config.gradle +++ b/android-configs/app-config.gradle @@ -3,5 +3,5 @@ apply plugin: 'com.android.application' apply from: '../RoboSwag/android-configs/common-config.gradle' apply plugin: 'kotlin-android' -apply plugin: 'kotlin-android-extensions' +apply plugin: 'kotlin-parcelize' apply plugin: 'kotlin-kapt' From cab47a058c765f1b63e1a306bbb60b6f4be3a9ba Mon Sep 17 00:00:00 2001 From: Kirill Nayduik Date: Sun, 13 Feb 2022 22:58:57 +0300 Subject: [PATCH 036/133] Add ViewCoroutineScope to handle scope inside view --- .../lifecycle/scope/ViewCoroutineScope.kt | 32 +++++++++++++++++++ lifecycle/src/main/res/values/strings.xml | 4 +++ 2 files changed, 36 insertions(+) create mode 100644 lifecycle/src/main/java/ru/touchin/lifecycle/scope/ViewCoroutineScope.kt create mode 100644 lifecycle/src/main/res/values/strings.xml diff --git a/lifecycle/src/main/java/ru/touchin/lifecycle/scope/ViewCoroutineScope.kt b/lifecycle/src/main/java/ru/touchin/lifecycle/scope/ViewCoroutineScope.kt new file mode 100644 index 0000000..9ff1fdb --- /dev/null +++ b/lifecycle/src/main/java/ru/touchin/lifecycle/scope/ViewCoroutineScope.kt @@ -0,0 +1,32 @@ +package ru.touchin.lifecycle.scope + +import android.view.View +import kotlinx.coroutines.* +import ru.touchin.lifecycle.R + +val View.viewScope: CoroutineScope + get() { + val storedScope = getTag(R.string.view_coroutine_scope) as? CoroutineScope + if (storedScope != null) return storedScope + + val newScope = ViewCoroutineScope() + if (isAttachedToWindow) { + addOnAttachStateChangeListener(newScope) + setTag(R.string.view_coroutine_scope, newScope) + } else { + newScope.cancel() + } + + return newScope + } + +private class ViewCoroutineScope : CoroutineScope, View.OnAttachStateChangeListener { + override val coroutineContext = SupervisorJob() + Dispatchers.Main + + override fun onViewAttachedToWindow(view: View) = Unit + + override fun onViewDetachedFromWindow(view: View) { + coroutineContext.cancel() + view.setTag(R.string.view_coroutine_scope, null) + } +} \ No newline at end of file diff --git a/lifecycle/src/main/res/values/strings.xml b/lifecycle/src/main/res/values/strings.xml new file mode 100644 index 0000000..4fbdb47 --- /dev/null +++ b/lifecycle/src/main/res/values/strings.xml @@ -0,0 +1,4 @@ + + + view_coroutine_scope + \ No newline at end of file From f13984d7277409585af193702f2722d2616fb1a0 Mon Sep 17 00:00:00 2001 From: Kirill Nayduik Date: Mon, 14 Feb 2022 04:18:47 +0300 Subject: [PATCH 037/133] Fix code style --- .../java/ru/touchin/lifecycle/scope/ViewCoroutineScope.kt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lifecycle/src/main/java/ru/touchin/lifecycle/scope/ViewCoroutineScope.kt b/lifecycle/src/main/java/ru/touchin/lifecycle/scope/ViewCoroutineScope.kt index 9ff1fdb..42e16c5 100644 --- a/lifecycle/src/main/java/ru/touchin/lifecycle/scope/ViewCoroutineScope.kt +++ b/lifecycle/src/main/java/ru/touchin/lifecycle/scope/ViewCoroutineScope.kt @@ -1,7 +1,10 @@ package ru.touchin.lifecycle.scope import android.view.View -import kotlinx.coroutines.* +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.cancel import ru.touchin.lifecycle.R val View.viewScope: CoroutineScope @@ -29,4 +32,4 @@ private class ViewCoroutineScope : CoroutineScope, View.OnAttachStateChangeListe coroutineContext.cancel() view.setTag(R.string.view_coroutine_scope, null) } -} \ No newline at end of file +} From d907193e4146cb8476de526f85f9a8f5724058b9 Mon Sep 17 00:00:00 2001 From: Kirill Nayduik Date: Mon, 28 Feb 2022 13:22:39 +0300 Subject: [PATCH 038/133] Add subclass of PagingDataAdapter with implementation of updating inner list --- recyclerview-adapters/build.gradle | 7 +++++ .../adapters/SubmittablePagingAdapter.kt | 27 +++++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 recyclerview-adapters/src/main/java/ru/touchin/roboswag/recyclerview_adapters/adapters/SubmittablePagingAdapter.kt diff --git a/recyclerview-adapters/build.gradle b/recyclerview-adapters/build.gradle index 8b8971b..c72248a 100644 --- a/recyclerview-adapters/build.gradle +++ b/recyclerview-adapters/build.gradle @@ -7,6 +7,8 @@ dependencies { implementation "androidx.core:core-ktx" + implementation "androidx.paging:paging-runtime" + constraints { implementation("androidx.recyclerview:recyclerview") { version { @@ -18,5 +20,10 @@ dependencies { require '1.0.0' } } + implementation("androidx.paging:paging-runtime") { + version { + require '3.0.0-alpha06' + } + } } } diff --git a/recyclerview-adapters/src/main/java/ru/touchin/roboswag/recyclerview_adapters/adapters/SubmittablePagingAdapter.kt b/recyclerview-adapters/src/main/java/ru/touchin/roboswag/recyclerview_adapters/adapters/SubmittablePagingAdapter.kt new file mode 100644 index 0000000..e2162c6 --- /dev/null +++ b/recyclerview-adapters/src/main/java/ru/touchin/roboswag/recyclerview_adapters/adapters/SubmittablePagingAdapter.kt @@ -0,0 +1,27 @@ +package ru.touchin.roboswag.recyclerview_adapters.adapters + +import androidx.paging.PagingDataAdapter +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.RecyclerView + +abstract class SubmittablePagingAdapter(private val diffCallback: DiffUtil.ItemCallback): + PagingDataAdapter(diffCallback) { + /** + * [items] list of updated elements + * [transform] callback that keeps logic of transformation item based on newItem. + * Should return updated element + */ + fun submitList(items: List, transform: T.(newItem: T, payload: Any?) -> T) { + items.forEach { newItem -> + snapshot().forEachIndexed { index, oldItem -> + if (oldItem != null && diffCallback.areItemsTheSame(oldItem, newItem) && !diffCallback.areContentsTheSame(oldItem, newItem)) { + val payload = diffCallback.getChangePayload(oldItem, newItem) + + oldItem.transform(newItem, payload) + + notifyItemChanged(index, payload) + } + } + } + } +} From 24b380fff06b0c3147a976c7c0f6057e83ec8d1a Mon Sep 17 00:00:00 2001 From: Alemoore Date: Wed, 2 Mar 2022 15:44:15 +0300 Subject: [PATCH 039/133] use interface in RxViewModel --- .../src/main/java/ru/touchin/lifecycle/viewmodel/RxViewModel.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lifecycle-rx/src/main/java/ru/touchin/lifecycle/viewmodel/RxViewModel.kt b/lifecycle-rx/src/main/java/ru/touchin/lifecycle/viewmodel/RxViewModel.kt index 74b8f67..5919bdf 100644 --- a/lifecycle-rx/src/main/java/ru/touchin/lifecycle/viewmodel/RxViewModel.kt +++ b/lifecycle-rx/src/main/java/ru/touchin/lifecycle/viewmodel/RxViewModel.kt @@ -8,7 +8,7 @@ import androidx.annotation.CallSuper */ open class RxViewModel( private val destroyable: BaseDestroyable = BaseDestroyable(), - private val liveDataDispatcher: BaseLiveDataDispatcher = BaseLiveDataDispatcher(destroyable) + private val liveDataDispatcher: LiveDataDispatcher = BaseLiveDataDispatcher(destroyable) ) : ViewModel(), Destroyable by destroyable, LiveDataDispatcher by liveDataDispatcher { @CallSuper From 7ed7a25bfeca2944d7fc09caaedf2047eac4ad34 Mon Sep 17 00:00:00 2001 From: Alemoore Date: Wed, 2 Mar 2022 15:46:55 +0300 Subject: [PATCH 040/133] add TestableLiveDataDispatcher without android classes --- .../viewmodel/TestableLiveDataDispatcher.kt | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 lifecycle-rx/src/main/java/ru/touchin/lifecycle/viewmodel/TestableLiveDataDispatcher.kt diff --git a/lifecycle-rx/src/main/java/ru/touchin/lifecycle/viewmodel/TestableLiveDataDispatcher.kt b/lifecycle-rx/src/main/java/ru/touchin/lifecycle/viewmodel/TestableLiveDataDispatcher.kt new file mode 100644 index 0000000..c7ea674 --- /dev/null +++ b/lifecycle-rx/src/main/java/ru/touchin/lifecycle/viewmodel/TestableLiveDataDispatcher.kt @@ -0,0 +1,49 @@ +package ru.touchin.lifecycle.viewmodel + +import androidx.lifecycle.MutableLiveData +import io.reactivex.Completable +import io.reactivex.Flowable +import io.reactivex.Maybe +import io.reactivex.Observable +import io.reactivex.Single +import io.reactivex.disposables.Disposable +import ru.touchin.lifecycle.event.ContentEvent +import ru.touchin.lifecycle.event.Event + +class TestableLiveDataDispatcher( + private val destroyable: BaseDestroyable = BaseDestroyable() +) : LiveDataDispatcher, Destroyable by destroyable { + + override fun Flowable.dispatchTo(liveData: MutableLiveData>): Disposable { + return untilDestroy( + { data -> liveData.value = ContentEvent.Success(data) }, + { throwable -> liveData.value = ContentEvent.Error(throwable, liveData.value?.data) }, + { liveData.value = ContentEvent.Complete(liveData.value?.data) }) + } + + override fun Observable.dispatchTo(liveData: MutableLiveData>): Disposable { + return untilDestroy( + { data -> liveData.value = ContentEvent.Success(data) }, + { throwable -> liveData.value = ContentEvent.Error(throwable, liveData.value?.data) }, + { liveData.value = ContentEvent.Complete(liveData.value?.data) }) + } + + override fun Single.dispatchTo(liveData: MutableLiveData>): Disposable { + return untilDestroy( + { data -> liveData.value = ContentEvent.Success(data) }, + { throwable -> liveData.value = ContentEvent.Error(throwable, liveData.value?.data) }) + } + + override fun Maybe.dispatchTo(liveData: MutableLiveData>): Disposable { + return untilDestroy( + { data -> liveData.value = ContentEvent.Success(data) }, + { throwable -> liveData.value = ContentEvent.Error(throwable, liveData.value?.data) }, + { liveData.value = ContentEvent.Complete(liveData.value?.data) }) + } + + override fun Completable.dispatchTo(liveData: MutableLiveData): Disposable { + return untilDestroy( + { liveData.value = Event.Complete }, + { throwable -> liveData.value = Event.Error(throwable) }) + } +} From 9a728a9e57ffa270a77a2b5f1ad2880af61b2ec5 Mon Sep 17 00:00:00 2001 From: Rinat Nurmukhametov Date: Tue, 15 Mar 2022 11:46:24 +0300 Subject: [PATCH 041/133] create captcha module with checking --- recaptcha/.gitignore | 1 + recaptcha/README.md | 12 ++++ recaptcha/build.gradle | 50 ++++++++++++++++ recaptcha/src/main/AndroidManifest.xml | 6 ++ .../touchin/recaptcha/RecaptchaController.kt | 60 +++++++++++++++++++ .../ru/touchin/recaptcha/ServicesUtils.kt | 19 ++++++ 6 files changed, 148 insertions(+) create mode 100644 recaptcha/.gitignore create mode 100644 recaptcha/README.md create mode 100644 recaptcha/build.gradle create mode 100644 recaptcha/src/main/AndroidManifest.xml create mode 100644 recaptcha/src/main/java/ru/touchin/recaptcha/RecaptchaController.kt create mode 100644 recaptcha/src/main/java/ru/touchin/recaptcha/ServicesUtils.kt diff --git a/recaptcha/.gitignore b/recaptcha/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/recaptcha/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/recaptcha/README.md b/recaptcha/README.md new file mode 100644 index 0000000..ca78bde --- /dev/null +++ b/recaptcha/README.md @@ -0,0 +1,12 @@ +recaptcha +===== + +Модуль содержит класс `RecaptchaController` - служит для проверки используемого сервиса (Huawei или Google) и показа диалога с каптчёй + +Для использования модуля нужно добавить json файл с сервисами в корневую папку проекта + +### Конструктор +`googleRecaptchaKey` - ключ для google recaptcha +`huaweiAppId` - `id` приложения для huawei captcha +`onNewTokenReceived` - callback на успешную проверку каптчи +`processThrowable` - callback на ошибку проверки каптчи diff --git a/recaptcha/build.gradle b/recaptcha/build.gradle new file mode 100644 index 0000000..42c941e --- /dev/null +++ b/recaptcha/build.gradle @@ -0,0 +1,50 @@ +apply from: "../android-configs/lib-config.gradle" +apply plugin: 'com.huawei.agconnect' + +dependencies { + implementation project(':kotlin-extensions') + implementation "androidx.core:core" + implementation "androidx.annotation:annotation" + implementation "com.google.android.material:material" + implementation "com.google.android.gms:play-services-safetynet" + implementation "com.google.android.gms:play-services-base" + implementation "com.huawei.hms:safetydetect" + + constraints { + implementation("androidx.core:core") { + version { + require '1.0.0' + } + } + + implementation("androidx.annotation:annotation") { + version { + require '1.1.0' + } + } + + implementation("com.google.android.gms:play-services-safetynet") { + version { + require '17.0.0' + } + } + + implementation("com.google.android.gms:play-services-base") { + version { + require '18.0.1' + } + } + + implementation("com.huawei.hms:safetydetect") { + version { + require '4.0.3.300' + } + } + + implementation("com.google.android.material:material") { + version { + require '1.2.0-rc01' + } + } + } +} diff --git a/recaptcha/src/main/AndroidManifest.xml b/recaptcha/src/main/AndroidManifest.xml new file mode 100644 index 0000000..17ef42a --- /dev/null +++ b/recaptcha/src/main/AndroidManifest.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/recaptcha/src/main/java/ru/touchin/recaptcha/RecaptchaController.kt b/recaptcha/src/main/java/ru/touchin/recaptcha/RecaptchaController.kt new file mode 100644 index 0000000..d3720ee --- /dev/null +++ b/recaptcha/src/main/java/ru/touchin/recaptcha/RecaptchaController.kt @@ -0,0 +1,60 @@ +package ru.touchin.recaptcha + +import android.app.Activity +import com.google.android.gms.safetynet.SafetyNet +import com.huawei.hms.support.api.safetydetect.SafetyDetect +import ru.touchin.recaptcha.ServicesUtils.checkGooglePlayServices +import ru.touchin.recaptcha.ServicesUtils.checkHuaweiServices + +class RecaptchaController( + private val activity: Activity, + private val googleRecaptchaKey: String? = null, + private val huaweiAppId: String? = null, + private val onNewTokenReceived: (String) -> Unit, + private val processThrowable: (Throwable) -> Unit +) { + + fun showRecaptchaAlert() { + when { + checkHuaweiServices(activity) -> showHuaweiCaptcha() + checkGooglePlayServices(activity) -> showGoogleCaptcha() + } + } + + private fun showGoogleCaptcha() { + if (googleRecaptchaKey != null) { + SafetyNet.getClient(activity) + .verifyWithRecaptcha(googleRecaptchaKey) + .addOnSuccessListener(activity) { response -> + onServiceTokenResponse(response?.tokenResult) + } + .addOnFailureListener(activity, processThrowable) + } + } + + private fun showHuaweiCaptcha() { + if (huaweiAppId != null) { + val huaweiSafetyDetectClient = SafetyDetect.getClient(activity) + huaweiSafetyDetectClient.initUserDetect() + .addOnSuccessListener { + huaweiSafetyDetectClient.userDetection(huaweiAppId) + .addOnSuccessListener { response -> + onServiceTokenResponse(response?.responseToken) + } + .addOnFailureListener(activity, processThrowable) + } + .addOnFailureListener(activity, processThrowable) + } + } + + private fun onServiceTokenResponse(newToken: String?) { + if (!newToken.isNullOrBlank()) { + onNewTokenReceived.invoke(newToken) + } else { + processThrowable.invoke(EmptyCaptchaTokenException()) + } + } + +} + +class EmptyCaptchaTokenException : Throwable("Captcha token is empty") diff --git a/recaptcha/src/main/java/ru/touchin/recaptcha/ServicesUtils.kt b/recaptcha/src/main/java/ru/touchin/recaptcha/ServicesUtils.kt new file mode 100644 index 0000000..502a731 --- /dev/null +++ b/recaptcha/src/main/java/ru/touchin/recaptcha/ServicesUtils.kt @@ -0,0 +1,19 @@ +package ru.touchin.recaptcha + +import android.content.Context +import com.google.android.gms.common.ConnectionResult +import com.google.android.gms.common.GoogleApiAvailability +import com.huawei.hms.api.HuaweiApiAvailability + +//TODO: in the future move to a module with services +object ServicesUtils { + + fun checkHuaweiServices(context: Context): Boolean = + HuaweiApiAvailability.getInstance() + .isHuaweiMobileNoticeAvailable(context) == ConnectionResult.SUCCESS + + fun checkGooglePlayServices(context: Context): Boolean = + GoogleApiAvailability.getInstance() + .isGooglePlayServicesAvailable(context) == ConnectionResult.SUCCESS + +} From d9e891c83840891dd7759a2a4858fe5e78e78290 Mon Sep 17 00:00:00 2001 From: rinstance <30986957+rinstance@users.noreply.github.com> Date: Tue, 15 Mar 2022 11:48:11 +0300 Subject: [PATCH 042/133] change README --- recaptcha/README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/recaptcha/README.md b/recaptcha/README.md index ca78bde..f2a26cb 100644 --- a/recaptcha/README.md +++ b/recaptcha/README.md @@ -6,7 +6,7 @@ recaptcha Для использования модуля нужно добавить json файл с сервисами в корневую папку проекта ### Конструктор -`googleRecaptchaKey` - ключ для google recaptcha -`huaweiAppId` - `id` приложения для huawei captcha -`onNewTokenReceived` - callback на успешную проверку каптчи -`processThrowable` - callback на ошибку проверки каптчи +* `googleRecaptchaKey` - ключ для google recaptcha +* `huaweiAppId` - `id` приложения для huawei captcha +* `onNewTokenReceived` - callback на успешную проверку каптчи +* `processThrowable` - callback на ошибку проверки каптчи From 8d70d2dad18c8bf26c1629aae10d61a10b94eaab Mon Sep 17 00:00:00 2001 From: Rinat Nurmukhametov Date: Wed, 16 Mar 2022 16:56:04 +0300 Subject: [PATCH 043/133] refactor manager + utils --- recaptcha/build.gradle | 9 +-- .../ru/touchin/recaptcha/CaptchaClient.kt | 24 ++++++++ .../ru/touchin/recaptcha/CaptchaManager.kt | 24 ++++++++ .../touchin/recaptcha/GoogleCaptchaClient.kt | 20 +++++++ .../touchin/recaptcha/HuaweiCaptchaClient.kt | 24 ++++++++ .../touchin/recaptcha/RecaptchaController.kt | 60 ------------------- .../ru/touchin/recaptcha/ServicesUtils.kt | 16 ++++- 7 files changed, 106 insertions(+), 71 deletions(-) create mode 100644 recaptcha/src/main/java/ru/touchin/recaptcha/CaptchaClient.kt create mode 100644 recaptcha/src/main/java/ru/touchin/recaptcha/CaptchaManager.kt create mode 100644 recaptcha/src/main/java/ru/touchin/recaptcha/GoogleCaptchaClient.kt create mode 100644 recaptcha/src/main/java/ru/touchin/recaptcha/HuaweiCaptchaClient.kt delete mode 100644 recaptcha/src/main/java/ru/touchin/recaptcha/RecaptchaController.kt diff --git a/recaptcha/build.gradle b/recaptcha/build.gradle index 42c941e..7f748b6 100644 --- a/recaptcha/build.gradle +++ b/recaptcha/build.gradle @@ -5,7 +5,6 @@ dependencies { implementation project(':kotlin-extensions') implementation "androidx.core:core" implementation "androidx.annotation:annotation" - implementation "com.google.android.material:material" implementation "com.google.android.gms:play-services-safetynet" implementation "com.google.android.gms:play-services-base" implementation "com.huawei.hms:safetydetect" @@ -25,7 +24,7 @@ dependencies { implementation("com.google.android.gms:play-services-safetynet") { version { - require '17.0.0' + require '18.0.1' } } @@ -40,11 +39,5 @@ dependencies { require '4.0.3.300' } } - - implementation("com.google.android.material:material") { - version { - require '1.2.0-rc01' - } - } } } diff --git a/recaptcha/src/main/java/ru/touchin/recaptcha/CaptchaClient.kt b/recaptcha/src/main/java/ru/touchin/recaptcha/CaptchaClient.kt new file mode 100644 index 0000000..207a6ef --- /dev/null +++ b/recaptcha/src/main/java/ru/touchin/recaptcha/CaptchaClient.kt @@ -0,0 +1,24 @@ +package ru.touchin.recaptcha + +import android.app.Activity + +abstract class CaptchaClient( + private val onNewTokenReceived: (String) -> Unit = {}, + private val processThrowable: (Throwable) -> Unit = {} +) { + + abstract fun showCaptcha(activity: Activity, captchaKey: String) + + protected fun onServiceTokenResponse(newToken: String?) { + if (!newToken.isNullOrBlank()) { + onNewTokenReceived.invoke(newToken) + } else { + processThrowable.invoke(EmptyCaptchaTokenException()) + } + } + +} + +class EmptyCaptchaKeyException : Throwable("Captcha key is empty") + +class EmptyCaptchaTokenException : Throwable("Captcha token is empty") diff --git a/recaptcha/src/main/java/ru/touchin/recaptcha/CaptchaManager.kt b/recaptcha/src/main/java/ru/touchin/recaptcha/CaptchaManager.kt new file mode 100644 index 0000000..8aa9d15 --- /dev/null +++ b/recaptcha/src/main/java/ru/touchin/recaptcha/CaptchaManager.kt @@ -0,0 +1,24 @@ +package ru.touchin.recaptcha + +import android.app.Activity + +class CaptchaManager( + onNewTokenReceived: (String) -> Unit, + private val processThrowable: (Throwable) -> Unit +) { + + private val clientsMap = mapOf( + MobileService.GOOGLE_SERVICE to GoogleCaptchaClient(onNewTokenReceived, processThrowable), + MobileService.HUAWEI_SERVICE to HuaweiCaptchaClient(onNewTokenReceived, processThrowable) + ) + + fun showRecaptchaAlert(activity: Activity, captchaKey: String) { + if (captchaKey.isBlank()) { + processThrowable.invoke(EmptyCaptchaKeyException()) + } else { + val service = ServicesUtils().getCurrentService(activity) + clientsMap[service]?.showCaptcha(activity, captchaKey) + } + } + +} diff --git a/recaptcha/src/main/java/ru/touchin/recaptcha/GoogleCaptchaClient.kt b/recaptcha/src/main/java/ru/touchin/recaptcha/GoogleCaptchaClient.kt new file mode 100644 index 0000000..b25bd34 --- /dev/null +++ b/recaptcha/src/main/java/ru/touchin/recaptcha/GoogleCaptchaClient.kt @@ -0,0 +1,20 @@ +package ru.touchin.recaptcha + +import android.app.Activity +import com.google.android.gms.safetynet.SafetyNet + +class GoogleCaptchaClient( + onNewTokenReceived: (String) -> Unit, + private val processThrowable: (Throwable) -> Unit +) : CaptchaClient(onNewTokenReceived, processThrowable) { + + override fun showCaptcha(activity: Activity, captchaKey: String) { + SafetyNet.getClient(activity) + .verifyWithRecaptcha(captchaKey) + .addOnSuccessListener(activity) { response -> + onServiceTokenResponse(response?.tokenResult) + } + .addOnFailureListener(activity, processThrowable) + } + +} diff --git a/recaptcha/src/main/java/ru/touchin/recaptcha/HuaweiCaptchaClient.kt b/recaptcha/src/main/java/ru/touchin/recaptcha/HuaweiCaptchaClient.kt new file mode 100644 index 0000000..6e860ca --- /dev/null +++ b/recaptcha/src/main/java/ru/touchin/recaptcha/HuaweiCaptchaClient.kt @@ -0,0 +1,24 @@ +package ru.touchin.recaptcha + +import android.app.Activity +import com.huawei.hms.support.api.safetydetect.SafetyDetect + +class HuaweiCaptchaClient( + onNewTokenReceived: (String) -> Unit, + private val processThrowable: (Throwable) -> Unit +) : CaptchaClient(onNewTokenReceived, processThrowable) { + + override fun showCaptcha(activity: Activity, captchaKey: String) { + val huaweiSafetyDetectClient = SafetyDetect.getClient(activity) + huaweiSafetyDetectClient.initUserDetect() + .addOnSuccessListener { + huaweiSafetyDetectClient.userDetection(captchaKey) + .addOnSuccessListener { response -> + onServiceTokenResponse(response?.responseToken) + } + .addOnFailureListener(activity, processThrowable) + } + .addOnFailureListener(activity, processThrowable) + } + +} diff --git a/recaptcha/src/main/java/ru/touchin/recaptcha/RecaptchaController.kt b/recaptcha/src/main/java/ru/touchin/recaptcha/RecaptchaController.kt deleted file mode 100644 index d3720ee..0000000 --- a/recaptcha/src/main/java/ru/touchin/recaptcha/RecaptchaController.kt +++ /dev/null @@ -1,60 +0,0 @@ -package ru.touchin.recaptcha - -import android.app.Activity -import com.google.android.gms.safetynet.SafetyNet -import com.huawei.hms.support.api.safetydetect.SafetyDetect -import ru.touchin.recaptcha.ServicesUtils.checkGooglePlayServices -import ru.touchin.recaptcha.ServicesUtils.checkHuaweiServices - -class RecaptchaController( - private val activity: Activity, - private val googleRecaptchaKey: String? = null, - private val huaweiAppId: String? = null, - private val onNewTokenReceived: (String) -> Unit, - private val processThrowable: (Throwable) -> Unit -) { - - fun showRecaptchaAlert() { - when { - checkHuaweiServices(activity) -> showHuaweiCaptcha() - checkGooglePlayServices(activity) -> showGoogleCaptcha() - } - } - - private fun showGoogleCaptcha() { - if (googleRecaptchaKey != null) { - SafetyNet.getClient(activity) - .verifyWithRecaptcha(googleRecaptchaKey) - .addOnSuccessListener(activity) { response -> - onServiceTokenResponse(response?.tokenResult) - } - .addOnFailureListener(activity, processThrowable) - } - } - - private fun showHuaweiCaptcha() { - if (huaweiAppId != null) { - val huaweiSafetyDetectClient = SafetyDetect.getClient(activity) - huaweiSafetyDetectClient.initUserDetect() - .addOnSuccessListener { - huaweiSafetyDetectClient.userDetection(huaweiAppId) - .addOnSuccessListener { response -> - onServiceTokenResponse(response?.responseToken) - } - .addOnFailureListener(activity, processThrowable) - } - .addOnFailureListener(activity, processThrowable) - } - } - - private fun onServiceTokenResponse(newToken: String?) { - if (!newToken.isNullOrBlank()) { - onNewTokenReceived.invoke(newToken) - } else { - processThrowable.invoke(EmptyCaptchaTokenException()) - } - } - -} - -class EmptyCaptchaTokenException : Throwable("Captcha token is empty") diff --git a/recaptcha/src/main/java/ru/touchin/recaptcha/ServicesUtils.kt b/recaptcha/src/main/java/ru/touchin/recaptcha/ServicesUtils.kt index 502a731..e2c24e1 100644 --- a/recaptcha/src/main/java/ru/touchin/recaptcha/ServicesUtils.kt +++ b/recaptcha/src/main/java/ru/touchin/recaptcha/ServicesUtils.kt @@ -6,14 +6,24 @@ import com.google.android.gms.common.GoogleApiAvailability import com.huawei.hms.api.HuaweiApiAvailability //TODO: in the future move to a module with services -object ServicesUtils { +class ServicesUtils { - fun checkHuaweiServices(context: Context): Boolean = + fun getCurrentService(context: Context): MobileService = when { + checkHuaweiServices(context) -> MobileService.HUAWEI_SERVICE + checkGooglePlayServices(context) -> MobileService.GOOGLE_SERVICE + else -> MobileService.GOOGLE_SERVICE + } + + private fun checkHuaweiServices(context: Context): Boolean = HuaweiApiAvailability.getInstance() .isHuaweiMobileNoticeAvailable(context) == ConnectionResult.SUCCESS - fun checkGooglePlayServices(context: Context): Boolean = + private fun checkGooglePlayServices(context: Context): Boolean = GoogleApiAvailability.getInstance() .isGooglePlayServicesAvailable(context) == ConnectionResult.SUCCESS } + +enum class MobileService { + HUAWEI_SERVICE, GOOGLE_SERVICE +} From d201cfb36fa44b4dac3d1673b3086e6b98444b93 Mon Sep 17 00:00:00 2001 From: Rinat Nurmukhametov Date: Wed, 16 Mar 2022 17:01:32 +0300 Subject: [PATCH 044/133] change README --- recaptcha/README.md | 6 ------ .../src/main/java/ru/touchin/recaptcha/CaptchaManager.kt | 5 +++++ 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/recaptcha/README.md b/recaptcha/README.md index f2a26cb..a9b84b7 100644 --- a/recaptcha/README.md +++ b/recaptcha/README.md @@ -4,9 +4,3 @@ recaptcha Модуль содержит класс `RecaptchaController` - служит для проверки используемого сервиса (Huawei или Google) и показа диалога с каптчёй Для использования модуля нужно добавить json файл с сервисами в корневую папку проекта - -### Конструктор -* `googleRecaptchaKey` - ключ для google recaptcha -* `huaweiAppId` - `id` приложения для huawei captcha -* `onNewTokenReceived` - callback на успешную проверку каптчи -* `processThrowable` - callback на ошибку проверки каптчи diff --git a/recaptcha/src/main/java/ru/touchin/recaptcha/CaptchaManager.kt b/recaptcha/src/main/java/ru/touchin/recaptcha/CaptchaManager.kt index 8aa9d15..ebf7744 100644 --- a/recaptcha/src/main/java/ru/touchin/recaptcha/CaptchaManager.kt +++ b/recaptcha/src/main/java/ru/touchin/recaptcha/CaptchaManager.kt @@ -2,6 +2,11 @@ package ru.touchin.recaptcha import android.app.Activity +/** + * onNewTokenReceived - callback на успешную проверку каптчи + * processThrowable - callback на ошибку проверки каптчи + */ + class CaptchaManager( onNewTokenReceived: (String) -> Unit, private val processThrowable: (Throwable) -> Unit From ccb3f1c4e1d6e2060d1663f294e892cf3f8ff2bc Mon Sep 17 00:00:00 2001 From: Rinat Nurmukhametov Date: Wed, 16 Mar 2022 18:31:01 +0300 Subject: [PATCH 045/133] create services module --- client-services/.gitignore | 1 + client-services/build.gradle | 35 +++++++++++++++++++ client-services/src/main/AndroidManifest.xml | 6 ++++ .../touchin/client_services/MobileService.kt | 5 +++ .../touchin/client_services}/ServicesUtils.kt | 7 +--- recaptcha/build.gradle | 3 +- .../ru/touchin/recaptcha/CaptchaManager.kt | 2 ++ 7 files changed, 52 insertions(+), 7 deletions(-) create mode 100644 client-services/.gitignore create mode 100644 client-services/build.gradle create mode 100644 client-services/src/main/AndroidManifest.xml create mode 100644 client-services/src/main/java/ru/touchin/client_services/MobileService.kt rename {recaptcha/src/main/java/ru/touchin/recaptcha => client-services/src/main/java/ru/touchin/client_services}/ServicesUtils.kt (85%) diff --git a/client-services/.gitignore b/client-services/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/client-services/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/client-services/build.gradle b/client-services/build.gradle new file mode 100644 index 0000000..c55e87e --- /dev/null +++ b/client-services/build.gradle @@ -0,0 +1,35 @@ +apply from: "../android-configs/lib-config.gradle" +apply plugin: 'com.huawei.agconnect' + +dependencies { + implementation "androidx.core:core" + implementation "androidx.annotation:annotation" + implementation "com.google.android.gms:play-services-base" + implementation "com.huawei.hms:safetydetect" + + constraints { + implementation("androidx.core:core") { + version { + require '1.0.0' + } + } + + implementation("androidx.annotation:annotation") { + version { + require '1.1.0' + } + } + + implementation("com.google.android.gms:play-services-base") { + version { + require '18.0.1' + } + } + + implementation("com.huawei.hms:safetydetect") { + version { + require '4.0.3.300' + } + } + } +} diff --git a/client-services/src/main/AndroidManifest.xml b/client-services/src/main/AndroidManifest.xml new file mode 100644 index 0000000..4390a88 --- /dev/null +++ b/client-services/src/main/AndroidManifest.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/client-services/src/main/java/ru/touchin/client_services/MobileService.kt b/client-services/src/main/java/ru/touchin/client_services/MobileService.kt new file mode 100644 index 0000000..6514e3a --- /dev/null +++ b/client-services/src/main/java/ru/touchin/client_services/MobileService.kt @@ -0,0 +1,5 @@ +package ru.touchin.client_services + +enum class MobileService { + HUAWEI_SERVICE, GOOGLE_SERVICE +} diff --git a/recaptcha/src/main/java/ru/touchin/recaptcha/ServicesUtils.kt b/client-services/src/main/java/ru/touchin/client_services/ServicesUtils.kt similarity index 85% rename from recaptcha/src/main/java/ru/touchin/recaptcha/ServicesUtils.kt rename to client-services/src/main/java/ru/touchin/client_services/ServicesUtils.kt index e2c24e1..a4566d9 100644 --- a/recaptcha/src/main/java/ru/touchin/recaptcha/ServicesUtils.kt +++ b/client-services/src/main/java/ru/touchin/client_services/ServicesUtils.kt @@ -1,11 +1,10 @@ -package ru.touchin.recaptcha +package ru.touchin.client_services import android.content.Context import com.google.android.gms.common.ConnectionResult import com.google.android.gms.common.GoogleApiAvailability import com.huawei.hms.api.HuaweiApiAvailability -//TODO: in the future move to a module with services class ServicesUtils { fun getCurrentService(context: Context): MobileService = when { @@ -23,7 +22,3 @@ class ServicesUtils { .isGooglePlayServicesAvailable(context) == ConnectionResult.SUCCESS } - -enum class MobileService { - HUAWEI_SERVICE, GOOGLE_SERVICE -} diff --git a/recaptcha/build.gradle b/recaptcha/build.gradle index 7f748b6..51b4418 100644 --- a/recaptcha/build.gradle +++ b/recaptcha/build.gradle @@ -2,7 +2,8 @@ apply from: "../android-configs/lib-config.gradle" apply plugin: 'com.huawei.agconnect' dependencies { - implementation project(':kotlin-extensions') + implementation project(':client-services') + implementation "androidx.core:core" implementation "androidx.annotation:annotation" implementation "com.google.android.gms:play-services-safetynet" diff --git a/recaptcha/src/main/java/ru/touchin/recaptcha/CaptchaManager.kt b/recaptcha/src/main/java/ru/touchin/recaptcha/CaptchaManager.kt index ebf7744..2fd00ee 100644 --- a/recaptcha/src/main/java/ru/touchin/recaptcha/CaptchaManager.kt +++ b/recaptcha/src/main/java/ru/touchin/recaptcha/CaptchaManager.kt @@ -1,6 +1,8 @@ package ru.touchin.recaptcha import android.app.Activity +import ru.touchin.client_services.MobileService +import ru.touchin.client_services.ServicesUtils /** * onNewTokenReceived - callback на успешную проверку каптчи From 3681f5c92d84aae67850d224d5e497822b53a15e Mon Sep 17 00:00:00 2001 From: Rinat Nurmukhametov Date: Thu, 17 Mar 2022 12:02:27 +0300 Subject: [PATCH 046/133] change documentation + gradle deps --- client-services/build.gradle | 6 ++-- .../touchin/client_services/ServicesUtils.kt | 4 +++ recaptcha/README.md | 30 +++++++++++++++++-- .../ru/touchin/recaptcha/CaptchaManager.kt | 9 ++++-- .../touchin/recaptcha/HuaweiCaptchaClient.kt | 1 + 5 files changed, 42 insertions(+), 8 deletions(-) diff --git a/client-services/build.gradle b/client-services/build.gradle index c55e87e..6799856 100644 --- a/client-services/build.gradle +++ b/client-services/build.gradle @@ -5,7 +5,7 @@ dependencies { implementation "androidx.core:core" implementation "androidx.annotation:annotation" implementation "com.google.android.gms:play-services-base" - implementation "com.huawei.hms:safetydetect" + implementation "com.huawei.hms:base" constraints { implementation("androidx.core:core") { @@ -26,9 +26,9 @@ dependencies { } } - implementation("com.huawei.hms:safetydetect") { + implementation("com.huawei.hms:base") { version { - require '4.0.3.300' + require '6.3.0.303' } } } diff --git a/client-services/src/main/java/ru/touchin/client_services/ServicesUtils.kt b/client-services/src/main/java/ru/touchin/client_services/ServicesUtils.kt index a4566d9..121b736 100644 --- a/client-services/src/main/java/ru/touchin/client_services/ServicesUtils.kt +++ b/client-services/src/main/java/ru/touchin/client_services/ServicesUtils.kt @@ -5,6 +5,10 @@ import com.google.android.gms.common.ConnectionResult import com.google.android.gms.common.GoogleApiAvailability import com.huawei.hms.api.HuaweiApiAvailability +/** + * A class with utils for interacting with Google, Huawei, etc. services + */ + class ServicesUtils { fun getCurrentService(context: Context): MobileService = when { diff --git a/recaptcha/README.md b/recaptcha/README.md index a9b84b7..a437d99 100644 --- a/recaptcha/README.md +++ b/recaptcha/README.md @@ -1,6 +1,32 @@ recaptcha ===== -Модуль содержит класс `RecaptchaController` - служит для проверки используемого сервиса (Huawei или Google) и показа диалога с каптчёй +### Общее описание -Для использования модуля нужно добавить json файл с сервисами в корневую папку проекта +Модуль содержит класс `CaptchaManager` - служит для проверки используемого сервиса (Huawei или Google) и показа диалога с каптчёй +В конструктуре `CaptchaManager` принимает два callback`а: +`onNewTokenReceived` - успешная проверка, возвращает токен +`processThrowable` - ошибка, возвращает `Throwable` + +### Требования + +Для использования модуля нужно добавить json файл с сервисами в корневую папку проекта: +Для Google - google-services.json +Для Huawei - agconnect-services.json + +### Пример + +Во `Fragment` + +```kotlin +val manager = CaptchaManager(onNewTokenReceived = { token -> + viewModel.sendRequest(token) + }, processThrowable = { error -> + showError(error) + }) + +manager.showRecaptchaAlert( + activity = activity, + captchaKey = "6Lc2heYeAAAAAHqe3mp0ylUnvXSY4lYfbRCwsVz_" +) +``` diff --git a/recaptcha/src/main/java/ru/touchin/recaptcha/CaptchaManager.kt b/recaptcha/src/main/java/ru/touchin/recaptcha/CaptchaManager.kt index 2fd00ee..2e5145b 100644 --- a/recaptcha/src/main/java/ru/touchin/recaptcha/CaptchaManager.kt +++ b/recaptcha/src/main/java/ru/touchin/recaptcha/CaptchaManager.kt @@ -5,12 +5,15 @@ import ru.touchin.client_services.MobileService import ru.touchin.client_services.ServicesUtils /** - * onNewTokenReceived - callback на успешную проверку каптчи - * processThrowable - callback на ошибку проверки каптчи + * A class for displaying a dialog with a captcha + * with a check on the current service of the application + * + * @param onNewTokenReceived - callback for a successful captcha check, return token + * @param processThrowable - callback for a captcha check error, return throwable */ class CaptchaManager( - onNewTokenReceived: (String) -> Unit, + private val onNewTokenReceived: (String) -> Unit, private val processThrowable: (Throwable) -> Unit ) { diff --git a/recaptcha/src/main/java/ru/touchin/recaptcha/HuaweiCaptchaClient.kt b/recaptcha/src/main/java/ru/touchin/recaptcha/HuaweiCaptchaClient.kt index 6e860ca..2dd8c20 100644 --- a/recaptcha/src/main/java/ru/touchin/recaptcha/HuaweiCaptchaClient.kt +++ b/recaptcha/src/main/java/ru/touchin/recaptcha/HuaweiCaptchaClient.kt @@ -10,6 +10,7 @@ class HuaweiCaptchaClient( override fun showCaptcha(activity: Activity, captchaKey: String) { val huaweiSafetyDetectClient = SafetyDetect.getClient(activity) + huaweiSafetyDetectClient.initUserDetect() .addOnSuccessListener { huaweiSafetyDetectClient.userDetection(captchaKey) From 05f2adb849158848de8973e6ee90e7cea425e60c Mon Sep 17 00:00:00 2001 From: Rinat Nurmukhametov Date: Thu, 17 Mar 2022 12:14:23 +0300 Subject: [PATCH 047/133] fix for comments --- .../java/ru/touchin/client_services/ServicesUtils.kt | 2 +- recaptcha/README.md | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/client-services/src/main/java/ru/touchin/client_services/ServicesUtils.kt b/client-services/src/main/java/ru/touchin/client_services/ServicesUtils.kt index 121b736..1cc5b2a 100644 --- a/client-services/src/main/java/ru/touchin/client_services/ServicesUtils.kt +++ b/client-services/src/main/java/ru/touchin/client_services/ServicesUtils.kt @@ -6,7 +6,7 @@ import com.google.android.gms.common.GoogleApiAvailability import com.huawei.hms.api.HuaweiApiAvailability /** - * A class with utils for interacting with Google, Huawei, etc. services + * A class with utils for interacting with Google, Huawei services */ class ServicesUtils { diff --git a/recaptcha/README.md b/recaptcha/README.md index a437d99..69a7392 100644 --- a/recaptcha/README.md +++ b/recaptcha/README.md @@ -4,15 +4,16 @@ recaptcha ### Общее описание Модуль содержит класс `CaptchaManager` - служит для проверки используемого сервиса (Huawei или Google) и показа диалога с каптчёй -В конструктуре `CaptchaManager` принимает два callback`а: +В конструктуре `CaptchaManager` принимает два callback: `onNewTokenReceived` - успешная проверка, возвращает токен `processThrowable` - ошибка, возвращает `Throwable` ### Требования Для использования модуля нужно добавить json файл с сервисами в корневую папку проекта: -Для Google - google-services.json -Для Huawei - agconnect-services.json + +1. Для Google - google-services.json +2. Для Huawei - agconnect-services.json ### Пример @@ -27,6 +28,6 @@ val manager = CaptchaManager(onNewTokenReceived = { token -> manager.showRecaptchaAlert( activity = activity, - captchaKey = "6Lc2heYeAAAAAHqe3mp0ylUnvXSY4lYfbRCwsVz_" + captchaKey = BuildConfig.CAPTCHA_TOKEN ) ``` From 82e1cff525aa64dd2a2ff1218468ad40bdf55c25 Mon Sep 17 00:00:00 2001 From: Grigorii Date: Mon, 18 Apr 2022 11:51:21 +0300 Subject: [PATCH 048/133] Remove pretty-print dependency from mvi module --- mvi-arch/build.gradle | 2 -- .../ru/touchin/roboswag/mvi_arch/core/MviViewModel.kt | 6 +++--- .../roboswag/mvi_arch/mediator/LoggingMediator.kt | 9 ++------- 3 files changed, 5 insertions(+), 12 deletions(-) diff --git a/mvi-arch/build.gradle b/mvi-arch/build.gradle index 1532294..77b2a22 100644 --- a/mvi-arch/build.gradle +++ b/mvi-arch/build.gradle @@ -28,8 +28,6 @@ dependencies { implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android") - implementation("com.tylerthrailkill.helpers:pretty-print:2.0.2") - def fragmentVersion = "1.2.1" def lifecycleVersion = "2.2.0" def coroutinesVersion = "1.3.7" diff --git a/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/core/MviViewModel.kt b/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/core/MviViewModel.kt index 8b4b170..ede36e5 100644 --- a/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/core/MviViewModel.kt +++ b/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/core/MviViewModel.kt @@ -9,8 +9,10 @@ import androidx.lifecycle.Transformations import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import kotlinx.coroutines.launch +import ru.touchin.mvi_arch.BuildConfig import ru.touchin.roboswag.mvi_arch.marker.ViewAction import ru.touchin.roboswag.mvi_arch.marker.ViewState +import ru.touchin.roboswag.mvi_arch.mediator.LoggingMediator import ru.touchin.roboswag.mvi_arch.mediator.MediatorStore /** @@ -40,9 +42,7 @@ abstract class MviViewModel Date: Fri, 15 Apr 2022 17:53:43 +0300 Subject: [PATCH 049/133] Create DateFormatUtils class for handling cases with DateTime format --- utils/build.gradle | 14 ++++++++ .../roboswag/core/utils/DateFormatUtils.kt | 32 +++++++++++++++++++ 2 files changed, 46 insertions(+) create mode 100644 utils/src/main/java/ru/touchin/roboswag/core/utils/DateFormatUtils.kt diff --git a/utils/build.gradle b/utils/build.gradle index fe6fd6a..954f81e 100644 --- a/utils/build.gradle +++ b/utils/build.gradle @@ -5,6 +5,8 @@ dependencies { implementation "androidx.core:core" implementation "androidx.annotation:annotation" implementation "com.google.android.material:material" + implementation "net.danlew:android.joda" + implementation "junit:junit" constraints { implementation("androidx.core:core") { @@ -24,5 +26,17 @@ dependencies { require '1.2.0-rc01' } } + + implementation("net.danlew:android.joda") { + version { + require '2.10.2' + } + } + + implementation("junit:junit") { + version { + require '4.13.2' + } + } } } diff --git a/utils/src/main/java/ru/touchin/roboswag/core/utils/DateFormatUtils.kt b/utils/src/main/java/ru/touchin/roboswag/core/utils/DateFormatUtils.kt new file mode 100644 index 0000000..9444416 --- /dev/null +++ b/utils/src/main/java/ru/touchin/roboswag/core/utils/DateFormatUtils.kt @@ -0,0 +1,32 @@ +package ru.touchin.roboswag.core.utils + +import org.joda.time.DateTime +import org.joda.time.format.DateTimeFormat + +/** + * Util object for handling some cases with DateTime e.g. parsing string to DateTime object + */ +object DateFormatUtils { + + enum class Format(val formatValue: String) { + DATE_TIME_FORMAT("yyyy-MM-dd'T'HH:mm:ss.SSSZZ"), + DATE_FORMAT("yyyy-MM-dd"), + TIME_FORMAT("HH:mm:ssZ") + } + + /** + * @return the result of parsed string value + * @param value is string value of date time in right format + * @param format is date time format for parsing string value. + * Default value is [Format.DATE_TIME_FORMAT] + * @param defaultValue is value returned in case of exception + */ + fun fromString( + value: String, + format: Format = Format.DATE_TIME_FORMAT, + defaultValue: DateTime? = null + ): DateTime? = runCatching { value.parse(format.formatValue) }.getOrDefault(defaultValue) + + private fun String.parse(format: String) = DateTimeFormat.forPattern(format).parseDateTime(this) + +} From a3241002c51800d60adb464119ef7de38f04335c Mon Sep 17 00:00:00 2001 From: Kirill Nayduik Date: Fri, 15 Apr 2022 18:22:23 +0300 Subject: [PATCH 050/133] Create DateFormatUtilsTest for testing DateFormatUtils --- utils/src/test/java/DateFormatUtilsTest.kt | 34 ++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 utils/src/test/java/DateFormatUtilsTest.kt diff --git a/utils/src/test/java/DateFormatUtilsTest.kt b/utils/src/test/java/DateFormatUtilsTest.kt new file mode 100644 index 0000000..62b4290 --- /dev/null +++ b/utils/src/test/java/DateFormatUtilsTest.kt @@ -0,0 +1,34 @@ +import org.joda.time.DateTime +import org.joda.time.DateTimeZone +import org.joda.time.tz.UTCProvider +import org.junit.Assert +import org.junit.Test +import ru.touchin.roboswag.core.utils.DateFormatUtils + +class DateFormatUtilsTest { + + init { + DateTimeZone.setProvider(UTCProvider()) + } + + @Test + fun `Assert Date format parsing`() { + val dateTime = DateFormatUtils.fromString( + value = "2015-04-29", + format = DateFormatUtils.Format.DATE_FORMAT + ) + Assert.assertEquals(DateTime(2015, 4, 29, 0, 0, 0), dateTime) + } + + @Test + fun `Assert Date format parsing with default value`() { + val currentDateTime = DateTime.now() + + val dateTime = DateFormatUtils.fromString( + value = "2015-04-29", + format = DateFormatUtils.Format.DATE_TIME_FORMAT, + defaultValue = currentDateTime + ) + Assert.assertEquals(currentDateTime, dateTime) + } +} From 7a37fb20b74f4b9f4913ca5f052144af5305930a Mon Sep 17 00:00:00 2001 From: Kirill Nayduik Date: Mon, 18 Apr 2022 13:13:35 +0300 Subject: [PATCH 051/133] Add implementation of 'joda-time' for tests to avoid issue https://github.com/dlew/joda-time-android/issues/148 --- utils/build.gradle | 23 +++++++++++++++++----- utils/src/test/java/DateFormatUtilsTest.kt | 6 ------ 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/utils/build.gradle b/utils/build.gradle index 954f81e..513c848 100644 --- a/utils/build.gradle +++ b/utils/build.gradle @@ -1,41 +1,54 @@ apply from: "../android-configs/lib-config.gradle" dependencies { + def coreVersion = '1.0.0' + def annotationVersion = '1.1.0' + def materialVersion = '1.2.0-rc01' + def jodaVersion = '2.10.2' + def junitVersion = '4.13.2' + implementation project(':kotlin-extensions') implementation "androidx.core:core" implementation "androidx.annotation:annotation" implementation "com.google.android.material:material" implementation "net.danlew:android.joda" implementation "junit:junit" + testImplementation "joda-time:joda-time" constraints { implementation("androidx.core:core") { version { - require '1.0.0' + require(coreVersion) } } implementation("androidx.annotation:annotation") { version { - require '1.1.0' + require(annotationVersion) } } implementation("com.google.android.material:material") { version { - require '1.2.0-rc01' + require(materialVersion) } } implementation("net.danlew:android.joda") { version { - require '2.10.2' + require(jodaVersion) + } + } + + testImplementation("joda-time:joda-time") { + version { + require(jodaVersion) } } implementation("junit:junit") { version { - require '4.13.2' + require(junitVersion) } } } diff --git a/utils/src/test/java/DateFormatUtilsTest.kt b/utils/src/test/java/DateFormatUtilsTest.kt index 62b4290..8036ede 100644 --- a/utils/src/test/java/DateFormatUtilsTest.kt +++ b/utils/src/test/java/DateFormatUtilsTest.kt @@ -1,16 +1,10 @@ import org.joda.time.DateTime -import org.joda.time.DateTimeZone -import org.joda.time.tz.UTCProvider import org.junit.Assert import org.junit.Test import ru.touchin.roboswag.core.utils.DateFormatUtils class DateFormatUtilsTest { - init { - DateTimeZone.setProvider(UTCProvider()) - } - @Test fun `Assert Date format parsing`() { val dateTime = DateFormatUtils.fromString( From 6aa11eb5e261653756c2371cfc5df129ff50fe8a Mon Sep 17 00:00:00 2001 From: Kirill Nayduik Date: Wed, 11 May 2022 13:42:16 +0300 Subject: [PATCH 052/133] Migrate from bintray to maven.dev --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 7a61b19..e56cb52 100644 --- a/build.gradle +++ b/build.gradle @@ -17,7 +17,7 @@ allprojects { google() jcenter() maven { - url "https://dl.bintray.com/touchin/touchin-tools" + url "https://maven.dev.touchin.ru/" metadataSources { artifact() } From 550cdd34b62b0f831fb82d1ffc58c3db1e3f7714 Mon Sep 17 00:00:00 2001 From: Grigorii Date: Fri, 3 Jun 2022 12:27:38 +0300 Subject: [PATCH 053/133] Fix startNavigationBarHeight initialization in KeyboardBehaviorDetector after DNKA --- .../keyboard_resizeable/KeyboardBehaviorDetector.kt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/navigation-base/src/main/java/ru/touchin/roboswag/navigation_base/keyboard_resizeable/KeyboardBehaviorDetector.kt b/navigation-base/src/main/java/ru/touchin/roboswag/navigation_base/keyboard_resizeable/KeyboardBehaviorDetector.kt index 7b711be..7664877 100644 --- a/navigation-base/src/main/java/ru/touchin/roboswag/navigation_base/keyboard_resizeable/KeyboardBehaviorDetector.kt +++ b/navigation-base/src/main/java/ru/touchin/roboswag/navigation_base/keyboard_resizeable/KeyboardBehaviorDetector.kt @@ -53,6 +53,12 @@ class KeyboardBehaviorDetector( if (startNavigationBarHeight == -1) startNavigationBarHeight = bottomInset + if (startNavigationBarHeight > bottomInset) { + // update height if it was initialized incorrectly + startNavigationBarHeight = bottomInset + listener.invoke(false, windowInsets) + } + ViewCompat.onApplyWindowInsets(view, windowInsets) } ViewCompat.requestApplyInsets(view) From 87657102289dd23268a2e07e6e320d22b9c453f9 Mon Sep 17 00:00:00 2001 From: Grigorii Date: Thu, 14 Jul 2022 19:28:41 +0300 Subject: [PATCH 054/133] PET-3233: fix KeyboardBehaviorDetector when bottomInset == 0 --- .../keyboard_resizeable/KeyboardBehaviorDetector.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/navigation-base/src/main/java/ru/touchin/roboswag/navigation_base/keyboard_resizeable/KeyboardBehaviorDetector.kt b/navigation-base/src/main/java/ru/touchin/roboswag/navigation_base/keyboard_resizeable/KeyboardBehaviorDetector.kt index 7664877..04a03b0 100644 --- a/navigation-base/src/main/java/ru/touchin/roboswag/navigation_base/keyboard_resizeable/KeyboardBehaviorDetector.kt +++ b/navigation-base/src/main/java/ru/touchin/roboswag/navigation_base/keyboard_resizeable/KeyboardBehaviorDetector.kt @@ -53,7 +53,7 @@ class KeyboardBehaviorDetector( if (startNavigationBarHeight == -1) startNavigationBarHeight = bottomInset - if (startNavigationBarHeight > bottomInset) { + if (startNavigationBarHeight > bottomInset && bottomInset != 0) { // update height if it was initialized incorrectly startNavigationBarHeight = bottomInset listener.invoke(false, windowInsets) From aad4c398e820b990857ce932755b75e728367624 Mon Sep 17 00:00:00 2001 From: AnastasiyaK97 Date: Wed, 27 Jul 2022 15:54:02 +0300 Subject: [PATCH 055/133] add base-filter module --- base-filters/.gitignore | 1 + base-filters/build.gradle | 23 +++++++++++++++++++++++ base-filters/src/main/AndroidManifest.xml | 2 ++ 3 files changed, 26 insertions(+) create mode 100644 base-filters/.gitignore create mode 100644 base-filters/build.gradle create mode 100644 base-filters/src/main/AndroidManifest.xml diff --git a/base-filters/.gitignore b/base-filters/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/base-filters/.gitignore @@ -0,0 +1 @@ +/build diff --git a/base-filters/build.gradle b/base-filters/build.gradle new file mode 100644 index 0000000..65ea18b --- /dev/null +++ b/base-filters/build.gradle @@ -0,0 +1,23 @@ +apply from: "../android-configs/lib-config.gradle" + +dependencies { + implementation("org.jetbrains.kotlin:kotlin-stdlib") + + implementation("androidx.core:core-ktx") + + implementation("androidx.appcompat:appcompat") + + constraints { + implementation("androidx.appcompat:appcompat") { + version { + require '1.0.0' + } + } + + implementation("androidx.core:core-ktx") { + version { + require '1.0.0' + } + } + } +} diff --git a/base-filters/src/main/AndroidManifest.xml b/base-filters/src/main/AndroidManifest.xml new file mode 100644 index 0000000..47d581d --- /dev/null +++ b/base-filters/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + From eb9d4adcf20d85858ddf3049eb316068d6a369f6 Mon Sep 17 00:00:00 2001 From: AnastasiyaK97 Date: Thu, 28 Jul 2022 19:54:04 +0300 Subject: [PATCH 056/133] =?UTF-8?q?INTERNAL-300=20+=20INTERNAL-299:=20?= =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B2=D0=BB=D0=B5=D0=BD=D0=B0=20=D0=BD=D0=B0?= =?UTF-8?q?=D1=81=D1=82=D1=80=D0=B0=D0=B8=D0=B2=D0=B0=D0=B5=D0=BC=D0=B0?= =?UTF-8?q?=D1=8F=20=D0=B2=D1=8C=D1=8E=20=D1=81=20=D0=B2=D1=8B=D0=B1=D0=BE?= =?UTF-8?q?=D1=80=D0=BE=D0=BC=20=D0=BE=D0=B4=D0=BD=D0=BE=D0=B3=D0=BE=20?= =?UTF-8?q?=D0=B8=D0=BB=D0=B8=20=D0=BD=D0=B5=D1=81=D0=BA=D0=BE=D0=BB=D1=8C?= =?UTF-8?q?=D0=BA=D0=B8=D1=85=20=D0=B2=D0=B0=D1=80=D0=B8=D0=B0=D0=BD=D1=82?= =?UTF-8?q?=D0=BE=D0=B2=20=D0=B8=D0=B7=20=D1=81=D0=BF=D0=B8=D1=81=D0=BA?= =?UTF-8?q?=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- base-filters/build.gradle | 14 ++++ base-filters/src/main/AndroidManifest.xml | 2 +- .../select_list_item/ListSelectionView.kt | 83 +++++++++++++++++++ .../adapter/SheetSelectionAdapter.kt | 24 ++++++ .../adapter/SheetSelectionDelegate.kt | 47 +++++++++++ .../model/RowSelectionItem.kt | 11 +++ .../src/main/res/layout/selection_item.xml | 29 +++++++ .../res/layout/single_selection_layout.xml | 10 +++ base-filters/src/main/res/values/attrs.xml | 9 ++ base-filters/src/main/res/values/styles.xml | 21 +++++ 10 files changed, 249 insertions(+), 1 deletion(-) create mode 100644 base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/ListSelectionView.kt create mode 100644 base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/adapter/SheetSelectionAdapter.kt create mode 100644 base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/adapter/SheetSelectionDelegate.kt create mode 100644 base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/model/RowSelectionItem.kt create mode 100644 base-filters/src/main/res/layout/selection_item.xml create mode 100644 base-filters/src/main/res/layout/single_selection_layout.xml create mode 100644 base-filters/src/main/res/values/attrs.xml create mode 100644 base-filters/src/main/res/values/styles.xml diff --git a/base-filters/build.gradle b/base-filters/build.gradle index 65ea18b..a8904a1 100644 --- a/base-filters/build.gradle +++ b/base-filters/build.gradle @@ -1,11 +1,25 @@ apply from: "../android-configs/lib-config.gradle" +apply plugin: 'kotlin-parcelize' + +android { + buildFeatures { + viewBinding true + } +} dependencies { + implementation project(":utils") + implementation project(":recyclerview-adapters") + implementation project(":navigation-base") + implementation project(":kotlin-extensions") + implementation("org.jetbrains.kotlin:kotlin-stdlib") implementation("androidx.core:core-ktx") implementation("androidx.appcompat:appcompat") + implementation("com.google.android.material:material") + implementation("androidx.constraintlayout:constraintlayout:2.2.0-alpha03") constraints { implementation("androidx.appcompat:appcompat") { diff --git a/base-filters/src/main/AndroidManifest.xml b/base-filters/src/main/AndroidManifest.xml index 47d581d..14e5b35 100644 --- a/base-filters/src/main/AndroidManifest.xml +++ b/base-filters/src/main/AndroidManifest.xml @@ -1,2 +1,2 @@ + package="ru.touchin.roboswag.base_filters"/> diff --git a/base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/ListSelectionView.kt b/base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/ListSelectionView.kt new file mode 100644 index 0000000..8e30fc8 --- /dev/null +++ b/base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/ListSelectionView.kt @@ -0,0 +1,83 @@ +package ru.touchin.roboswag.base_filters.select_list_item + +import android.content.Context +import android.util.AttributeSet +import android.view.LayoutInflater +import android.widget.FrameLayout +import androidx.recyclerview.widget.RecyclerView +import ru.touchin.roboswag.base_filters.databinding.SingleSelectionLayoutBinding +import ru.touchin.roboswag.base_filters.select_list_item.adapter.SheetSelectionAdapter +import ru.touchin.roboswag.base_filters.select_list_item.model.RowSelectionItem + +typealias OnItemSelectedListener = (item: RowSelectionItem) -> Unit +typealias OnSelectionResultListener = (items: List) -> Unit + +class ListSelectionView @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0, + defStyleRes: Int = 0 +) : FrameLayout(context, attrs, defStyleAttr, defStyleRes) { + + private var mutableItems: List = emptyList() + private var selectionType = SelectionType.SINGLE_SELECT + + private var onItemsClickListener: OnSelectionResultListener? = null + private var onItemClickListener: OnItemSelectedListener? = null + + private val binding = SingleSelectionLayoutBinding.inflate(LayoutInflater.from(context), this, true) + + private val adapter by lazy { + SheetSelectionAdapter(onItemSelectAction = onItemSelectedListener) + } + + private val onItemSelectedListener: OnItemSelectedListener = { item -> + onItemClickListener?.invoke(item) + updateAfterSelection(item) + onItemsClickListener?.invoke(mutableItems) + } + + private fun updateList() { + adapter.submitList(mutableItems) + } + + private fun updateAfterSelection(selectedItem: RowSelectionItem) { + mutableItems = mutableItems.map { item -> + when { + item.id == selectedItem.id -> selectedItem + selectionType == SelectionType.SINGLE_SELECT -> item.copy(isSelected = false) + else -> item + } + } + updateList() + } + + fun setItems(items: List) = apply { + binding.itemsRecycler.adapter = adapter + mutableItems = items + updateList() + } + + fun setItems( + source: List, + mapper: (T) -> RowSelectionItem + ) = setItems(source.map { item -> mapper.invoke(item) }) + + fun addItemDecoration(itemDecoration: RecyclerView.ItemDecoration) = apply { + binding.itemsRecycler.addItemDecoration(itemDecoration) + } + + fun onItemClickListener(listener: OnItemSelectedListener) = apply { + this@ListSelectionView.onItemClickListener = listener + } + + fun onResultListener(listener: OnSelectionResultListener) = apply { + this@ListSelectionView.onItemsClickListener = listener + } + + fun withSelectionType(type: SelectionType) = apply { + selectionType = type + } + + enum class SelectionType { SINGLE_SELECT, MULTI_SELECT } +} diff --git a/base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/adapter/SheetSelectionAdapter.kt b/base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/adapter/SheetSelectionAdapter.kt new file mode 100644 index 0000000..6b92b5a --- /dev/null +++ b/base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/adapter/SheetSelectionAdapter.kt @@ -0,0 +1,24 @@ +package ru.touchin.roboswag.base_filters.select_list_item.adapter + +import androidx.recyclerview.widget.DiffUtil +import ru.touchin.roboswag.base_filters.select_list_item.OnItemSelectedListener +import ru.touchin.roboswag.base_filters.select_list_item.model.RowSelectionItem +import ru.touchin.roboswag.recyclerview_adapters.adapters.DelegationListAdapter + +class SheetSelectionAdapter( + onItemSelectAction: OnItemSelectedListener +): DelegationListAdapter(object : DiffUtil.ItemCallback() { + + override fun areItemsTheSame(oldItem: RowSelectionItem, newItem: RowSelectionItem): Boolean = + oldItem.id == newItem.id + + override fun areContentsTheSame(oldItem: RowSelectionItem, newItem: RowSelectionItem): Boolean = + oldItem == newItem + +}) { + + init { + addDelegate(SheetSelectionDelegate(onItemSelectAction)) + } + +} diff --git a/base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/adapter/SheetSelectionDelegate.kt b/base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/adapter/SheetSelectionDelegate.kt new file mode 100644 index 0000000..4dbfa35 --- /dev/null +++ b/base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/adapter/SheetSelectionDelegate.kt @@ -0,0 +1,47 @@ +package ru.touchin.roboswag.base_filters.select_list_item.adapter + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import ru.touchin.roboswag.base_filters.databinding.SelectionItemBinding +import ru.touchin.roboswag.base_filters.select_list_item.OnItemSelectedListener +import ru.touchin.roboswag.base_filters.select_list_item.model.RowSelectionItem +import ru.touchin.roboswag.recyclerview_adapters.adapters.ItemAdapterDelegate + +class SheetSelectionDelegate( + private val onItemSelectAction: OnItemSelectedListener +) : ItemAdapterDelegate() { + + override fun onCreateViewHolder(parent: ViewGroup): SelectionItemViewHolder = SelectionItemViewHolder( + binding = SelectionItemBinding.inflate(LayoutInflater.from(parent.context), parent, false) + ) + + override fun onBindViewHolder( + holder: SelectionItemViewHolder, + item: RowSelectionItem, + adapterPosition: Int, + collectionPosition: Int, + payloads: MutableList + ) = holder.bind(item) + + inner class SelectionItemViewHolder(val binding: SelectionItemBinding) : RecyclerView.ViewHolder(binding.root) { + + fun bind(item: RowSelectionItem) { + binding.run { + val checkListener = View.OnClickListener { + itemRadiobutton.isChecked = true + onItemSelectAction.invoke(item.copy(isSelected = !item.isSelected)) + } + + itemTitle.text = item.title + root.setOnClickListener(checkListener) + + itemRadiobutton.setOnClickListener(checkListener) + itemRadiobutton.isChecked = item.isSelected + } + } + + } + +} diff --git a/base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/model/RowSelectionItem.kt b/base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/model/RowSelectionItem.kt new file mode 100644 index 0000000..007eb69 --- /dev/null +++ b/base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/model/RowSelectionItem.kt @@ -0,0 +1,11 @@ +package ru.touchin.roboswag.base_filters.select_list_item.model + +import android.os.Parcelable +import kotlinx.parcelize.Parcelize + +@Parcelize +data class RowSelectionItem( + val id: Int, + val title: String, + val isSelected: Boolean = false +): Parcelable diff --git a/base-filters/src/main/res/layout/selection_item.xml b/base-filters/src/main/res/layout/selection_item.xml new file mode 100644 index 0000000..72bb7eb --- /dev/null +++ b/base-filters/src/main/res/layout/selection_item.xml @@ -0,0 +1,29 @@ + + + + + + + + diff --git a/base-filters/src/main/res/layout/single_selection_layout.xml b/base-filters/src/main/res/layout/single_selection_layout.xml new file mode 100644 index 0000000..2db23f9 --- /dev/null +++ b/base-filters/src/main/res/layout/single_selection_layout.xml @@ -0,0 +1,10 @@ + + diff --git a/base-filters/src/main/res/values/attrs.xml b/base-filters/src/main/res/values/attrs.xml new file mode 100644 index 0000000..bd6618a --- /dev/null +++ b/base-filters/src/main/res/values/attrs.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/base-filters/src/main/res/values/styles.xml b/base-filters/src/main/res/values/styles.xml new file mode 100644 index 0000000..3520002 --- /dev/null +++ b/base-filters/src/main/res/values/styles.xml @@ -0,0 +1,21 @@ + + + + + + + + + + From d08800af46e4a72a84ded333fa6c35711cb0385d Mon Sep 17 00:00:00 2001 From: AnastasiyaK97 Date: Fri, 29 Jul 2022 12:41:30 +0300 Subject: [PATCH 057/133] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=20readme=20=D1=84=D0=B0=D0=B9=D0=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- base-filters/README.md | 70 +++++++++++++++++++ .../select_list_item/ListSelectionView.kt | 12 +++- .../adapter/SheetSelectionAdapter.kt | 6 +- .../adapter/SheetSelectionDelegate.kt | 10 ++- 4 files changed, 91 insertions(+), 7 deletions(-) create mode 100644 base-filters/README.md diff --git a/base-filters/README.md b/base-filters/README.md new file mode 100644 index 0000000..cb6175b --- /dev/null +++ b/base-filters/README.md @@ -0,0 +1,70 @@ +# Описание + +Модуль содержит реализацию следующих типов фильтров: + +1. Выбор одного из доступных значений списка +2. Выбор нескольких доступных значений из списка +3. добавить остальные по ходу реализаации + +# Использование + +## Выбор одного/нескольких из доступных значений списка + +### Как использовать +``` kotlin +val selectorView = ListSelectionView(context) + .setItems(navArgs.items) + .addItemDecoration((TopDividerItemDecoration( + context = requireContext(), + drawableId = R.drawable.list_divider_1dp, + startMargin = START_MARGIN_DIVIDER_DP.px + ))) + .withSelectionType(ListSelectionView.SelectionType.SINGLE_SELECT) + .onResultListener { items -> + viewModel.dispatchAction(SelectItemAction.SelectItem(items)) } + .build() +``` +### Конфигурации +* в метод `setItems(List)` необходимо передать список объектов +* метод `addItemDecoration()` можно использовать для передачи объекта `RecyclerView.ItemDecoration` +* метод `withSelectionType()` используется для указания типа выбора: + * `SINGLE_SELECT` - по умолчанию - позволяет выбрать один выариант, при этом будет выбран всегда как минимум один вариант + * `MULTI_SELECT` - позволяет выбрать несколько вариантов из списка, при этом можно полностью выбрать все варианты и убрать выделение со всех вариантов +* колбэк `onResultListener()` можно использовать для получения списка всех элементов `RowSelectionItem` после каждого выбора +* колбэк `onItemClickListener()` можно использовать для получения элемента списка `RowSelectionItem`, по которому произошел клик +* после вызова конфигурационных методов обязательно необходимо вызать метод `build()` + +### Кастомизация стиля + +#### 1. Определить кастомную тему и стили элементов +1. Стиль для **текста элемента списка** должен быть наследником стиля `Widget.FilterSelection.Item` +``` xml + +``` +2. Стиль для **индикатора выбора** должен быть наследником стиля `Widget.FilterSelection.Radio` +Передайте `selector-drawable` для кастомизации вида индикатора в конце строки +``` xml + +``` +3. Создайте **тему**, которая должна быть наследником `Theme.FilterSelection` +``` xml + +``` +#### 2. Применить тему при создании view +При создании вью в коде можно указать тему, используя `ContextThemeWrapper` +``` kotlin +val newContext = ContextThemeWrapper(requireContext(), R.style.Theme_Custom_FilterSelection) +val selectorView = ListSelectionView(newContext) + ... + .build() +``` diff --git a/base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/ListSelectionView.kt b/base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/ListSelectionView.kt index 8e30fc8..f217c65 100644 --- a/base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/ListSelectionView.kt +++ b/base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/ListSelectionView.kt @@ -28,7 +28,10 @@ class ListSelectionView @JvmOverloads constructor( private val binding = SingleSelectionLayoutBinding.inflate(LayoutInflater.from(context), this, true) private val adapter by lazy { - SheetSelectionAdapter(onItemSelectAction = onItemSelectedListener) + SheetSelectionAdapter( + onItemSelectAction = onItemSelectedListener, + selectionType = selectionType + ) } private val onItemSelectedListener: OnItemSelectedListener = { item -> @@ -53,9 +56,7 @@ class ListSelectionView @JvmOverloads constructor( } fun setItems(items: List) = apply { - binding.itemsRecycler.adapter = adapter mutableItems = items - updateList() } fun setItems( @@ -79,5 +80,10 @@ class ListSelectionView @JvmOverloads constructor( selectionType = type } + fun build() = apply { + binding.itemsRecycler.adapter = adapter + updateList() + } + enum class SelectionType { SINGLE_SELECT, MULTI_SELECT } } diff --git a/base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/adapter/SheetSelectionAdapter.kt b/base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/adapter/SheetSelectionAdapter.kt index 6b92b5a..93fadef 100644 --- a/base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/adapter/SheetSelectionAdapter.kt +++ b/base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/adapter/SheetSelectionAdapter.kt @@ -1,12 +1,14 @@ package ru.touchin.roboswag.base_filters.select_list_item.adapter import androidx.recyclerview.widget.DiffUtil +import ru.touchin.roboswag.base_filters.select_list_item.ListSelectionView import ru.touchin.roboswag.base_filters.select_list_item.OnItemSelectedListener import ru.touchin.roboswag.base_filters.select_list_item.model.RowSelectionItem import ru.touchin.roboswag.recyclerview_adapters.adapters.DelegationListAdapter class SheetSelectionAdapter( - onItemSelectAction: OnItemSelectedListener + onItemSelectAction: OnItemSelectedListener, + selectionType: ListSelectionView.SelectionType ): DelegationListAdapter(object : DiffUtil.ItemCallback() { override fun areItemsTheSame(oldItem: RowSelectionItem, newItem: RowSelectionItem): Boolean = @@ -18,7 +20,7 @@ class SheetSelectionAdapter( }) { init { - addDelegate(SheetSelectionDelegate(onItemSelectAction)) + addDelegate(SheetSelectionDelegate(onItemSelectAction, selectionType)) } } diff --git a/base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/adapter/SheetSelectionDelegate.kt b/base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/adapter/SheetSelectionDelegate.kt index 4dbfa35..31982ba 100644 --- a/base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/adapter/SheetSelectionDelegate.kt +++ b/base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/adapter/SheetSelectionDelegate.kt @@ -5,12 +5,15 @@ import android.view.View import android.view.ViewGroup import androidx.recyclerview.widget.RecyclerView import ru.touchin.roboswag.base_filters.databinding.SelectionItemBinding +import ru.touchin.roboswag.base_filters.select_list_item.ListSelectionView +import ru.touchin.roboswag.base_filters.select_list_item.ListSelectionView.SelectionType import ru.touchin.roboswag.base_filters.select_list_item.OnItemSelectedListener import ru.touchin.roboswag.base_filters.select_list_item.model.RowSelectionItem import ru.touchin.roboswag.recyclerview_adapters.adapters.ItemAdapterDelegate class SheetSelectionDelegate( - private val onItemSelectAction: OnItemSelectedListener + private val onItemSelectAction: OnItemSelectedListener, + private val selectionType: ListSelectionView.SelectionType ) : ItemAdapterDelegate() { override fun onCreateViewHolder(parent: ViewGroup): SelectionItemViewHolder = SelectionItemViewHolder( @@ -31,7 +34,10 @@ class SheetSelectionDelegate( binding.run { val checkListener = View.OnClickListener { itemRadiobutton.isChecked = true - onItemSelectAction.invoke(item.copy(isSelected = !item.isSelected)) + onItemSelectAction.invoke(item.copy(isSelected = when (selectionType) { + SelectionType.SINGLE_SELECT -> true + else -> !item.isSelected + })) } itemTitle.text = item.title From 719252a3e182df32333893fcf2483aac39787b01 Mon Sep 17 00:00:00 2001 From: AnastasiyaK97 Date: Mon, 1 Aug 2022 11:37:49 +0300 Subject: [PATCH 058/133] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B0=20=D0=B2=D0=BE=D0=B7=D0=BC=D0=BE=D0=B6=D0=BD?= =?UTF-8?q?=D0=BE=D1=81=D1=82=D1=8C=20=D0=B8=D1=81=D0=BF=D0=BE=D0=BB=D1=8C?= =?UTF-8?q?=D0=B7=D0=BE=D0=B2=D0=B0=D1=82=D1=8C=20=D0=B2=20=D1=81=D0=BF?= =?UTF-8?q?=D0=B8=D1=81=D0=BA=D0=B5=20=D0=BA=D0=B0=D1=81=D1=82=D0=BE=D0=BC?= =?UTF-8?q?=D0=BD=D1=83=D1=8E=20=D1=80=D0=B0=D0=B7=D0=BC=D0=B5=D1=82=D0=BA?= =?UTF-8?q?=D1=83=20=D0=B8=20=D1=82=D0=B8=D0=BF=20=D0=B4=D0=B0=D0=BD=D0=BD?= =?UTF-8?q?=D1=8B=D1=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- base-filters/README.md | 29 ++++++-- .../select_list_item/ListSelectionView.kt | 73 +++++++++++++------ .../adapter/BaseSelectionViewHolder.kt | 11 +++ .../adapter/SheetSelectionAdapter.kt | 26 ++++--- .../adapter/SheetSelectionDelegate.kt | 63 ++++++++-------- .../model/BaseSelectionItem.kt | 14 ++++ .../model/DefaultSelectionItem.kt | 24 ++++++ .../model/RowSelectionItem.kt | 11 --- 8 files changed, 171 insertions(+), 80 deletions(-) create mode 100644 base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/adapter/BaseSelectionViewHolder.kt create mode 100644 base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/model/BaseSelectionItem.kt create mode 100644 base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/model/DefaultSelectionItem.kt delete mode 100644 base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/model/RowSelectionItem.kt diff --git a/base-filters/README.md b/base-filters/README.md index cb6175b..09d83de 100644 --- a/base-filters/README.md +++ b/base-filters/README.md @@ -12,7 +12,7 @@ ### Как использовать ``` kotlin -val selectorView = ListSelectionView(context) +val selectorView = ListSelectionView>(context) .setItems(navArgs.items) .addItemDecoration((TopDividerItemDecoration( context = requireContext(), @@ -25,16 +25,31 @@ val selectorView = ListSelectionView(context) .build() ``` ### Конфигурации -* в метод `setItems(List)` необходимо передать список объектов -* метод `addItemDecoration()` можно использовать для передачи объекта `RecyclerView.ItemDecoration` -* метод `withSelectionType()` используется для указания типа выбора: +* при создании `ListSelectionView` необходимо передлать `ItemType` - класс модели данных в списке, `HolderType` - класс viewHolder-а в recyclerView. +Для использования дефолтной реализации необходимо использовать типы `>` +* в метод `setItems(List)` необходимо передать список объектов +* метод `addItemDecoration(itemDecoration: RecyclerView.ItemDecoration)` можно использовать для передачи объекта `RecyclerView.ItemDecoration` +* метод `withSelectionType(type: SelectionType)` используется для указания типа выбора: * `SINGLE_SELECT` - по умолчанию - позволяет выбрать один выариант, при этом будет выбран всегда как минимум один вариант * `MULTI_SELECT` - позволяет выбрать несколько вариантов из списка, при этом можно полностью выбрать все варианты и убрать выделение со всех вариантов -* колбэк `onResultListener()` можно использовать для получения списка всех элементов `RowSelectionItem` после каждого выбора -* колбэк `onItemClickListener()` можно использовать для получения элемента списка `RowSelectionItem`, по которому произошел клик +* метод `showInHolder(HolderFactoryType)` используется для определения кастомного viewHolder для списка с недефолтной разметкой. +``` kotlin +val selectorView = ListSelectionView(context) + .showInHolder { parent, clickListener, selectionType -> + TestItemViewHolder( + binding = TestSelectionItemBinding.inflate(LayoutInflater.from(parent.context), parent, false), + onItemSelectAction = clickListener, + selectionType = selectionType + ) + } + ... + .build() +``` +* колбэк `onSelectedItemsListener(listener: OnSelectedItemsListener)` можно использовать для получения списка всех элементов `ItemType` после каждого выбора +* колбэк `onSelectedItemListener(listener: OnSelectedItemListener)` можно использовать для получения элемента списка `ItemType`, по которому произошел клик * после вызова конфигурационных методов обязательно необходимо вызать метод `build()` -### Кастомизация стиля +### Кастомизация стиля дефолтной реализации ViewHolder без необходимости создания кастомного layout и viewHolder #### 1. Определить кастомную тему и стили элементов 1. Стиль для **текста элемента списка** должен быть наследником стиля `Widget.FilterSelection.Item` diff --git a/base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/ListSelectionView.kt b/base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/ListSelectionView.kt index f217c65..e095ada 100644 --- a/base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/ListSelectionView.kt +++ b/base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/ListSelectionView.kt @@ -5,75 +5,106 @@ import android.util.AttributeSet import android.view.LayoutInflater import android.widget.FrameLayout import androidx.recyclerview.widget.RecyclerView +import ru.touchin.roboswag.base_filters.databinding.SelectionItemBinding import ru.touchin.roboswag.base_filters.databinding.SingleSelectionLayoutBinding +import ru.touchin.roboswag.base_filters.select_list_item.adapter.BaseSelectionViewHolder +import ru.touchin.roboswag.base_filters.select_list_item.adapter.HolderFactoryType +import ru.touchin.roboswag.base_filters.select_list_item.adapter.SelectionItemViewHolder import ru.touchin.roboswag.base_filters.select_list_item.adapter.SheetSelectionAdapter -import ru.touchin.roboswag.base_filters.select_list_item.model.RowSelectionItem +import ru.touchin.roboswag.base_filters.select_list_item.model.BaseSelectionItem -typealias OnItemSelectedListener = (item: RowSelectionItem) -> Unit -typealias OnSelectionResultListener = (items: List) -> Unit +typealias OnSelectedItemListener = (item: ItemType) -> Unit +typealias OnSelectedItemsListener = (items: List) -> Unit -class ListSelectionView @JvmOverloads constructor( +/** + * Base [ListSelectionView] to use in filters screen for choosing single or multi items in list. + * + * @param ItemType Type of model's element in list. + * It must implement [BaseSelectionItem] abstract class. + * + * @param HolderType Type of viewHolder in recyclerView. + * It must implement [BaseSelectionViewHolder] abstract class. + * +**/ + +class ListSelectionView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0, defStyleRes: Int = 0 -) : FrameLayout(context, attrs, defStyleAttr, defStyleRes) { +) : FrameLayout(context, attrs, defStyleAttr, defStyleRes) + where ItemType: BaseSelectionItem, + HolderType: BaseSelectionViewHolder{ - private var mutableItems: List = emptyList() + private var mutableItems: List = emptyList() private var selectionType = SelectionType.SINGLE_SELECT - private var onItemsClickListener: OnSelectionResultListener? = null - private var onItemClickListener: OnItemSelectedListener? = null + private var onSelectedItemChanged: OnSelectedItemListener? = null + private var onSelectedItemsChanged: OnSelectedItemsListener? = null + private var factory: HolderFactoryType = getDefaultFactory() + + private fun getDefaultFactory(): HolderFactoryType = { parent, clickListener, selectionType -> + SelectionItemViewHolder( + binding = SelectionItemBinding.inflate(LayoutInflater.from(parent.context), parent, false), + onItemSelectAction = clickListener, + selectionType = selectionType + ) + } private val binding = SingleSelectionLayoutBinding.inflate(LayoutInflater.from(context), this, true) private val adapter by lazy { SheetSelectionAdapter( onItemSelectAction = onItemSelectedListener, - selectionType = selectionType + selectionType = selectionType, + factory = factory ) } - private val onItemSelectedListener: OnItemSelectedListener = { item -> - onItemClickListener?.invoke(item) + private val onItemSelectedListener: (item: ItemType) -> Unit = { item -> + onSelectedItemChanged?.invoke(item) updateAfterSelection(item) - onItemsClickListener?.invoke(mutableItems) + onSelectedItemsChanged?.invoke(mutableItems) } private fun updateList() { adapter.submitList(mutableItems) } - private fun updateAfterSelection(selectedItem: RowSelectionItem) { + private fun updateAfterSelection(selectedItem: ItemType) { mutableItems = mutableItems.map { item -> when { - item.id == selectedItem.id -> selectedItem - selectionType == SelectionType.SINGLE_SELECT -> item.copy(isSelected = false) + item.isItemTheSame(selectedItem) -> selectedItem + selectionType == SelectionType.SINGLE_SELECT -> item.copyWithSelection(isSelected = false) else -> item } } updateList() } - fun setItems(items: List) = apply { + fun setItems(items: List) = apply { mutableItems = items } fun setItems( source: List, - mapper: (T) -> RowSelectionItem + mapper: (T) -> ItemType ) = setItems(source.map { item -> mapper.invoke(item) }) + fun showInHolder(holderFactory: HolderFactoryType) = apply { + factory = holderFactory + } + fun addItemDecoration(itemDecoration: RecyclerView.ItemDecoration) = apply { binding.itemsRecycler.addItemDecoration(itemDecoration) } - fun onItemClickListener(listener: OnItemSelectedListener) = apply { - this@ListSelectionView.onItemClickListener = listener + fun onSelectedItemListener(listener: OnSelectedItemListener) = apply { + this@ListSelectionView.onSelectedItemChanged = listener } - fun onResultListener(listener: OnSelectionResultListener) = apply { - this@ListSelectionView.onItemsClickListener = listener + fun onSelectedItemsListener(listener: OnSelectedItemsListener) = apply { + this@ListSelectionView.onSelectedItemsChanged = listener } fun withSelectionType(type: SelectionType) = apply { diff --git a/base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/adapter/BaseSelectionViewHolder.kt b/base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/adapter/BaseSelectionViewHolder.kt new file mode 100644 index 0000000..a4b9af0 --- /dev/null +++ b/base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/adapter/BaseSelectionViewHolder.kt @@ -0,0 +1,11 @@ +package ru.touchin.roboswag.base_filters.select_list_item.adapter + +import android.view.View +import androidx.recyclerview.widget.RecyclerView +import ru.touchin.roboswag.base_filters.select_list_item.model.BaseSelectionItem + +abstract class BaseSelectionViewHolder(val view: View) + : RecyclerView.ViewHolder(view) { + + abstract fun bind(item: ItemType) +} diff --git a/base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/adapter/SheetSelectionAdapter.kt b/base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/adapter/SheetSelectionAdapter.kt index 93fadef..c88d331 100644 --- a/base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/adapter/SheetSelectionAdapter.kt +++ b/base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/adapter/SheetSelectionAdapter.kt @@ -2,25 +2,29 @@ package ru.touchin.roboswag.base_filters.select_list_item.adapter import androidx.recyclerview.widget.DiffUtil import ru.touchin.roboswag.base_filters.select_list_item.ListSelectionView -import ru.touchin.roboswag.base_filters.select_list_item.OnItemSelectedListener -import ru.touchin.roboswag.base_filters.select_list_item.model.RowSelectionItem +import ru.touchin.roboswag.base_filters.select_list_item.model.BaseSelectionItem import ru.touchin.roboswag.recyclerview_adapters.adapters.DelegationListAdapter -class SheetSelectionAdapter( - onItemSelectAction: OnItemSelectedListener, - selectionType: ListSelectionView.SelectionType -): DelegationListAdapter(object : DiffUtil.ItemCallback() { +class SheetSelectionAdapter( + onItemSelectAction: (ItemType) -> Unit, + selectionType: ListSelectionView.SelectionType, + factory: HolderFactoryType +): DelegationListAdapter(object : DiffUtil.ItemCallback() { - override fun areItemsTheSame(oldItem: RowSelectionItem, newItem: RowSelectionItem): Boolean = - oldItem.id == newItem.id + override fun areItemsTheSame(oldItem: BaseSelectionItem, newItem: BaseSelectionItem): Boolean = + oldItem.isItemTheSame(newItem) - override fun areContentsTheSame(oldItem: RowSelectionItem, newItem: RowSelectionItem): Boolean = - oldItem == newItem + override fun areContentsTheSame(oldItem: BaseSelectionItem, newItem: BaseSelectionItem): Boolean = + oldItem.isContentTheSame(newItem) }) { init { - addDelegate(SheetSelectionDelegate(onItemSelectAction, selectionType)) + addDelegate(SheetSelectionDelegate( + onItemSelectAction = onItemSelectAction, + selectionType = selectionType, + factory = factory + )) } } diff --git a/base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/adapter/SheetSelectionDelegate.kt b/base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/adapter/SheetSelectionDelegate.kt index 31982ba..15540a5 100644 --- a/base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/adapter/SheetSelectionDelegate.kt +++ b/base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/adapter/SheetSelectionDelegate.kt @@ -1,53 +1,56 @@ package ru.touchin.roboswag.base_filters.select_list_item.adapter -import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import androidx.recyclerview.widget.RecyclerView import ru.touchin.roboswag.base_filters.databinding.SelectionItemBinding -import ru.touchin.roboswag.base_filters.select_list_item.ListSelectionView import ru.touchin.roboswag.base_filters.select_list_item.ListSelectionView.SelectionType -import ru.touchin.roboswag.base_filters.select_list_item.OnItemSelectedListener -import ru.touchin.roboswag.base_filters.select_list_item.model.RowSelectionItem +import ru.touchin.roboswag.base_filters.select_list_item.model.BaseSelectionItem import ru.touchin.roboswag.recyclerview_adapters.adapters.ItemAdapterDelegate -class SheetSelectionDelegate( - private val onItemSelectAction: OnItemSelectedListener, - private val selectionType: ListSelectionView.SelectionType -) : ItemAdapterDelegate() { +typealias HolderFactoryType = (ViewGroup, (ItemType) -> Unit, SelectionType) -> BaseSelectionViewHolder - override fun onCreateViewHolder(parent: ViewGroup): SelectionItemViewHolder = SelectionItemViewHolder( - binding = SelectionItemBinding.inflate(LayoutInflater.from(parent.context), parent, false) - ) +class SheetSelectionDelegate( + private val onItemSelectAction: (ItemType) -> Unit, + private val selectionType: SelectionType, + private val factory: HolderFactoryType +) : ItemAdapterDelegate, ItemType>() + where ItemType : BaseSelectionItem { + + override fun onCreateViewHolder(parent: ViewGroup): BaseSelectionViewHolder = + factory.invoke(parent, onItemSelectAction, selectionType) override fun onBindViewHolder( - holder: SelectionItemViewHolder, - item: RowSelectionItem, + holder: BaseSelectionViewHolder, + item: ItemType, adapterPosition: Int, collectionPosition: Int, payloads: MutableList ) = holder.bind(item) - inner class SelectionItemViewHolder(val binding: SelectionItemBinding) : RecyclerView.ViewHolder(binding.root) { +} - fun bind(item: RowSelectionItem) { - binding.run { - val checkListener = View.OnClickListener { - itemRadiobutton.isChecked = true - onItemSelectAction.invoke(item.copy(isSelected = when (selectionType) { - SelectionType.SINGLE_SELECT -> true - else -> !item.isSelected - })) - } +class SelectionItemViewHolder(private val binding: SelectionItemBinding, + private val onItemSelectAction: (ItemType) -> Unit, + private val selectionType: SelectionType + ) : BaseSelectionViewHolder(binding.root) { - itemTitle.text = item.title - root.setOnClickListener(checkListener) - - itemRadiobutton.setOnClickListener(checkListener) - itemRadiobutton.isChecked = item.isSelected + override fun bind(item: ItemType) { + binding.run { + val checkListener = View.OnClickListener { + itemRadiobutton.isChecked = true + onItemSelectAction.invoke(item.copyWithSelection(isSelected = when (selectionType) { + SelectionType.SINGLE_SELECT -> true + else -> !item.isSelected + })) } - } + itemTitle.text = item.title + root.setOnClickListener(checkListener) + + itemRadiobutton.setOnClickListener(checkListener) + itemRadiobutton.isChecked = item.isSelected + } } } + diff --git a/base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/model/BaseSelectionItem.kt b/base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/model/BaseSelectionItem.kt new file mode 100644 index 0000000..b19d0d0 --- /dev/null +++ b/base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/model/BaseSelectionItem.kt @@ -0,0 +1,14 @@ +package ru.touchin.roboswag.base_filters.select_list_item.model + +abstract class BaseSelectionItem( + open val id: Int, + open val title: String, + open val isSelected: Boolean +) { + + abstract fun isItemTheSame(compareItem: BaseSelectionItem): Boolean + + abstract fun isContentTheSame(compareItem: BaseSelectionItem): Boolean + + abstract fun copyWithSelection(isSelected: Boolean): ItemType +} diff --git a/base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/model/DefaultSelectionItem.kt b/base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/model/DefaultSelectionItem.kt new file mode 100644 index 0000000..603961f --- /dev/null +++ b/base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/model/DefaultSelectionItem.kt @@ -0,0 +1,24 @@ +package ru.touchin.roboswag.base_filters.select_list_item.model + +import android.os.Parcelable +import kotlinx.parcelize.Parcelize + +@Parcelize +data class DefaultSelectionItem( + override val id: Int, + override val title: String, + override val isSelected: Boolean = false +) : BaseSelectionItem(id, title, isSelected), Parcelable { + + override fun isItemTheSame(compareItem: BaseSelectionItem): Boolean = when { + compareItem is DefaultSelectionItem && id == compareItem.id -> true + else -> false + } + + override fun isContentTheSame(compareItem: BaseSelectionItem): Boolean = + this == compareItem + + @Suppress("UNCHECKED_CAST") + override fun copyWithSelection(isSelected: Boolean): ItemType = + this.copy(isSelected = isSelected) as ItemType +} diff --git a/base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/model/RowSelectionItem.kt b/base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/model/RowSelectionItem.kt deleted file mode 100644 index 007eb69..0000000 --- a/base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/model/RowSelectionItem.kt +++ /dev/null @@ -1,11 +0,0 @@ -package ru.touchin.roboswag.base_filters.select_list_item.model - -import android.os.Parcelable -import kotlinx.parcelize.Parcelize - -@Parcelize -data class RowSelectionItem( - val id: Int, - val title: String, - val isSelected: Boolean = false -): Parcelable From d8dc470805e5302499ec29d2d0fd7f35e84bc831 Mon Sep 17 00:00:00 2001 From: AnastasiyaK97 Date: Mon, 1 Aug 2022 12:42:00 +0300 Subject: [PATCH 059/133] =?UTF-8?q?SelectionItemViewHolder=20=D0=B2=D1=8B?= =?UTF-8?q?=D0=BD=D0=B5=D1=81=D0=B5=D0=BD=20=D0=B2=20=D0=BE=D1=82=D0=B4?= =?UTF-8?q?=D0=B5=D0=BB=D1=8C=D0=BD=D1=8B=D0=B9=20=D1=84=D0=B0=D0=B9=D0=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../adapter/SelectionItemViewHolder.kt | 31 +++++++++++++++++++ .../adapter/SheetSelectionDelegate.kt | 28 ----------------- 2 files changed, 31 insertions(+), 28 deletions(-) create mode 100644 base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/adapter/SelectionItemViewHolder.kt diff --git a/base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/adapter/SelectionItemViewHolder.kt b/base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/adapter/SelectionItemViewHolder.kt new file mode 100644 index 0000000..cc81c0c --- /dev/null +++ b/base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/adapter/SelectionItemViewHolder.kt @@ -0,0 +1,31 @@ +package ru.touchin.roboswag.base_filters.select_list_item.adapter + +import android.view.View +import ru.touchin.roboswag.base_filters.databinding.SelectionItemBinding +import ru.touchin.roboswag.base_filters.select_list_item.ListSelectionView +import ru.touchin.roboswag.base_filters.select_list_item.model.BaseSelectionItem + +class SelectionItemViewHolder(private val binding: SelectionItemBinding, + private val onItemSelectAction: (ItemType) -> Unit, + private val selectionType: ListSelectionView.SelectionType + ) : BaseSelectionViewHolder(binding.root) { + + override fun bind(item: ItemType) { + binding.run { + val checkListener = View.OnClickListener { + itemRadiobutton.isChecked = true + onItemSelectAction.invoke(item.copyWithSelection(isSelected = when (selectionType) { + ListSelectionView.SelectionType.SINGLE_SELECT -> true + else -> !item.isSelected + })) + } + + itemTitle.text = item.title + root.setOnClickListener(checkListener) + + itemRadiobutton.setOnClickListener(checkListener) + itemRadiobutton.isChecked = item.isSelected + } + } + +} diff --git a/base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/adapter/SheetSelectionDelegate.kt b/base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/adapter/SheetSelectionDelegate.kt index 15540a5..14f5460 100644 --- a/base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/adapter/SheetSelectionDelegate.kt +++ b/base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/adapter/SheetSelectionDelegate.kt @@ -1,8 +1,6 @@ package ru.touchin.roboswag.base_filters.select_list_item.adapter -import android.view.View import android.view.ViewGroup -import ru.touchin.roboswag.base_filters.databinding.SelectionItemBinding import ru.touchin.roboswag.base_filters.select_list_item.ListSelectionView.SelectionType import ru.touchin.roboswag.base_filters.select_list_item.model.BaseSelectionItem import ru.touchin.roboswag.recyclerview_adapters.adapters.ItemAdapterDelegate @@ -28,29 +26,3 @@ class SheetSelectionDelegate( ) = holder.bind(item) } - -class SelectionItemViewHolder(private val binding: SelectionItemBinding, - private val onItemSelectAction: (ItemType) -> Unit, - private val selectionType: SelectionType - ) : BaseSelectionViewHolder(binding.root) { - - override fun bind(item: ItemType) { - binding.run { - val checkListener = View.OnClickListener { - itemRadiobutton.isChecked = true - onItemSelectAction.invoke(item.copyWithSelection(isSelected = when (selectionType) { - SelectionType.SINGLE_SELECT -> true - else -> !item.isSelected - })) - } - - itemTitle.text = item.title - root.setOnClickListener(checkListener) - - itemRadiobutton.setOnClickListener(checkListener) - itemRadiobutton.isChecked = item.isSelected - } - } - -} - From e14273642ddcf02806912e4642212bd1b445fda5 Mon Sep 17 00:00:00 2001 From: tonlirise Date: Thu, 4 Aug 2022 12:10:02 +0700 Subject: [PATCH 060/133] Added ability to save and view logs --- kotlin-extensions/build.gradle | 7 ++ logging/build.gradle | 16 +++- logging/src/main/AndroidManifest.xml | 16 +++- .../core/log_file/DebugLogsDialogFragment.kt | 84 +++++++++++++++++++ .../roboswag/core/log_file/LogFileManager.kt | 60 +++++++++++++ .../roboswag/core/log_file/LogItemAdapter.kt | 35 ++++++++ .../res/layout/dialog_fragment_debug_logs.xml | 51 +++++++++++ logging/src/main/res/layout/log_item.xml | 28 +++++++ logging/src/main/res/values/styles.xml | 8 ++ logging/src/main/res/xml/provider_paths.xml | 6 ++ 10 files changed, 309 insertions(+), 2 deletions(-) create mode 100644 logging/src/main/java/ru/touchin/roboswag/core/log_file/DebugLogsDialogFragment.kt create mode 100644 logging/src/main/java/ru/touchin/roboswag/core/log_file/LogFileManager.kt create mode 100644 logging/src/main/java/ru/touchin/roboswag/core/log_file/LogItemAdapter.kt create mode 100644 logging/src/main/res/layout/dialog_fragment_debug_logs.xml create mode 100644 logging/src/main/res/layout/log_item.xml create mode 100644 logging/src/main/res/values/styles.xml create mode 100644 logging/src/main/res/xml/provider_paths.xml diff --git a/kotlin-extensions/build.gradle b/kotlin-extensions/build.gradle index afbabac..50960e5 100644 --- a/kotlin-extensions/build.gradle +++ b/kotlin-extensions/build.gradle @@ -2,6 +2,7 @@ apply from: "../android-configs/lib-config.gradle" dependencies { implementation "androidx.recyclerview:recyclerview" + implementation "androidx.coordinatorlayout:coordinatorlayout" implementation "androidx.fragment:fragment-ktx" implementation project(path: ':logging') @@ -12,6 +13,12 @@ dependencies { } } + implementation("androidx.coordinatorlayout:coordinatorlayout") { + version { + require '1.1.0' + } + } + implementation("androidx.fragment:fragment-ktx") { version { require '1.2.1' diff --git a/logging/build.gradle b/logging/build.gradle index 57d4392..aaf00db 100644 --- a/logging/build.gradle +++ b/logging/build.gradle @@ -2,9 +2,11 @@ apply from: "../android-configs/lib-config.gradle" dependencies { implementation "androidx.annotation:annotation" - implementation "com.google.firebase:firebase-crashlytics" + implementation "androidx.recyclerview:recyclerview" + implementation "androidx.constraintlayout:constraintlayout" + constraints { implementation("androidx.annotation:annotation") { version { @@ -17,5 +19,17 @@ dependencies { require '17.1.0' } } + + implementation("androidx.recyclerview:recyclerview") { + version { + require '1.1.0' + } + } + + implementation("androidx.constraintlayout:constraintlayout"){ + version { + require '2.2.0-alpha03' + } + } } } diff --git a/logging/src/main/AndroidManifest.xml b/logging/src/main/AndroidManifest.xml index 8ce26b3..b655062 100644 --- a/logging/src/main/AndroidManifest.xml +++ b/logging/src/main/AndroidManifest.xml @@ -1 +1,15 @@ - + + + + + + + \ No newline at end of file diff --git a/logging/src/main/java/ru/touchin/roboswag/core/log_file/DebugLogsDialogFragment.kt b/logging/src/main/java/ru/touchin/roboswag/core/log_file/DebugLogsDialogFragment.kt new file mode 100644 index 0000000..f34cd81 --- /dev/null +++ b/logging/src/main/java/ru/touchin/roboswag/core/log_file/DebugLogsDialogFragment.kt @@ -0,0 +1,84 @@ +package ru.touchin.roboswag.core.log_file + +import android.content.Intent +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.AdapterView +import android.widget.ArrayAdapter +import androidx.core.content.FileProvider +import androidx.fragment.app.DialogFragment +import androidx.recyclerview.widget.LinearLayoutManager +import ru.touchin.roboswag.core.log.BuildConfig +import ru.touchin.roboswag.core.log.R +import ru.touchin.roboswag.core.log.databinding.DialogFragmentDebugLogsBinding +import ru.touchin.roboswag.core.log_file.LogFileManager.Companion.getLogDirectory +import java.io.File + +class DebugLogsDialogFragment : DialogFragment() { + + private val logItemsList: MutableList = mutableListOf() + private lateinit var binding: DialogFragmentDebugLogsBinding + + override fun getTheme(): Int = R.style.DialogFullscreenTheme + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { + binding = DialogFragmentDebugLogsBinding.inflate(inflater, container, false) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + initSpinner() + + binding.logsRecycler.layoutManager = LinearLayoutManager(requireContext()) + binding.logsRecycler.adapter = LogItemAdapter(requireContext(), logItemsList) + + binding.shareBtn.setOnClickListener { + val files = getLogDirectory(requireContext()).listFiles() + + if (!files.isNullOrEmpty()) { + val uri = FileProvider.getUriForFile( + requireContext(), + BuildConfig.LIBRARY_PACKAGE_NAME + LogFileManager.fileProviderName, + files.first() + ) + + val intent = Intent(Intent.ACTION_SEND) + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + intent.setType("*/*") + intent.putExtra(Intent.EXTRA_STREAM, uri) + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(intent) + } + } + } + + private fun initSpinner() { + val priorityTitle = LogFileManager.Priority.values().map { it.title } + val adapter = ArrayAdapter(requireContext(), android.R.layout.simple_spinner_item, priorityTitle) + adapter.setDropDownViewResource(android.R.layout.simple_dropdown_item_1line) + + binding.priorityFilter.adapter = adapter; + binding.priorityFilter.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { + override fun onItemSelected(parent: AdapterView<*>, view: View, position: Int, id: Long) { + val priority = LogFileManager.Priority.values()[position] + LogFileManager.saveLogcatToFile(requireContext(), priority.tag) + } + + override fun onNothingSelected(parent: AdapterView<*>) {} + } + binding.updateBtn.setOnClickListener { updateRecycler() } + } + + private fun updateRecycler() { + logItemsList.clear() + val files = getLogDirectory(requireContext()).listFiles() + if (!files.isNullOrEmpty()) { + File(files.firstOrNull()?.getAbsolutePath() ?: "") + .useLines { lines -> lines.forEach { logItemsList.add(it) } } + } + binding.logsRecycler.adapter?.notifyDataSetChanged() + } +} diff --git a/logging/src/main/java/ru/touchin/roboswag/core/log_file/LogFileManager.kt b/logging/src/main/java/ru/touchin/roboswag/core/log_file/LogFileManager.kt new file mode 100644 index 0000000..c17477b --- /dev/null +++ b/logging/src/main/java/ru/touchin/roboswag/core/log_file/LogFileManager.kt @@ -0,0 +1,60 @@ +package ru.touchin.roboswag.core.log_file + +import android.content.Context +import java.io.File +import java.io.IOException +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale + +class LogFileManager { + + enum class Priority(val title: String, val tag: String) { + VERBOSE("VERBOSE", "*:V"), + DEBUG("DEBUG", "*:D"), + INFO("INFO", "*:I"), + WARNING("WARNING", "*:W"), + ERROR("ERROR", "*:E"), + ASSERT("ASSERT", "*:A") + } + + companion object { + private const val logDirecroryName = "log" + const val fileProviderName = ".fileprovider" + + fun getLogDirectory(context: Context) : File{ + val appDirectory = context.getExternalFilesDir(null) + return File(appDirectory.toString() + "/$logDirecroryName") + } + + fun saveLogcatToFile(context: Context, priorityTag: String) { + val logDirectory = initLogDirectory(context) + + val sdf = SimpleDateFormat("yyyy-MM-dd'T'HH_mm_ss_SSS", Locale.getDefault()) + val logFile = File(logDirectory, "logcat_${sdf.format(Date())}.txt") + + try { + Runtime.getRuntime().exec("logcat ${priorityTag} -f $logFile") + } catch (e: IOException) { + e.printStackTrace() + } + } + + private fun initLogDirectory(context: Context): File { + val appDirectory = context.getExternalFilesDir(null) + if (!appDirectory!!.exists()) { + appDirectory.mkdir() + } + + val logDirectory = File(appDirectory.toString() + "/$logDirecroryName") + if (!logDirectory.exists()) { + logDirectory.mkdir() + } + + for (file in logDirectory.listFiles()) { + file.delete() + } + return logDirectory + } + } +} diff --git a/logging/src/main/java/ru/touchin/roboswag/core/log_file/LogItemAdapter.kt b/logging/src/main/java/ru/touchin/roboswag/core/log_file/LogItemAdapter.kt new file mode 100644 index 0000000..8b4306e --- /dev/null +++ b/logging/src/main/java/ru/touchin/roboswag/core/log_file/LogItemAdapter.kt @@ -0,0 +1,35 @@ +package ru.touchin.roboswag.core.log_file + +import android.content.Context +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import ru.touchin.roboswag.core.log.databinding.LogItemBinding + +class LogItemAdapter(private val context: Context, private val logItemList:MutableList) + : RecyclerView.Adapter() { + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): LogItemViewHolder { + val binding = LogItemBinding.inflate(LayoutInflater.from(context),parent,false) + return LogItemViewHolder(binding) + } + + override fun onBindViewHolder(holder: LogItemViewHolder, position: Int) { + val foodItem = logItemList[position] + holder.bind(foodItem) + } + + override fun getItemCount(): Int { + return logItemList.size + } + + class LogItemViewHolder(logItemLayoutBinding: LogItemBinding) + : RecyclerView.ViewHolder(logItemLayoutBinding.root){ + + private val binding = logItemLayoutBinding + + fun bind(logItem: String){ + binding.logDescription.text = logItem + } + } +} diff --git a/logging/src/main/res/layout/dialog_fragment_debug_logs.xml b/logging/src/main/res/layout/dialog_fragment_debug_logs.xml new file mode 100644 index 0000000..e90c427 --- /dev/null +++ b/logging/src/main/res/layout/dialog_fragment_debug_logs.xml @@ -0,0 +1,51 @@ + + + + + +