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.core.Const
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 {

View File

@ -7,6 +7,8 @@ import androidx.annotation.AttrRes
import androidx.browser.customtabs.CustomTabsIntent
import androidx.core.net.toUri
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.ui.base.ContextExecutor
import com.topjohnwu.magisk.ui.base.ViewEvent
data class OpenInappLinkEvent(
private val link: String
@ -28,4 +30,4 @@ data class OpenInappLinkEvent(
resolveRefs: Boolean = true
) = 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.topjohnwu.magisk.core.base.BaseActivity
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.ViewEvent
class SnackbarEvent private constructor(
@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.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
@ -12,12 +11,19 @@ import com.topjohnwu.magisk.core.utils.SafetyNetHelper
import com.topjohnwu.magisk.data.network.GithubRawServices
import com.topjohnwu.magisk.ktx.DynamicClassLoader
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.view.MagiskDialog
import com.topjohnwu.magisk.view.MarkDownWindow
import com.topjohnwu.superuser.Shell
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.koin.core.KoinComponent
import org.koin.core.inject
@ -26,35 +32,9 @@ import java.io.File
import java.io.IOException
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(
private val callback: (SafetyNetResult) -> Unit = {}
) : ViewEventsWithScope(), ContextExecutor, KoinComponent, SafetyNetHelper.Callback {
) : ViewEventWithScope(), ContextExecutor, KoinComponent, SafetyNetHelper.Callback {
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)
}
class OpenChangelogEvent(val item: Repo) : ViewEventsWithScope(), ContextExecutor {
class OpenChangelogEvent(val item: Repo) : ViewEventWithScope(), ContextExecutor {
override fun invoke(context: Context) {
scope.launch {
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.utils.BiometricHelper
import com.topjohnwu.magisk.model.events.ActivityExecutor
import com.topjohnwu.magisk.model.events.ViewEvent
import com.topjohnwu.magisk.ui.base.ActivityExecutor
import com.topjohnwu.magisk.ui.base.ViewEvent
class BiometricDialog(
builder: Builder.() -> Unit

View File

@ -5,7 +5,7 @@ import androidx.appcompat.app.AppCompatDelegate
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.Config
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 java.lang.ref.WeakReference

View File

@ -1,8 +1,8 @@
package com.topjohnwu.magisk.model.events.dialog
import android.content.Context
import com.topjohnwu.magisk.model.events.ContextExecutor
import com.topjohnwu.magisk.model.events.ViewEvent
import com.topjohnwu.magisk.ui.base.ContextExecutor
import com.topjohnwu.magisk.ui.base.ViewEvent
import com.topjohnwu.magisk.view.MagiskDialog
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 com.topjohnwu.magisk.core.base.BaseActivity
import com.topjohnwu.magisk.model.events.ActivityExecutor
import com.topjohnwu.magisk.model.events.ViewEvent
import com.topjohnwu.magisk.ui.base.ActivityExecutor
import com.topjohnwu.magisk.ui.base.BaseUIActivity
import com.topjohnwu.magisk.ui.base.ViewEvent
class NavigationWrapper(
private val directions: NavDirections
@ -15,4 +15,4 @@ class NavigationWrapper(
directions.navigate()
}
}
}
}

View File

@ -17,9 +17,6 @@ import com.topjohnwu.magisk.BR
import com.topjohnwu.magisk.core.Config
import com.topjohnwu.magisk.core.base.BaseActivity
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
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)
}
override fun onEventDispatched(event: ViewEvent) {
(event as? ContextExecutor)?.invoke(this)
(event as? ActivityExecutor)?.invoke(this)
override fun onEventDispatched(event: ViewEvent) = when(event) {
is ContextExecutor -> event(this)
is ActivityExecutor -> event(this)
else -> Unit
}
override fun onBackPressed() {

View File

@ -5,7 +5,6 @@ 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 {

View File

@ -13,10 +13,6 @@ 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.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> :
Fragment(), BaseUIComponent<VM> {
@ -47,10 +43,11 @@ abstract class BaseUIFragment<VM : BaseViewModel, Binding : ViewDataBinding> :
return binding.root
}
override fun onEventDispatched(event: ViewEvent) {
(event as? ContextExecutor)?.invoke(requireContext())
(event as? FragmentExecutor)?.invoke(this)
(event as? ActivityExecutor)?.invoke(activity)
override fun onEventDispatched(event: ViewEvent) = when(event) {
is ContextExecutor -> event(requireContext())
is ActivityExecutor -> event(activity)
is FragmentExecutor -> event(this)
else -> Unit
}
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.core.Info
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.utils.ObservableHost
import com.topjohnwu.magisk.utils.set
@ -102,7 +105,7 @@ abstract class BaseViewModel(
_viewEvents.postValue(this)
}
fun <Event : ViewEventsWithScope> Event.publish() {
fun <Event : ViewEventWithScope> Event.publish() {
scope = viewModelScope
_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.model.entity.IconLink
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.ViewEvent
import com.topjohnwu.magisk.model.events.dialog.EnvFixDialog
import com.topjohnwu.magisk.model.events.dialog.ManagerInstallDialog
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.ViewEvent
import com.topjohnwu.magisk.ui.base.itemBindingOf
import com.topjohnwu.magisk.utils.set
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.ktx.hideKeyboard
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.base.BaseUIFragment
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.MotionRevealHelper
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.RecyclerView
import androidx.recyclerview.widget.StaggeredGridLayoutManager
import com.topjohnwu.magisk.model.events.ViewEvent
import com.topjohnwu.magisk.ui.base.ViewEvent
class EndlessRecyclerScrollListener(
private val layoutManager: RecyclerView.LayoutManager,