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.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)
resultCallbacks[requestCode]?.also {
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.content.Context
import android.content.Intent
import androidx.fragment.app.Fragment
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.core.base.BaseActivity
@ -32,7 +33,6 @@ import java.lang.reflect.InvocationHandler
* Use [ViewEventObserver] for observing these events
*/
abstract class ViewEvent {
var handled = false
}
@ -40,6 +40,18 @@ abstract class ViewEventsWithScope: ViewEvent() {
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(
private val callback: (SafetyNetResult) -> Unit = {}
) : ViewEventsWithScope(), ContextExecutor, KoinComponent, SafetyNetHelper.Callback {
@ -146,7 +158,7 @@ class CheckSafetyNetEvent(
}
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 {

View File

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

View File

@ -107,10 +107,6 @@ abstract class BaseViewModel(
_viewEvents.postValue(this)
}
fun Int.publish() {
_viewEvents.postValue(SimpleViewEvent(this))
}
fun NavDirections.publish() {
_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.REQUEST
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 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 viewModel: SuRequestViewModel by viewModel()
override val navigation: NavController? = null
override fun onBackPressed() {
@ -65,14 +61,6 @@ open class SuRequestActivity : BaseUIActivity<SuRequestViewModel, ActivityReques
return theme
}
override fun onEventDispatched(event: ViewEvent) {
super.onEventDispatched(event)
when (event) {
is ViewActionEvent -> event.action(this)
is DieEvent -> finish()
}
}
private fun lockOrientation() {
requestedOrientation = if (Build.VERSION.SDK_INT < 18)
resources.configuration.orientation