Remove unnecessary abstractions

This commit is contained in:
topjohnwu 2020-08-18 05:03:56 -07:00
parent b2bece9ef6
commit a86d5b3e61
13 changed files with 117 additions and 220 deletions

View File

@ -66,7 +66,7 @@ abstract class BaseActivity : AppCompatActivity() {
} }
resultCallbacks[requestCode]?.also { resultCallbacks[requestCode]?.also {
resultCallbacks.remove(requestCode) resultCallbacks.remove(requestCode)
it(this@BaseActivity, if (success) 1 else -1, null) it(this, if (success) 1 else -1, null)
} }
} }
@ -75,7 +75,7 @@ abstract class BaseActivity : AppCompatActivity() {
super.onActivityResult(requestCode, resultCode, data) super.onActivityResult(requestCode, resultCode, data)
resultCallbacks[requestCode]?.also { resultCallbacks[requestCode]?.also {
resultCallbacks.remove(requestCode) resultCallbacks.remove(requestCode)
it(this@BaseActivity, resultCode, data) it(this, resultCode, data)
} }
} }

View File

@ -1,23 +0,0 @@
package com.topjohnwu.magisk.model.events
import android.content.Context
import androidx.fragment.app.Fragment
import com.topjohnwu.magisk.core.base.BaseActivity
interface ContextExecutor {
operator fun invoke(context: Context)
}
interface ActivityExecutor {
operator fun invoke(activity: BaseActivity)
}
interface FragmentExecutor {
operator fun invoke(fragment: Fragment)
}

View File

@ -1,25 +0,0 @@
package com.topjohnwu.magisk.model.events
internal interface EventHandler {
/**
* Called for all [ViewEvent]s published by associated viewModel.
* For [SimpleViewEvent]s, both this and [onSimpleEventDispatched]
* methods are called - you can choose the way how you handle them.
*/
fun onEventDispatched(event: ViewEvent) {}
/**
* Called for all [SimpleViewEvent]s published by associated viewModel.
* Both this and [onEventDispatched] methods are called - you can choose
* the way how you handle them.
*/
fun onSimpleEventDispatched(event: Int) {}
val viewEventObserver get() = ViewEventObserver {
onEventDispatched(it)
if (it is SimpleViewEvent) {
onSimpleEventDispatched(it.event)
}
}
}

View File

@ -1,5 +0,0 @@
package com.topjohnwu.magisk.model.events
class SimpleViewEvent(
val event: Int
) : ViewEvent()

View File

@ -3,6 +3,7 @@ package com.topjohnwu.magisk.model.events
import android.app.Activity import android.app.Activity
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import androidx.fragment.app.Fragment
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.Const import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.core.base.BaseActivity import com.topjohnwu.magisk.core.base.BaseActivity
@ -32,7 +33,6 @@ import java.lang.reflect.InvocationHandler
* Use [ViewEventObserver] for observing these events * Use [ViewEventObserver] for observing these events
*/ */
abstract class ViewEvent { abstract class ViewEvent {
var handled = false var handled = false
} }
@ -40,6 +40,18 @@ abstract class ViewEventsWithScope: ViewEvent() {
lateinit var scope: CoroutineScope lateinit var scope: CoroutineScope
} }
interface ContextExecutor {
operator fun invoke(context: Context)
}
interface ActivityExecutor {
operator fun invoke(activity: BaseActivity)
}
interface FragmentExecutor {
operator fun invoke(fragment: Fragment)
}
class CheckSafetyNetEvent( class CheckSafetyNetEvent(
private val callback: (SafetyNetResult) -> Unit = {} private val callback: (SafetyNetResult) -> Unit = {}
) : ViewEventsWithScope(), ContextExecutor, KoinComponent, SafetyNetHelper.Callback { ) : ViewEventsWithScope(), ContextExecutor, KoinComponent, SafetyNetHelper.Callback {
@ -146,7 +158,7 @@ class CheckSafetyNetEvent(
} }
class ViewActionEvent(val action: BaseActivity.() -> Unit) : ViewEvent(), ActivityExecutor { class ViewActionEvent(val action: BaseActivity.() -> Unit) : ViewEvent(), ActivityExecutor {
override fun invoke(activity: BaseActivity) = activity.run(action) override fun invoke(activity: BaseActivity) = action(activity)
} }
class OpenChangelogEvent(val item: Repo) : ViewEventsWithScope(), ContextExecutor { class OpenChangelogEvent(val item: Repo) : ViewEventsWithScope(), ContextExecutor {

View File

@ -7,26 +7,23 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.appcompat.app.AppCompatDelegate import androidx.appcompat.app.AppCompatDelegate
import androidx.core.content.res.use import androidx.core.content.res.use
import androidx.core.graphics.Insets
import androidx.databinding.DataBindingUtil import androidx.databinding.DataBindingUtil
import androidx.databinding.OnRebindCallback import androidx.databinding.OnRebindCallback
import androidx.databinding.ViewDataBinding import androidx.databinding.ViewDataBinding
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Observer
import androidx.navigation.NavDirections import androidx.navigation.NavDirections
import androidx.navigation.findNavController import androidx.navigation.findNavController
import com.topjohnwu.magisk.BR import com.topjohnwu.magisk.BR
import com.topjohnwu.magisk.core.Config import com.topjohnwu.magisk.core.Config
import com.topjohnwu.magisk.core.base.BaseActivity import com.topjohnwu.magisk.core.base.BaseActivity
import com.topjohnwu.magisk.ktx.snackbar
import com.topjohnwu.magisk.ktx.startAnimations import com.topjohnwu.magisk.ktx.startAnimations
import com.topjohnwu.magisk.model.events.EventHandler import com.topjohnwu.magisk.model.events.ActivityExecutor
import com.topjohnwu.magisk.model.events.SnackbarEvent import com.topjohnwu.magisk.model.events.ContextExecutor
import com.topjohnwu.magisk.model.events.ViewEvent import com.topjohnwu.magisk.model.events.ViewEvent
import com.topjohnwu.magisk.ui.theme.Theme import com.topjohnwu.magisk.ui.theme.Theme
abstract class BaseUIActivity<ViewModel : BaseViewModel, Binding : ViewDataBinding> : abstract class BaseUIActivity<VM : BaseViewModel, Binding : ViewDataBinding> :
BaseActivity(), CompatView<ViewModel>, EventHandler { BaseActivity(), BaseUIComponent<VM> {
protected lateinit var binding: Binding protected lateinit var binding: Binding
protected abstract val layoutRes: Int protected abstract val layoutRes: Int
@ -37,12 +34,10 @@ abstract class BaseUIActivity<ViewModel : BaseViewModel, Binding : ViewDataBindi
protected val currentFragment get() = topFragment as? BaseUIFragment<*, *> protected val currentFragment get() = topFragment as? BaseUIFragment<*, *>
override val viewRoot: View get() = binding.root override val viewRoot: View get() = binding.root
override val navigation by lazy { open val navigation by lazy {
kotlin.runCatching { findNavController(navHost) }.getOrNull() runCatching { findNavController(navHost) }.getOrNull()
} }
private val delegate by lazy { CompatDelegate(this) }
open val navHost: Int = 0 open val navHost: Int = 0
open val snackbarView get() = binding.root open val snackbarView get() = binding.root
@ -66,35 +61,33 @@ abstract class BaseUIActivity<ViewModel : BaseViewModel, Binding : ViewDataBindi
.also { window.setBackgroundDrawable(it) } .also { window.setBackgroundDrawable(it) }
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
startObserveEvents()
viewModel.viewEvents.observe(this, viewEventObserver) binding = DataBindingUtil.setContentView<Binding>(this, layoutRes).also {
it.setVariable(BR.viewModel, viewModel)
binding = DataBindingUtil.setContentView<Binding>(this, layoutRes).apply { it.lifecycleOwner = this
setVariable(BR.viewModel, viewModel) it.addOnRebindCallback(object : OnRebindCallback<Binding>() {
lifecycleOwner = this@BaseUIActivity override fun onPreBind(binding: Binding): Boolean {
(binding.root as? ViewGroup)?.startAnimations()
return super.onPreBind(binding)
}
})
} }
binding.addOnRebindCallback(object : OnRebindCallback<Binding>() { ensureInsets()
override fun onPreBind(binding: Binding): Boolean {
(binding.root as? ViewGroup)?.startAnimations()
return super.onPreBind(binding)
}
})
delegate.onCreate() directionsDispatcher.observe(this) {
directionsDispatcher.observe(this, Observer {
it?.navigate() it?.navigate()
// we don't want the directions to be re-dispatched, so we preemptively set them to null // we don't want the directions to be re-dispatched, so we preemptively set them to null
if (it != null) { if (it != null) {
directionsDispatcher.value = null directionsDispatcher.value = null
} }
}) }
} }
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
delegate.onResume() viewModel.requestRefresh()
} }
override fun dispatchKeyEvent(event: KeyEvent): Boolean { override fun dispatchKeyEvent(event: KeyEvent): Boolean {
@ -102,10 +95,8 @@ abstract class BaseUIActivity<ViewModel : BaseViewModel, Binding : ViewDataBindi
} }
override fun onEventDispatched(event: ViewEvent) { override fun onEventDispatched(event: ViewEvent) {
delegate.onEventExecute(event, this) (event as? ContextExecutor)?.invoke(this)
when (event) { (event as? ActivityExecutor)?.invoke(this)
is SnackbarEvent -> snackbar(snackbarView, event.message(this), event.length, event.f)
}
} }
override fun onBackPressed() { override fun onBackPressed() {
@ -114,12 +105,6 @@ abstract class BaseUIActivity<ViewModel : BaseViewModel, Binding : ViewDataBindi
} }
} }
override fun peekSystemWindowInsets(insets: Insets) {
viewModel.insets = insets
}
protected fun ViewEvent.dispatchOnSelf() = onEventDispatched(this)
fun NavDirections.navigate() { fun NavDirections.navigate() {
navigation?.navigate(this) navigation?.navigate(this)
} }

View File

@ -0,0 +1,64 @@
package com.topjohnwu.magisk.ui.base
import android.view.View
import androidx.core.graphics.Insets
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.lifecycle.LifecycleOwner
import com.topjohnwu.magisk.model.events.ViewEvent
interface BaseUIComponent<VM : BaseViewModel>: LifecycleOwner {
val viewRoot: View
val viewModel: VM
fun startObserveEvents() {
viewModel.viewEvents.observe(this) {
onEventDispatched(it)
}
}
fun consumeSystemWindowInsets(insets: Insets): Insets? = null
/**
* Called for all [ViewEvent]s published by associated viewModel.
*/
fun onEventDispatched(event: ViewEvent) {}
fun ensureInsets() {
ViewCompat.setOnApplyWindowInsetsListener(viewRoot) { _, insets ->
insets.asInsets()
.also { viewModel.insets = it }
.let { consumeSystemWindowInsets(it) }
?.subtractBy(insets) ?: insets
}
if (ViewCompat.isAttachedToWindow(viewRoot)) {
ViewCompat.requestApplyInsets(viewRoot)
} else {
viewRoot.addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener {
override fun onViewDetachedFromWindow(v: View) = Unit
override fun onViewAttachedToWindow(v: View) {
ViewCompat.requestApplyInsets(v)
}
})
}
}
private fun WindowInsetsCompat.asInsets() = Insets.of(
systemWindowInsetLeft,
systemWindowInsetTop,
systemWindowInsetRight,
systemWindowInsetBottom
)
private fun Insets.subtractBy(insets: WindowInsetsCompat) =
WindowInsetsCompat.Builder(insets).setSystemWindowInsets(
Insets.of(
insets.systemWindowInsetLeft - left,
insets.systemWindowInsetTop - top,
insets.systemWindowInsetRight - right,
insets.systemWindowInsetBottom - bottom
)
).build()
}

View File

@ -13,25 +13,26 @@ import androidx.fragment.app.Fragment
import androidx.navigation.NavDirections import androidx.navigation.NavDirections
import com.topjohnwu.magisk.BR import com.topjohnwu.magisk.BR
import com.topjohnwu.magisk.ktx.startAnimations import com.topjohnwu.magisk.ktx.startAnimations
import com.topjohnwu.magisk.model.events.EventHandler import com.topjohnwu.magisk.model.events.ActivityExecutor
import com.topjohnwu.magisk.model.events.ContextExecutor
import com.topjohnwu.magisk.model.events.FragmentExecutor
import com.topjohnwu.magisk.model.events.ViewEvent import com.topjohnwu.magisk.model.events.ViewEvent
abstract class BaseUIFragment<ViewModel : BaseViewModel, Binding : ViewDataBinding> : abstract class BaseUIFragment<VM : BaseViewModel, Binding : ViewDataBinding> :
Fragment(), CompatView<ViewModel>, EventHandler { Fragment(), BaseUIComponent<VM> {
protected val activity get() = requireActivity() as BaseUIActivity<*, *> protected val activity get() = requireActivity() as BaseUIActivity<*, *>
protected lateinit var binding: Binding protected lateinit var binding: Binding
protected abstract val layoutRes: Int protected abstract val layoutRes: Int
override val viewRoot: View get() = binding.root override val viewRoot: View get() = binding.root
override val navigation get() = activity.navigation private val navigation get() = activity.navigation
private val delegate by lazy { CompatDelegate(this) }
override fun consumeSystemWindowInsets(insets: Insets) = insets override fun consumeSystemWindowInsets(insets: Insets) = insets
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
viewModel.viewEvents.observe(this, viewEventObserver) startObserveEvents()
} }
override fun onCreateView( override fun onCreateView(
@ -39,17 +40,17 @@ abstract class BaseUIFragment<ViewModel : BaseViewModel, Binding : ViewDataBindi
container: ViewGroup?, container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View? { ): View? {
binding = DataBindingUtil.inflate<Binding>(inflater, layoutRes, container, false).apply { binding = DataBindingUtil.inflate<Binding>(inflater, layoutRes, container, false).also {
setVariable(BR.viewModel, viewModel) it.setVariable(BR.viewModel, viewModel)
lifecycleOwner = this@BaseUIFragment it.lifecycleOwner = this
} }
return binding.root return binding.root
} }
override fun onEventDispatched(event: ViewEvent) { override fun onEventDispatched(event: ViewEvent) {
super.onEventDispatched(event) (event as? ContextExecutor)?.invoke(requireContext())
delegate.onEventExecute(event, this) (event as? FragmentExecutor)?.invoke(this)
(event as? ActivityExecutor)?.invoke(activity)
} }
open fun onKeyEvent(event: KeyEvent): Boolean { open fun onKeyEvent(event: KeyEvent): Boolean {
@ -61,29 +62,24 @@ abstract class BaseUIFragment<ViewModel : BaseViewModel, Binding : ViewDataBindi
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
binding.addOnRebindCallback(object : OnRebindCallback<Binding>() { binding.addOnRebindCallback(object : OnRebindCallback<Binding>() {
override fun onPreBind(binding: Binding): Boolean { override fun onPreBind(binding: Binding): Boolean {
this@BaseUIFragment.onPreBind(binding) this@BaseUIFragment.onPreBind(binding)
return true return true
} }
}) })
ensureInsets()
delegate.onCreate()
} }
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
viewModel.requestRefresh()
delegate.onResume()
} }
protected open fun onPreBind(binding: Binding) { protected open fun onPreBind(binding: Binding) {
(binding.root as? ViewGroup)?.startAnimations() (binding.root as? ViewGroup)?.startAnimations()
} }
protected fun ViewEvent.dispatchOnSelf() = delegate.onEventExecute(this, this@BaseUIFragment)
fun NavDirections.navigate() { fun NavDirections.navigate() {
navigation?.navigate(this) navigation?.navigate(this)
} }

View File

@ -107,10 +107,6 @@ abstract class BaseViewModel(
_viewEvents.postValue(this) _viewEvents.postValue(this)
} }
fun Int.publish() {
_viewEvents.postValue(SimpleViewEvent(this))
}
fun NavDirections.publish() { fun NavDirections.publish() {
_viewEvents.postValue(NavigationWrapper(this)) _viewEvents.postValue(NavigationWrapper(this))
} }

View File

@ -1,75 +0,0 @@
package com.topjohnwu.magisk.ui.base
import android.view.View
import androidx.core.graphics.Insets
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.fragment.app.Fragment
import com.topjohnwu.magisk.model.events.ActivityExecutor
import com.topjohnwu.magisk.model.events.ContextExecutor
import com.topjohnwu.magisk.model.events.FragmentExecutor
import com.topjohnwu.magisk.model.events.ViewEvent
import timber.log.Timber
class CompatDelegate internal constructor(
private val view: CompatView<*>
) {
fun onCreate() {
ensureInsets()
}
fun onResume() {
view.viewModel.requestRefresh()
}
fun onEventExecute(event: ViewEvent, activity: BaseUIActivity<*, *>) {
(event as? ContextExecutor)?.invoke(activity)
(event as? ActivityExecutor)?.invoke(activity)
(event as? FragmentExecutor)?.let {
Timber.e("Cannot run ${FragmentExecutor::class.java.simpleName} in Activity. Consider adding ${ContextExecutor::class.java.simpleName} as fallback.")
}
}
fun onEventExecute(event: ViewEvent, fragment: Fragment) {
(event as? ContextExecutor)?.invoke(fragment.requireContext())
(event as? FragmentExecutor)?.invoke(fragment)
(event as? ActivityExecutor)?.invoke(fragment.requireActivity() as BaseUIActivity<*, *>)
}
private fun ensureInsets() {
ViewCompat.setOnApplyWindowInsetsListener(view.viewRoot) { _, insets ->
insets.asInsets()
.also { view.peekSystemWindowInsets(it) }
.let { view.consumeSystemWindowInsets(it) }
?.also { view.viewModel.insets = it }
?.subtractBy(insets) ?: insets
}
if (ViewCompat.isAttachedToWindow(view.viewRoot)) {
ViewCompat.requestApplyInsets(view.viewRoot)
} else {
view.viewRoot.addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener {
override fun onViewDetachedFromWindow(v: View) = Unit
override fun onViewAttachedToWindow(v: View) {
ViewCompat.requestApplyInsets(v)
}
})
}
}
private fun WindowInsetsCompat.asInsets() = Insets.of(
systemWindowInsetLeft,
systemWindowInsetTop,
systemWindowInsetRight,
systemWindowInsetBottom
)
private fun Insets.subtractBy(insets: WindowInsetsCompat) = insets.replaceSystemWindowInsets(
insets.systemWindowInsetLeft - left,
insets.systemWindowInsetTop - top,
insets.systemWindowInsetRight - right,
insets.systemWindowInsetBottom - bottom
)
}

View File

@ -1,16 +0,0 @@
package com.topjohnwu.magisk.ui.base
import android.view.View
import androidx.core.graphics.Insets
import androidx.navigation.NavController
internal interface CompatView<ViewModel : BaseViewModel> {
val viewRoot: View
val viewModel: ViewModel
val navigation: NavController?
fun peekSystemWindowInsets(insets: Insets) = Unit
fun consumeSystemWindowInsets(insets: Insets): Insets? = null
}

View File

@ -12,9 +12,6 @@ import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.su.SuCallbackHandler import com.topjohnwu.magisk.core.su.SuCallbackHandler
import com.topjohnwu.magisk.core.su.SuCallbackHandler.REQUEST import com.topjohnwu.magisk.core.su.SuCallbackHandler.REQUEST
import com.topjohnwu.magisk.databinding.ActivityRequestBinding import com.topjohnwu.magisk.databinding.ActivityRequestBinding
import com.topjohnwu.magisk.model.events.DieEvent
import com.topjohnwu.magisk.model.events.ViewActionEvent
import com.topjohnwu.magisk.model.events.ViewEvent
import com.topjohnwu.magisk.ui.base.BaseUIActivity import com.topjohnwu.magisk.ui.base.BaseUIActivity
import org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.androidx.viewmodel.ext.android.viewModel
@ -22,7 +19,6 @@ open class SuRequestActivity : BaseUIActivity<SuRequestViewModel, ActivityReques
override val layoutRes: Int = R.layout.activity_request override val layoutRes: Int = R.layout.activity_request
override val viewModel: SuRequestViewModel by viewModel() override val viewModel: SuRequestViewModel by viewModel()
override val navigation: NavController? = null override val navigation: NavController? = null
override fun onBackPressed() { override fun onBackPressed() {
@ -65,14 +61,6 @@ open class SuRequestActivity : BaseUIActivity<SuRequestViewModel, ActivityReques
return theme return theme
} }
override fun onEventDispatched(event: ViewEvent) {
super.onEventDispatched(event)
when (event) {
is ViewActionEvent -> event.action(this)
is DieEvent -> finish()
}
}
private fun lockOrientation() { private fun lockOrientation() {
requestedOrientation = if (Build.VERSION.SDK_INT < 18) requestedOrientation = if (Build.VERSION.SDK_INT < 18)
resources.configuration.orientation resources.configuration.orientation