Tidy up ViewEvents

This commit is contained in:
topjohnwu 2020-08-18 06:03:12 -07:00
parent a86d5b3e61
commit d7a26dbf27
17 changed files with 72 additions and 78 deletions

View File

@ -8,6 +8,8 @@ import androidx.navigation.NavDirections
import com.topjohnwu.magisk.MainDirections import com.topjohnwu.magisk.MainDirections
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
import com.topjohnwu.magisk.ui.base.ActivityExecutor
import com.topjohnwu.magisk.ui.base.ViewEvent
class InstallExternalModuleEvent : ViewEvent(), ActivityExecutor { class InstallExternalModuleEvent : ViewEvent(), ActivityExecutor {

View File

@ -7,6 +7,8 @@ import androidx.annotation.AttrRes
import androidx.browser.customtabs.CustomTabsIntent import androidx.browser.customtabs.CustomTabsIntent
import androidx.core.net.toUri import androidx.core.net.toUri
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.ui.base.ContextExecutor
import com.topjohnwu.magisk.ui.base.ViewEvent
data class OpenInappLinkEvent( data class OpenInappLinkEvent(
private val link: String private val link: String
@ -28,4 +30,4 @@ data class OpenInappLinkEvent(
resolveRefs: Boolean = true resolveRefs: Boolean = true
) = TypedValue().also { resolveAttribute(attribute, it, resolveRefs) } ) = TypedValue().also { resolveAttribute(attribute, it, resolveRefs) }
} }

View File

@ -5,7 +5,9 @@ import androidx.annotation.StringRes
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
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.snackbar
import com.topjohnwu.magisk.ui.base.ActivityExecutor
import com.topjohnwu.magisk.ui.base.BaseUIActivity import com.topjohnwu.magisk.ui.base.BaseUIActivity
import com.topjohnwu.magisk.ui.base.ViewEvent
class SnackbarEvent private constructor( class SnackbarEvent private constructor(
@StringRes private val messageRes: Int, @StringRes private val messageRes: Int,

View File

@ -1,17 +0,0 @@
package com.topjohnwu.magisk.model.events
import androidx.lifecycle.Observer
/**
* Observer for [ViewEvent]s, which automatically checks if event was handled
*/
class ViewEventObserver(private val onEventUnhandled: (ViewEvent) -> Unit) : Observer<ViewEvent> {
override fun onChanged(event: ViewEvent?) {
event?.let {
if (!it.handled) {
it.handled = true
onEventUnhandled(it)
}
}
}
}

View File

@ -3,7 +3,6 @@ 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
@ -12,12 +11,19 @@ import com.topjohnwu.magisk.core.utils.SafetyNetHelper
import com.topjohnwu.magisk.data.network.GithubRawServices import com.topjohnwu.magisk.data.network.GithubRawServices
import com.topjohnwu.magisk.ktx.DynamicClassLoader import com.topjohnwu.magisk.ktx.DynamicClassLoader
import com.topjohnwu.magisk.ktx.writeTo import com.topjohnwu.magisk.ktx.writeTo
import com.topjohnwu.magisk.ui.base.ActivityExecutor
import com.topjohnwu.magisk.ui.base.ContextExecutor
import com.topjohnwu.magisk.ui.base.ViewEvent
import com.topjohnwu.magisk.ui.base.ViewEventWithScope
import com.topjohnwu.magisk.ui.safetynet.SafetyNetResult import com.topjohnwu.magisk.ui.safetynet.SafetyNetResult
import com.topjohnwu.magisk.view.MagiskDialog import com.topjohnwu.magisk.view.MagiskDialog
import com.topjohnwu.magisk.view.MarkDownWindow import com.topjohnwu.magisk.view.MarkDownWindow
import com.topjohnwu.superuser.Shell import com.topjohnwu.superuser.Shell
import dalvik.system.DexFile import dalvik.system.DexFile
import kotlinx.coroutines.* import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.json.JSONObject import org.json.JSONObject
import org.koin.core.KoinComponent import org.koin.core.KoinComponent
import org.koin.core.inject import org.koin.core.inject
@ -26,35 +32,9 @@ import java.io.File
import java.io.IOException import java.io.IOException
import java.lang.reflect.InvocationHandler import java.lang.reflect.InvocationHandler
/**
* Class for passing events from ViewModels to Activities/Fragments
* Variable [handled] used so each event is handled only once
* (see https://medium.com/google-developers/livedata-with-snackbar-navigation-and-other-events-the-singleliveevent-case-ac2622673150)
* Use [ViewEventObserver] for observing these events
*/
abstract class ViewEvent {
var handled = false
}
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( class CheckSafetyNetEvent(
private val callback: (SafetyNetResult) -> Unit = {} private val callback: (SafetyNetResult) -> Unit = {}
) : ViewEventsWithScope(), ContextExecutor, KoinComponent, SafetyNetHelper.Callback { ) : ViewEventWithScope(), ContextExecutor, KoinComponent, SafetyNetHelper.Callback {
private val svc by inject<GithubRawServices>() private val svc by inject<GithubRawServices>()
@ -161,7 +141,7 @@ class ViewActionEvent(val action: BaseActivity.() -> Unit) : ViewEvent(), Activi
override fun invoke(activity: BaseActivity) = action(activity) override fun invoke(activity: BaseActivity) = action(activity)
} }
class OpenChangelogEvent(val item: Repo) : ViewEventsWithScope(), ContextExecutor { class OpenChangelogEvent(val item: Repo) : ViewEventWithScope(), ContextExecutor {
override fun invoke(context: Context) { override fun invoke(context: Context) {
scope.launch { scope.launch {
MarkDownWindow.show(context, null, item::readme) MarkDownWindow.show(context, null, item::readme)

View File

@ -2,8 +2,8 @@ package com.topjohnwu.magisk.model.events.dialog
import com.topjohnwu.magisk.core.base.BaseActivity import com.topjohnwu.magisk.core.base.BaseActivity
import com.topjohnwu.magisk.core.utils.BiometricHelper import com.topjohnwu.magisk.core.utils.BiometricHelper
import com.topjohnwu.magisk.model.events.ActivityExecutor import com.topjohnwu.magisk.ui.base.ActivityExecutor
import com.topjohnwu.magisk.model.events.ViewEvent import com.topjohnwu.magisk.ui.base.ViewEvent
class BiometricDialog( class BiometricDialog(
builder: Builder.() -> Unit builder: Builder.() -> Unit

View File

@ -5,7 +5,7 @@ import androidx.appcompat.app.AppCompatDelegate
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
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.model.events.ActivityExecutor import com.topjohnwu.magisk.ui.base.ActivityExecutor
import com.topjohnwu.magisk.view.MagiskDialog import com.topjohnwu.magisk.view.MagiskDialog
import java.lang.ref.WeakReference import java.lang.ref.WeakReference

View File

@ -1,8 +1,8 @@
package com.topjohnwu.magisk.model.events.dialog package com.topjohnwu.magisk.model.events.dialog
import android.content.Context import android.content.Context
import com.topjohnwu.magisk.model.events.ContextExecutor import com.topjohnwu.magisk.ui.base.ContextExecutor
import com.topjohnwu.magisk.model.events.ViewEvent import com.topjohnwu.magisk.ui.base.ViewEvent
import com.topjohnwu.magisk.view.MagiskDialog import com.topjohnwu.magisk.view.MagiskDialog
abstract class DialogEvent : ViewEvent(), ContextExecutor { abstract class DialogEvent : ViewEvent(), ContextExecutor {
@ -17,4 +17,4 @@ abstract class DialogEvent : ViewEvent(), ContextExecutor {
} }
typealias GenericDialogListener = () -> Unit typealias GenericDialogListener = () -> Unit

View File

@ -2,9 +2,9 @@ package com.topjohnwu.magisk.model.navigation
import androidx.navigation.NavDirections import androidx.navigation.NavDirections
import com.topjohnwu.magisk.core.base.BaseActivity import com.topjohnwu.magisk.core.base.BaseActivity
import com.topjohnwu.magisk.model.events.ActivityExecutor import com.topjohnwu.magisk.ui.base.ActivityExecutor
import com.topjohnwu.magisk.model.events.ViewEvent
import com.topjohnwu.magisk.ui.base.BaseUIActivity import com.topjohnwu.magisk.ui.base.BaseUIActivity
import com.topjohnwu.magisk.ui.base.ViewEvent
class NavigationWrapper( class NavigationWrapper(
private val directions: NavDirections private val directions: NavDirections
@ -15,4 +15,4 @@ class NavigationWrapper(
directions.navigate() directions.navigate()
} }
} }
} }

View File

@ -17,9 +17,6 @@ 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.startAnimations import com.topjohnwu.magisk.ktx.startAnimations
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 import com.topjohnwu.magisk.ui.theme.Theme
abstract class BaseUIActivity<VM : BaseViewModel, Binding : ViewDataBinding> : abstract class BaseUIActivity<VM : BaseViewModel, Binding : ViewDataBinding> :
@ -94,9 +91,10 @@ abstract class BaseUIActivity<VM : BaseViewModel, Binding : ViewDataBinding> :
return currentFragment?.onKeyEvent(event) == true || super.dispatchKeyEvent(event) return currentFragment?.onKeyEvent(event) == true || super.dispatchKeyEvent(event)
} }
override fun onEventDispatched(event: ViewEvent) { override fun onEventDispatched(event: ViewEvent) = when(event) {
(event as? ContextExecutor)?.invoke(this) is ContextExecutor -> event(this)
(event as? ActivityExecutor)?.invoke(this) is ActivityExecutor -> event(this)
else -> Unit
} }
override fun onBackPressed() { override fun onBackPressed() {

View File

@ -5,7 +5,6 @@ import androidx.core.graphics.Insets
import androidx.core.view.ViewCompat import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsCompat
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import com.topjohnwu.magisk.model.events.ViewEvent
interface BaseUIComponent<VM : BaseViewModel>: LifecycleOwner { interface BaseUIComponent<VM : BaseViewModel>: LifecycleOwner {

View File

@ -13,10 +13,6 @@ 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.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<VM : BaseViewModel, Binding : ViewDataBinding> : abstract class BaseUIFragment<VM : BaseViewModel, Binding : ViewDataBinding> :
Fragment(), BaseUIComponent<VM> { Fragment(), BaseUIComponent<VM> {
@ -47,10 +43,11 @@ abstract class BaseUIFragment<VM : BaseViewModel, Binding : ViewDataBinding> :
return binding.root return binding.root
} }
override fun onEventDispatched(event: ViewEvent) { override fun onEventDispatched(event: ViewEvent) = when(event) {
(event as? ContextExecutor)?.invoke(requireContext()) is ContextExecutor -> event(requireContext())
(event as? FragmentExecutor)?.invoke(this) is ActivityExecutor -> event(activity)
(event as? ActivityExecutor)?.invoke(activity) is FragmentExecutor -> event(this)
else -> Unit
} }
open fun onKeyEvent(event: KeyEvent): Boolean { open fun onKeyEvent(event: KeyEvent): Boolean {

View File

@ -15,7 +15,10 @@ import com.topjohnwu.magisk.BR
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.Info import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.core.base.BaseActivity import com.topjohnwu.magisk.core.base.BaseActivity
import com.topjohnwu.magisk.model.events.* import com.topjohnwu.magisk.model.events.BackPressEvent
import com.topjohnwu.magisk.model.events.PermissionEvent
import com.topjohnwu.magisk.model.events.SnackbarEvent
import com.topjohnwu.magisk.model.events.ViewActionEvent
import com.topjohnwu.magisk.model.navigation.NavigationWrapper import com.topjohnwu.magisk.model.navigation.NavigationWrapper
import com.topjohnwu.magisk.utils.ObservableHost import com.topjohnwu.magisk.utils.ObservableHost
import com.topjohnwu.magisk.utils.set import com.topjohnwu.magisk.utils.set
@ -102,7 +105,7 @@ abstract class BaseViewModel(
_viewEvents.postValue(this) _viewEvents.postValue(this)
} }
fun <Event : ViewEventsWithScope> Event.publish() { fun <Event : ViewEventWithScope> Event.publish() {
scope = viewModelScope scope = viewModelScope
_viewEvents.postValue(this) _viewEvents.postValue(this)
} }

View File

@ -0,0 +1,28 @@
package com.topjohnwu.magisk.ui.base
import android.content.Context
import androidx.fragment.app.Fragment
import com.topjohnwu.magisk.core.base.BaseActivity
import kotlinx.coroutines.CoroutineScope
/**
* Class for passing events from ViewModels to Activities/Fragments
* (see https://medium.com/google-developers/livedata-with-snackbar-navigation-and-other-events-the-singleliveevent-case-ac2622673150)
*/
abstract class ViewEvent
abstract class ViewEventWithScope: 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)
}

View File

@ -17,13 +17,13 @@ import com.topjohnwu.magisk.ktx.packageName
import com.topjohnwu.magisk.ktx.res import com.topjohnwu.magisk.ktx.res
import com.topjohnwu.magisk.model.entity.IconLink import com.topjohnwu.magisk.model.entity.IconLink
import com.topjohnwu.magisk.model.entity.internal.DownloadSubject.Manager import com.topjohnwu.magisk.model.entity.internal.DownloadSubject.Manager
import com.topjohnwu.magisk.model.events.ActivityExecutor
import com.topjohnwu.magisk.model.events.OpenInappLinkEvent import com.topjohnwu.magisk.model.events.OpenInappLinkEvent
import com.topjohnwu.magisk.model.events.ViewEvent
import com.topjohnwu.magisk.model.events.dialog.EnvFixDialog import com.topjohnwu.magisk.model.events.dialog.EnvFixDialog
import com.topjohnwu.magisk.model.events.dialog.ManagerInstallDialog import com.topjohnwu.magisk.model.events.dialog.ManagerInstallDialog
import com.topjohnwu.magisk.model.events.dialog.UninstallDialog import com.topjohnwu.magisk.model.events.dialog.UninstallDialog
import com.topjohnwu.magisk.ui.base.ActivityExecutor
import com.topjohnwu.magisk.ui.base.BaseViewModel import com.topjohnwu.magisk.ui.base.BaseViewModel
import com.topjohnwu.magisk.ui.base.ViewEvent
import com.topjohnwu.magisk.ui.base.itemBindingOf import com.topjohnwu.magisk.ui.base.itemBindingOf
import com.topjohnwu.magisk.utils.set import com.topjohnwu.magisk.utils.set
import com.topjohnwu.superuser.Shell import com.topjohnwu.superuser.Shell

View File

@ -12,10 +12,10 @@ import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.databinding.FragmentModuleMd2Binding import com.topjohnwu.magisk.databinding.FragmentModuleMd2Binding
import com.topjohnwu.magisk.ktx.hideKeyboard import com.topjohnwu.magisk.ktx.hideKeyboard
import com.topjohnwu.magisk.model.events.InstallExternalModuleEvent import com.topjohnwu.magisk.model.events.InstallExternalModuleEvent
import com.topjohnwu.magisk.model.events.ViewEvent
import com.topjohnwu.magisk.ui.MainActivity import com.topjohnwu.magisk.ui.MainActivity
import com.topjohnwu.magisk.ui.base.BaseUIFragment import com.topjohnwu.magisk.ui.base.BaseUIFragment
import com.topjohnwu.magisk.ui.base.ReselectionTarget import com.topjohnwu.magisk.ui.base.ReselectionTarget
import com.topjohnwu.magisk.ui.base.ViewEvent
import com.topjohnwu.magisk.utils.EndlessRecyclerScrollListener import com.topjohnwu.magisk.utils.EndlessRecyclerScrollListener
import com.topjohnwu.magisk.utils.MotionRevealHelper import com.topjohnwu.magisk.utils.MotionRevealHelper
import org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.androidx.viewmodel.ext.android.viewModel

View File

@ -4,7 +4,7 @@ import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.StaggeredGridLayoutManager import androidx.recyclerview.widget.StaggeredGridLayoutManager
import com.topjohnwu.magisk.model.events.ViewEvent import com.topjohnwu.magisk.ui.base.ViewEvent
class EndlessRecyclerScrollListener( class EndlessRecyclerScrollListener(
private val layoutManager: RecyclerView.LayoutManager, private val layoutManager: RecyclerView.LayoutManager,