From fc886a5a47d586136457ebf07927c2e1da61dc4c Mon Sep 17 00:00:00 2001 From: topjohnwu Date: Sat, 28 Sep 2019 01:56:16 -0400 Subject: [PATCH] Merge Teanity into sources --- app/build.gradle | 18 +- .../magisk/databinding/AdaptersGeneric.kt | 26 ++ .../magisk/databinding/AdaptersRecycler.kt | 57 ++++ .../magisk/databinding/BindingBoundAdapter.kt | 13 + .../magisk/databinding/RecyclerViewItems.kt | 48 +++ .../topjohnwu/magisk/di/ApplicationModule.kt | 2 +- .../magisk/extensions/DataBinding.kt | 57 ++++ .../com/topjohnwu/magisk/extensions/Dimens.kt | 9 + .../com/topjohnwu/magisk/extensions/Misc.kt | 6 + .../com/topjohnwu/magisk/extensions/RxJava.kt | 201 +++++++++++++ .../topjohnwu/magisk/extensions/Snackbar.kt | 126 ++++++++ .../topjohnwu/magisk/extensions/XAndroid.kt | 34 ++- .../topjohnwu/magisk/extensions/XBinding.kt | 2 +- .../com/topjohnwu/magisk/extensions/XList.kt | 7 +- .../com/topjohnwu/magisk/extensions/XRx.kt | 9 - .../magisk/model/binding/BindingAdapter.kt | 2 +- .../model/download/RemoteFileService.kt | 2 +- .../topjohnwu/magisk/model/entity/Version.kt | 3 - .../model/entity/recycler/HideRvItem.kt | 10 +- .../model/entity/recycler/LenientRvItem.kt | 2 +- .../magisk/model/entity/recycler/LogRvItem.kt | 6 +- .../model/entity/recycler/ModuleRvItem.kt | 6 +- .../model/entity/recycler/PolicyRvItem.kt | 8 +- .../model/entity/recycler/SectionRvItem.kt | 2 +- .../model/entity/recycler/SpinnerRvItem.kt | 2 +- .../magisk/model/events/EventHandler.kt | 18 ++ .../topjohnwu/magisk/model/events/RxEvents.kt | 2 +- .../magisk/model/events/SimpleViewEvent.kt | 7 + .../magisk/model/events/SnackbarEvent.kt | 28 ++ .../magisk/model/events/ViewEventObserver.kt | 17 ++ .../magisk/model/events/ViewEvents.kt | 13 +- .../model/navigation/MagiskNavigationEvent.kt | 6 +- .../com/topjohnwu/magisk/tasks/FlashZip.kt | 2 +- .../topjohnwu/magisk/tasks/MagiskInstaller.kt | 1 - .../com/topjohnwu/magisk/ui/MainActivity.kt | 6 +- .../magisk/ui/base/MagiskActivity.kt | 48 ++- .../magisk/ui/base/MagiskFragment.kt | 47 ++- .../magisk/ui/base/MagiskViewModel.kt | 8 +- .../magisk/ui/flash/FlashViewModel.kt | 9 +- .../topjohnwu/magisk/ui/hide/HideViewModel.kt | 12 +- .../topjohnwu/magisk/ui/home/HomeFragment.kt | 4 +- .../topjohnwu/magisk/ui/home/HomeViewModel.kt | 10 +- .../topjohnwu/magisk/ui/log/LogFragment.kt | 2 +- .../topjohnwu/magisk/ui/log/LogViewModel.kt | 14 +- .../magisk/ui/module/ModuleViewModel.kt | 12 +- .../magisk/ui/module/ModulesFragment.kt | 2 +- .../magisk/ui/module/ReposFragment.kt | 2 +- .../magisk/ui/settings/SettingsFragment.kt | 3 +- .../magisk/ui/superuser/SuperuserViewModel.kt | 12 +- .../magisk/ui/surequest/SuRequestActivity.kt | 2 +- .../magisk/ui/surequest/SuRequestViewModel.kt | 8 +- .../magisk/utils/DataBindingAdapters.kt | 2 +- .../magisk/utils/DiffObservableList.kt | 234 +++++++++++++++ .../topjohnwu/magisk/utils/KItemDecoration.kt | 117 ++++++++ .../magisk/utils/KObservableField.kt | 49 ++++ .../com/topjohnwu/magisk/utils/PatchAPK.kt | 2 +- .../java/com/topjohnwu/magisk/utils/RxBus.kt | 36 +++ .../com/topjohnwu/magisk/view/MagiskDialog.kt | 155 ---------- .../topjohnwu/magisk/view/MarkDownWindow.kt | 2 +- .../magisk/view/dialogs/EnvFixDialog.kt | 2 +- .../magisk/viewmodel/LoadingViewModel.kt | 78 +++++ .../magisk/viewmodel/ObservableViewModel.kt | 46 +++ .../magisk/viewmodel/StatefulViewModel.kt | 15 + .../magisk/viewmodel/TeanityViewModel.kt | 33 +++ .../main/res/layout/dialog_magisk_base.xml | 276 ------------------ app/src/main/res/layout/fragment_magisk.xml | 2 +- build.gradle | 4 +- 67 files changed, 1436 insertions(+), 570 deletions(-) create mode 100644 app/src/main/java/com/topjohnwu/magisk/databinding/AdaptersGeneric.kt create mode 100644 app/src/main/java/com/topjohnwu/magisk/databinding/AdaptersRecycler.kt create mode 100644 app/src/main/java/com/topjohnwu/magisk/databinding/BindingBoundAdapter.kt create mode 100644 app/src/main/java/com/topjohnwu/magisk/databinding/RecyclerViewItems.kt create mode 100644 app/src/main/java/com/topjohnwu/magisk/extensions/DataBinding.kt create mode 100644 app/src/main/java/com/topjohnwu/magisk/extensions/Dimens.kt create mode 100644 app/src/main/java/com/topjohnwu/magisk/extensions/Misc.kt create mode 100644 app/src/main/java/com/topjohnwu/magisk/extensions/RxJava.kt create mode 100644 app/src/main/java/com/topjohnwu/magisk/extensions/Snackbar.kt delete mode 100644 app/src/main/java/com/topjohnwu/magisk/extensions/XRx.kt delete mode 100644 app/src/main/java/com/topjohnwu/magisk/model/entity/Version.kt create mode 100644 app/src/main/java/com/topjohnwu/magisk/model/events/EventHandler.kt create mode 100644 app/src/main/java/com/topjohnwu/magisk/model/events/SimpleViewEvent.kt create mode 100644 app/src/main/java/com/topjohnwu/magisk/model/events/SnackbarEvent.kt create mode 100644 app/src/main/java/com/topjohnwu/magisk/model/events/ViewEventObserver.kt create mode 100644 app/src/main/java/com/topjohnwu/magisk/utils/DiffObservableList.kt create mode 100644 app/src/main/java/com/topjohnwu/magisk/utils/KItemDecoration.kt create mode 100644 app/src/main/java/com/topjohnwu/magisk/utils/KObservableField.kt create mode 100644 app/src/main/java/com/topjohnwu/magisk/utils/RxBus.kt delete mode 100644 app/src/main/java/com/topjohnwu/magisk/view/MagiskDialog.kt create mode 100644 app/src/main/java/com/topjohnwu/magisk/viewmodel/LoadingViewModel.kt create mode 100644 app/src/main/java/com/topjohnwu/magisk/viewmodel/ObservableViewModel.kt create mode 100644 app/src/main/java/com/topjohnwu/magisk/viewmodel/StatefulViewModel.kt create mode 100644 app/src/main/java/com/topjohnwu/magisk/viewmodel/TeanityViewModel.kt delete mode 100644 app/src/main/res/layout/dialog_magisk_base.xml diff --git a/app/build.gradle b/app/build.gradle index 4b1156ed7..348df90e3 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -60,9 +60,19 @@ dependencies { implementation 'com.github.topjohnwu:jtar:1.0.0' implementation 'com.jakewharton.timber:timber:4.7.1' - implementation 'com.github.skoumalcz:teanity:0.3.3' implementation 'com.ncapdevi:frag-nav:3.2.0' implementation 'com.github.pwittchen:reactivenetwork-rx2:3.0.6' + implementation 'com.karumi:dexter:6.0.0' + + implementation "io.reactivex.rxjava2:rxjava:2.2.12" + implementation "io.reactivex.rxjava2:rxkotlin:2.4.0" + + implementation "org.jetbrains.kotlin:kotlin-stdlib:${vKotlin}" + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${vKotlin}" + + def vBAdapt = '3.1.1' + implementation "me.tatarka.bindingcollectionadapter2:bindingcollectionadapter:${vBAdapt}" + implementation "me.tatarka.bindingcollectionadapter2:bindingcollectionadapter-recyclerview:${vBAdapt}" def vMarkwon = '3.1.0' implementation "ru.noties.markwon:core:${vMarkwon}" @@ -104,8 +114,9 @@ dependencies { implementation "com.github.topjohnwu:room-runtime:${vRoom}" kapt "androidx.room:room-compiler:${vRoom}" - implementation "org.jetbrains.kotlin:kotlin-stdlib:${kotlinVer}" - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${kotlinVer}" + def vNav = "2.1.0" + implementation "androidx.navigation:navigation-fragment-ktx:$vNav" + implementation "androidx.navigation:navigation-ui-ktx:$vNav" implementation 'androidx.constraintlayout:constraintlayout:1.1.3' implementation 'androidx.browser:browser:1.0.0' @@ -115,5 +126,6 @@ dependencies { implementation 'androidx.work:work-runtime:2.2.0' implementation 'androidx.transition:transition:1.2.0-rc01' implementation 'androidx.multidex:multidex:2.0.1' + implementation 'androidx.core:core-ktx:1.1.0' implementation 'com.google.android.material:material:1.1.0-alpha10' } diff --git a/app/src/main/java/com/topjohnwu/magisk/databinding/AdaptersGeneric.kt b/app/src/main/java/com/topjohnwu/magisk/databinding/AdaptersGeneric.kt new file mode 100644 index 000000000..0038cb7af --- /dev/null +++ b/app/src/main/java/com/topjohnwu/magisk/databinding/AdaptersGeneric.kt @@ -0,0 +1,26 @@ +package com.topjohnwu.magisk.databinding + +import android.view.View +import androidx.core.view.isGone +import androidx.core.view.isInvisible +import androidx.databinding.BindingAdapter + +@BindingAdapter("gone") +fun setGone(view: View, gone: Boolean) { + view.isGone = gone +} + +@BindingAdapter("invisible") +fun setInvisible(view: View, invisible: Boolean) { + view.isInvisible = invisible +} + +@BindingAdapter("goneUnless") +fun setGoneUnless(view: View, goneUnless: Boolean) { + setGone(view, goneUnless.not()) +} + +@BindingAdapter("invisibleUnless") +fun setInvisibleUnless(view: View, invisibleUnless: Boolean) { + setInvisible(view, invisibleUnless.not()) +} diff --git a/app/src/main/java/com/topjohnwu/magisk/databinding/AdaptersRecycler.kt b/app/src/main/java/com/topjohnwu/magisk/databinding/AdaptersRecycler.kt new file mode 100644 index 000000000..9815383b3 --- /dev/null +++ b/app/src/main/java/com/topjohnwu/magisk/databinding/AdaptersRecycler.kt @@ -0,0 +1,57 @@ +package com.topjohnwu.magisk.databinding + +import android.graphics.drawable.GradientDrawable +import android.graphics.drawable.InsetDrawable +import androidx.databinding.BindingAdapter +import androidx.recyclerview.widget.RecyclerView +import com.topjohnwu.magisk.extensions.startEndToLeftRight +import com.topjohnwu.magisk.extensions.toPx +import com.topjohnwu.magisk.utils.KItemDecoration +import kotlin.math.roundToInt + +@BindingAdapter( + "dividerColor", + "dividerHorizontal", + "dividerSize", + "dividerAfterLast", + "dividerMarginStart", + "dividerMarginEnd", + "dividerMarginTop", + "dividerMarginBottom", + requireAll = false +) +fun setDivider( + view: RecyclerView, + color: Int, + horizontal: Boolean, + _size: Float, + _afterLast: Boolean?, + marginStartF: Float, + marginEndF: Float, + marginTopF: Float, + marginBottomF: Float +) { + val orientation = if (horizontal) RecyclerView.HORIZONTAL else RecyclerView.VERTICAL + val size = if (_size > 0) _size.roundToInt() else 1.toPx() + val (width, height) = if (horizontal) size to 1 else 1 to size + val afterLast = _afterLast ?: true + + val marginStart = marginStartF.roundToInt() + val marginEnd = marginEndF.roundToInt() + val marginTop = marginTopF.roundToInt() + val marginBottom = marginBottomF.roundToInt() + val (marginLeft, marginRight) = view.context.startEndToLeftRight(marginStart, marginEnd) + + val drawable = GradientDrawable().apply { + setSize(width, height) + shape = GradientDrawable.RECTANGLE + setColor(color) + }.let { + InsetDrawable(it, marginLeft, marginTop, marginRight, marginBottom) + } + + val decoration = KItemDecoration(view.context, orientation) + .setDeco(drawable) + .apply { showAfterLast = afterLast } + view.addItemDecoration(decoration) +} diff --git a/app/src/main/java/com/topjohnwu/magisk/databinding/BindingBoundAdapter.kt b/app/src/main/java/com/topjohnwu/magisk/databinding/BindingBoundAdapter.kt new file mode 100644 index 000000000..39d439303 --- /dev/null +++ b/app/src/main/java/com/topjohnwu/magisk/databinding/BindingBoundAdapter.kt @@ -0,0 +1,13 @@ +package com.topjohnwu.magisk.databinding + +import androidx.databinding.ViewDataBinding +import me.tatarka.bindingcollectionadapter2.BindingRecyclerViewAdapter + +open class BindingBoundAdapter : BindingRecyclerViewAdapter() { + + override fun onBindBinding(binding: ViewDataBinding, variableId: Int, layoutRes: Int, position: Int, item: RvItem) { + super.onBindBinding(binding, variableId, layoutRes, position, item) + + item.onBindingBound(binding) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/topjohnwu/magisk/databinding/RecyclerViewItems.kt b/app/src/main/java/com/topjohnwu/magisk/databinding/RecyclerViewItems.kt new file mode 100644 index 000000000..087a753f1 --- /dev/null +++ b/app/src/main/java/com/topjohnwu/magisk/databinding/RecyclerViewItems.kt @@ -0,0 +1,48 @@ +package com.topjohnwu.magisk.databinding + +import androidx.annotation.CallSuper +import androidx.databinding.ViewDataBinding +import com.topjohnwu.magisk.BR +import com.topjohnwu.magisk.utils.DiffObservableList +import me.tatarka.bindingcollectionadapter2.ItemBinding + +abstract class RvItem { + + abstract val layoutRes: Int + + @CallSuper + open fun bind(binding: ItemBinding<*>) { + binding.set(BR.item, layoutRes) + } + + /** + * This callback is useful if you want to manipulate your views directly. + * If you want to use this callback, you must set [me.tatarka.bindingcollectionadapter2.BindingRecyclerViewAdapter] + * on your RecyclerView and call it from there. You can use [BindingBoundAdapter] for your convenience. + */ + open fun onBindingBound(binding: ViewDataBinding) {} +} + +abstract class ComparableRvItem : RvItem() { + + abstract fun itemSameAs(other: T): Boolean + abstract fun contentSameAs(other: T): Boolean + @Suppress("UNCHECKED_CAST") + open fun genericItemSameAs(other: Any): Boolean = other::class == this::class && itemSameAs(other as T) + @Suppress("UNCHECKED_CAST") + open fun genericContentSameAs(other: Any): Boolean = other::class == this::class && contentSameAs(other as T) + + companion object { + val callback = object : DiffObservableList.Callback> { + override fun areItemsTheSame( + oldItem: ComparableRvItem<*>, + newItem: ComparableRvItem<*> + ) = oldItem.genericItemSameAs(newItem) + + override fun areContentsTheSame( + oldItem: ComparableRvItem<*>, + newItem: ComparableRvItem<*> + ) = oldItem.genericContentSameAs(newItem) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/topjohnwu/magisk/di/ApplicationModule.kt b/app/src/main/java/com/topjohnwu/magisk/di/ApplicationModule.kt index d03cec643..437c69c3a 100644 --- a/app/src/main/java/com/topjohnwu/magisk/di/ApplicationModule.kt +++ b/app/src/main/java/com/topjohnwu/magisk/di/ApplicationModule.kt @@ -7,7 +7,7 @@ import android.content.Context import android.os.Build import android.os.Bundle import androidx.preference.PreferenceManager -import com.skoumal.teanity.rxbus.RxBus +import com.topjohnwu.magisk.utils.RxBus import org.koin.core.qualifier.named import org.koin.dsl.module diff --git a/app/src/main/java/com/topjohnwu/magisk/extensions/DataBinding.kt b/app/src/main/java/com/topjohnwu/magisk/extensions/DataBinding.kt new file mode 100644 index 000000000..f671815a1 --- /dev/null +++ b/app/src/main/java/com/topjohnwu/magisk/extensions/DataBinding.kt @@ -0,0 +1,57 @@ +package com.topjohnwu.magisk.extensions + +import androidx.databinding.Observable +import androidx.databinding.ObservableBoolean +import androidx.databinding.ObservableField +import androidx.databinding.ObservableInt + +fun ObservableField.addOnPropertyChangedCallback( + removeAfterChanged: Boolean = false, + callback: (T?) -> Unit +) { + addOnPropertyChangedCallback(object : Observable.OnPropertyChangedCallback() { + override fun onPropertyChanged(sender: Observable?, propertyId: Int) { + callback(get()) + if (removeAfterChanged) removeOnPropertyChangedCallback(this) + } + }) +} + +fun ObservableInt.addOnPropertyChangedCallback( + removeAfterChanged: Boolean = false, + callback: (Int) -> Unit +) { + addOnPropertyChangedCallback(object : Observable.OnPropertyChangedCallback() { + override fun onPropertyChanged(sender: Observable?, propertyId: Int) { + callback(get()) + if (removeAfterChanged) removeOnPropertyChangedCallback(this) + } + }) +} + +fun ObservableBoolean.addOnPropertyChangedCallback( + removeAfterChanged: Boolean = false, + callback: (Boolean) -> Unit +) { + addOnPropertyChangedCallback(object : Observable.OnPropertyChangedCallback() { + override fun onPropertyChanged(sender: Observable?, propertyId: Int) { + callback(get()) + if (removeAfterChanged) removeOnPropertyChangedCallback(this) + } + }) +} + +inline fun ObservableField.update(block: (T?) -> Unit) { + set(get().apply(block)) +} + +inline fun ObservableField.updateNonNull(block: (T) -> Unit) { + update { + it ?: return@update + block(it) + } +} + +inline fun ObservableInt.update(block: (Int) -> Unit) { + set(get().apply(block)) +} \ No newline at end of file diff --git a/app/src/main/java/com/topjohnwu/magisk/extensions/Dimens.kt b/app/src/main/java/com/topjohnwu/magisk/extensions/Dimens.kt new file mode 100644 index 000000000..77c5d3439 --- /dev/null +++ b/app/src/main/java/com/topjohnwu/magisk/extensions/Dimens.kt @@ -0,0 +1,9 @@ +package com.topjohnwu.magisk.extensions + +import android.content.res.Resources +import kotlin.math.ceil +import kotlin.math.roundToInt + +fun Int.toDp(): Int = ceil(this / Resources.getSystem().displayMetrics.density).roundToInt() + +fun Int.toPx(): Int = (this * Resources.getSystem().displayMetrics.density).roundToInt() diff --git a/app/src/main/java/com/topjohnwu/magisk/extensions/Misc.kt b/app/src/main/java/com/topjohnwu/magisk/extensions/Misc.kt new file mode 100644 index 000000000..b3a54ab4f --- /dev/null +++ b/app/src/main/java/com/topjohnwu/magisk/extensions/Misc.kt @@ -0,0 +1,6 @@ +package com.topjohnwu.magisk.extensions + +import android.os.Handler +import android.os.Looper + +fun ui(body: () -> Unit) = Handler(Looper.getMainLooper()).post(body) \ No newline at end of file diff --git a/app/src/main/java/com/topjohnwu/magisk/extensions/RxJava.kt b/app/src/main/java/com/topjohnwu/magisk/extensions/RxJava.kt new file mode 100644 index 000000000..a4eefb0e9 --- /dev/null +++ b/app/src/main/java/com/topjohnwu/magisk/extensions/RxJava.kt @@ -0,0 +1,201 @@ +package com.topjohnwu.magisk.extensions + +import androidx.databinding.ObservableField +import com.topjohnwu.magisk.utils.KObservableField +import io.reactivex.* +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.disposables.Disposables +import io.reactivex.functions.BiFunction +import io.reactivex.schedulers.Schedulers +import androidx.databinding.Observable as BindingObservable + +fun Observable.applySchedulers( + subscribeOn: Scheduler = Schedulers.io(), + observeOn: Scheduler = AndroidSchedulers.mainThread() +): Observable = this.subscribeOn(subscribeOn).observeOn(observeOn) + +fun Flowable.applySchedulers( + subscribeOn: Scheduler = Schedulers.io(), + observeOn: Scheduler = AndroidSchedulers.mainThread() +): Flowable = this.subscribeOn(subscribeOn).observeOn(observeOn) + +fun Single.applySchedulers( + subscribeOn: Scheduler = Schedulers.io(), + observeOn: Scheduler = AndroidSchedulers.mainThread() +): Single = this.subscribeOn(subscribeOn).observeOn(observeOn) + +fun Maybe.applySchedulers( + subscribeOn: Scheduler = Schedulers.io(), + observeOn: Scheduler = AndroidSchedulers.mainThread() +): Maybe = this.subscribeOn(subscribeOn).observeOn(observeOn) + +fun Completable.applySchedulers( + subscribeOn: Scheduler = Schedulers.io(), + observeOn: Scheduler = AndroidSchedulers.mainThread() +): Completable = this.subscribeOn(subscribeOn).observeOn(observeOn) + +/*=== ALIASES FOR OBSERVABLES ===*/ + +typealias OnCompleteListener = () -> Unit +typealias OnSuccessListener = (T) -> Unit +typealias OnErrorListener = (Throwable) -> Unit + +/*=== ALIASES FOR OBSERVABLES ===*/ + +fun Observable.subscribeK( + onError: OnErrorListener = { it.printStackTrace() }, + onComplete: OnCompleteListener = {}, + onNext: OnSuccessListener = {} +) = applySchedulers() + .subscribe(onNext, onError, onComplete) + +fun Single.subscribeK( + onError: OnErrorListener = { it.printStackTrace() }, + onNext: OnSuccessListener = {} +) = applySchedulers() + .subscribe(onNext, onError) + +fun Maybe.subscribeK( + onError: OnErrorListener = { it.printStackTrace() }, + onComplete: OnCompleteListener = {}, + onNext: OnSuccessListener = {} +) = applySchedulers() + .subscribe(onNext, onError, onComplete) + +fun Flowable.subscribeK( + onError: OnErrorListener = { it.printStackTrace() }, + onComplete: OnCompleteListener = {}, + onNext: OnSuccessListener = {} +) = applySchedulers() + .subscribe(onNext, onError, onComplete) + +fun Completable.subscribeK( + onError: OnErrorListener = { it.printStackTrace() }, + onComplete: OnCompleteListener = {} +) = applySchedulers() + .subscribe(onComplete, onError) + + +fun Observable.updateBy( + field: KObservableField +) = doOnNextUi { field.value = it } + .doOnErrorUi { field.value = null } + +fun Single.updateBy( + field: KObservableField +) = doOnSuccessUi { field.value = it } + .doOnErrorUi { field.value = null } + +fun Maybe.updateBy( + field: KObservableField +) = doOnSuccessUi { field.value = it } + .doOnErrorUi { field.value = null } + .doOnComplete { field.value = field.value } + +fun Flowable.updateBy( + field: KObservableField +) = doOnNextUi { field.value = it } + .doOnErrorUi { field.value = null } + +fun Completable.updateBy( + field: KObservableField +) = doOnCompleteUi { field.value = true } + .doOnErrorUi { field.value = false } + + +fun Observable.doOnSubscribeUi(body: () -> Unit) = + doOnSubscribe { ui { body() } } + +fun Single.doOnSubscribeUi(body: () -> Unit) = + doOnSubscribe { ui { body() } } + +fun Maybe.doOnSubscribeUi(body: () -> Unit) = + doOnSubscribe { ui { body() } } + +fun Flowable.doOnSubscribeUi(body: () -> Unit) = + doOnSubscribe { ui { body() } } + +fun Completable.doOnSubscribeUi(body: () -> Unit) = + doOnSubscribe { ui { body() } } + + +fun Observable.doOnErrorUi(body: (Throwable) -> Unit) = + doOnError { ui { body(it) } } + +fun Single.doOnErrorUi(body: (Throwable) -> Unit) = + doOnError { ui { body(it) } } + +fun Maybe.doOnErrorUi(body: (Throwable) -> Unit) = + doOnError { ui { body(it) } } + +fun Flowable.doOnErrorUi(body: (Throwable) -> Unit) = + doOnError { ui { body(it) } } + +fun Completable.doOnErrorUi(body: (Throwable) -> Unit) = + doOnError { ui { body(it) } } + + +fun Observable.doOnNextUi(body: (T) -> Unit) = + doOnNext { ui { body(it) } } + +fun Flowable.doOnNextUi(body: (T) -> Unit) = + doOnNext { ui { body(it) } } + +fun Single.doOnSuccessUi(body: (T) -> Unit) = + doOnSuccess { ui { body(it) } } + +fun Maybe.doOnSuccessUi(body: (T) -> Unit) = + doOnSuccess { ui { body(it) } } + +fun Maybe.doOnCompleteUi(body: () -> Unit) = + doOnComplete { ui { body() } } + +fun Completable.doOnCompleteUi(body: () -> Unit) = + doOnComplete { ui { body() } } + + +fun Observable>.mapList( + transformer: (T) -> R +) = flatMapIterable { it } + .map(transformer) + .toList() + +fun Single>.mapList( + transformer: (T) -> R +) = flattenAsFlowable { it } + .map(transformer) + .toList() + +fun Maybe>.mapList( + transformer: (T) -> R +) = flattenAsFlowable { it } + .map(transformer) + .toList() + +fun Flowable>.mapList( + transformer: (T) -> R +) = flatMapIterable { it } + .map(transformer) + .toList() + +fun ObservableField.toObservable(): Observable { + val observableField = this + return Observable.create { emitter -> + observableField.get()?.let { emitter.onNext(it) } + + val callback = object : BindingObservable.OnPropertyChangedCallback() { + override fun onPropertyChanged(sender: BindingObservable?, propertyId: Int) { + observableField.get()?.let { emitter.onNext(it) } + } + } + observableField.addOnPropertyChangedCallback(callback) + emitter.setDisposable(Disposables.fromAction { + observableField.removeOnPropertyChangedCallback(callback) + }) + } +} + +fun T.toSingle() = Single.just(this) + +fun zip(t1: Single, t2: Single, zipper: (T1, T2) -> R) = + Single.zip(t1, t2, BiFunction { rt1, rt2 -> zipper(rt1, rt2) }) diff --git a/app/src/main/java/com/topjohnwu/magisk/extensions/Snackbar.kt b/app/src/main/java/com/topjohnwu/magisk/extensions/Snackbar.kt new file mode 100644 index 000000000..a9e5e133f --- /dev/null +++ b/app/src/main/java/com/topjohnwu/magisk/extensions/Snackbar.kt @@ -0,0 +1,126 @@ +package com.topjohnwu.magisk.extensions + +import android.content.Context +import android.content.res.ColorStateList +import android.view.View +import android.widget.TextView +import androidx.annotation.ColorInt +import androidx.annotation.ColorRes +import androidx.annotation.StringRes +import androidx.appcompat.app.AppCompatActivity +import androidx.core.content.ContextCompat +import androidx.core.view.ViewCompat +import androidx.fragment.app.Fragment +import com.google.android.material.snackbar.Snackbar + +fun AppCompatActivity.snackbar( + view: View, + @StringRes messageRes: Int, + length: Int = Snackbar.LENGTH_SHORT, + f: Snackbar.() -> Unit = {} +) { + snackbar(view, getString(messageRes), length, f) +} + +fun AppCompatActivity.snackbar( + view: View, + message: String, + length: Int = Snackbar.LENGTH_SHORT, + f: Snackbar.() -> Unit = {} +) = Snackbar.make(view, message, length) + .apply(f) + .show() + +fun Fragment.snackbar( + view: View, + @StringRes messageRes: Int, + length: Int = Snackbar.LENGTH_SHORT, + f: Snackbar.() -> Unit = {} +) { + snackbar(view, getString(messageRes), length, f) +} + +fun Fragment.snackbar( + view: View, + message: String, + length: Int = Snackbar.LENGTH_SHORT, + f: Snackbar.() -> Unit = {} +) = Snackbar.make(view, message, length) + .apply(f) + .show() + +fun Snackbar.action(init: KSnackbar.() -> Unit) = apply { + val config = KSnackbar().apply(init) + + setAction(config.title(context), config.onClickListener) + + when { + config.hasValidColor -> setActionTextColor(config.color(context) ?: return@apply) + config.hasValidColorStateList -> setActionTextColor(config.colorStateList(context) ?: return@apply) + } +} + +class KSnackbar { + var colorRes: Int = -1 + var colorStateListRes: Int = -1 + + var title: CharSequence = "" + var titleRes: Int = -1 + + internal var onClickListener: (View) -> Unit = {} + internal val hasValidColor get() = colorRes != -1 + internal val hasValidColorStateList get() = colorStateListRes != -1 + + fun onClicked(listener: (View) -> Unit) { + onClickListener = listener + } + + internal fun title(context: Context) = if (title.isBlank()) context.getString(titleRes) else title + internal fun colorStateList(context: Context) = context.colorStateListCompat(colorStateListRes) + internal fun color(context: Context) = context.colorCompat(colorRes) +} + +@Deprecated("Kotlin DSL version is preferred", ReplaceWith("action {}")) +fun Snackbar.action( + @StringRes actionRes: Int, + @ColorRes colorRes: Int? = null, + listener: (View) -> Unit +) { + view.resources.getString(actionRes) + colorRes?.let { ContextCompat.getColor(view.context, colorRes) } + action {} +} + +@Deprecated("Kotlin DSL version is preferred", ReplaceWith("action {}")) +fun Snackbar.action(action: String, @ColorInt color: Int? = null, listener: (View) -> Unit) { + setAction(action, listener) + color?.let { setActionTextColor(color) } +} + +fun Snackbar.textColorRes(@ColorRes colorRes: Int) { + textColor(context.colorCompat(colorRes) ?: return) +} + +fun Snackbar.textColor(@ColorInt color: Int) { + val tv = view.findViewById(com.google.android.material.R.id.snackbar_text) + tv.setTextColor(color) +} + +fun Snackbar.backgroundColorRes(@ColorRes colorRes: Int) { + backgroundColor(context.colorCompat(colorRes) ?: return) +} + +fun Snackbar.backgroundColor(@ColorInt color: Int) { + ViewCompat.setBackgroundTintList( + view, + ColorStateList.valueOf(color) + ) +} + +fun Snackbar.alert() { + textColor(0xF44336) +} + +fun Snackbar.success() { + textColor(0x4CAF50) +} \ No newline at end of file diff --git a/app/src/main/java/com/topjohnwu/magisk/extensions/XAndroid.kt b/app/src/main/java/com/topjohnwu/magisk/extensions/XAndroid.kt index 2ebb2eb38..3c669600a 100644 --- a/app/src/main/java/com/topjohnwu/magisk/extensions/XAndroid.kt +++ b/app/src/main/java/com/topjohnwu/magisk/extensions/XAndroid.kt @@ -8,9 +8,15 @@ import android.content.pm.PackageInfo import android.content.pm.PackageManager import android.content.pm.PackageManager.* import android.content.res.Configuration +import android.content.res.Resources import android.database.Cursor import android.net.Uri +import android.os.Build import android.provider.OpenableColumns +import android.view.View +import androidx.annotation.ColorRes +import androidx.annotation.DrawableRes +import androidx.core.content.ContextCompat import com.topjohnwu.magisk.utils.FileProvider import com.topjohnwu.magisk.utils.currentLocale import java.io.File @@ -120,4 +126,30 @@ fun ApplicationInfo.getLabel(pm: PackageManager): String { return loadLabel(pm).toString() } -fun Intent.exists(packageManager: PackageManager) = resolveActivity(packageManager) != null \ No newline at end of file +fun Intent.exists(packageManager: PackageManager) = resolveActivity(packageManager) != null + +fun Context.colorCompat(@ColorRes id: Int) = try { + ContextCompat.getColor(this, id) +} catch (e: Resources.NotFoundException) { + null +} + +fun Context.colorStateListCompat(@ColorRes id: Int) = try { + ContextCompat.getColorStateList(this, id) +} catch (e: Resources.NotFoundException) { + null +} + +fun Context.drawableCompat(@DrawableRes id: Int) = ContextCompat.getDrawable(this, id) +/** + * Pass [start] and [end] dimensions, function will return left and right + * with respect to RTL layout direction + */ +fun Context.startEndToLeftRight(start: Int, end: Int): Pair { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 && + resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_RTL + ) { + return end to start + } + return start to end +} \ No newline at end of file diff --git a/app/src/main/java/com/topjohnwu/magisk/extensions/XBinding.kt b/app/src/main/java/com/topjohnwu/magisk/extensions/XBinding.kt index 36874f1e4..c0c5c8dd1 100644 --- a/app/src/main/java/com/topjohnwu/magisk/extensions/XBinding.kt +++ b/app/src/main/java/com/topjohnwu/magisk/extensions/XBinding.kt @@ -1,6 +1,6 @@ package com.topjohnwu.magisk.extensions -import com.skoumal.teanity.util.KObservableField +import com.topjohnwu.magisk.utils.KObservableField fun KObservableField.toggle() { diff --git a/app/src/main/java/com/topjohnwu/magisk/extensions/XList.kt b/app/src/main/java/com/topjohnwu/magisk/extensions/XList.kt index ee79f13f8..88e5807c1 100644 --- a/app/src/main/java/com/topjohnwu/magisk/extensions/XList.kt +++ b/app/src/main/java/com/topjohnwu/magisk/extensions/XList.kt @@ -2,8 +2,7 @@ package com.topjohnwu.magisk.extensions import androidx.collection.SparseArrayCompat import androidx.databinding.ObservableList -import com.skoumal.teanity.extensions.subscribeK -import com.skoumal.teanity.util.DiffObservableList +import com.topjohnwu.magisk.utils.DiffObservableList import io.reactivex.disposables.Disposable fun MutableList.update(newList: List) { @@ -26,8 +25,8 @@ fun List.toShellCmd(): String { } fun ObservableList.sendUpdatesTo( - target: DiffObservableList, - mapper: (List) -> List + target: DiffObservableList, + mapper: (List) -> List ) = addOnListChangedCallback(object : ObservableList.OnListChangedCallback>() { override fun onChanged(sender: ObservableList?) { diff --git a/app/src/main/java/com/topjohnwu/magisk/extensions/XRx.kt b/app/src/main/java/com/topjohnwu/magisk/extensions/XRx.kt deleted file mode 100644 index 7dbb2bd08..000000000 --- a/app/src/main/java/com/topjohnwu/magisk/extensions/XRx.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.topjohnwu.magisk.extensions - -import io.reactivex.Single -import io.reactivex.functions.BiFunction - -fun T.toSingle() = Single.just(this) - -fun zip(t1: Single, t2: Single, zipper: (T1, T2) -> R) = - Single.zip(t1, t2, BiFunction { rt1, rt2 -> zipper(rt1, rt2) }) \ No newline at end of file diff --git a/app/src/main/java/com/topjohnwu/magisk/model/binding/BindingAdapter.kt b/app/src/main/java/com/topjohnwu/magisk/model/binding/BindingAdapter.kt index 5bfad68c6..ec4cd43b2 100644 --- a/app/src/main/java/com/topjohnwu/magisk/model/binding/BindingAdapter.kt +++ b/app/src/main/java/com/topjohnwu/magisk/model/binding/BindingAdapter.kt @@ -2,7 +2,7 @@ package com.topjohnwu.magisk.model.binding import androidx.databinding.ViewDataBinding import androidx.recyclerview.widget.RecyclerView -import com.skoumal.teanity.databinding.ComparableRvItem +import com.topjohnwu.magisk.databinding.ComparableRvItem import com.topjohnwu.magisk.model.entity.recycler.LenientRvItem import me.tatarka.bindingcollectionadapter2.BindingRecyclerViewAdapter diff --git a/app/src/main/java/com/topjohnwu/magisk/model/download/RemoteFileService.kt b/app/src/main/java/com/topjohnwu/magisk/model/download/RemoteFileService.kt index 84b5a6742..1c5fea005 100644 --- a/app/src/main/java/com/topjohnwu/magisk/model/download/RemoteFileService.kt +++ b/app/src/main/java/com/topjohnwu/magisk/model/download/RemoteFileService.kt @@ -3,11 +3,11 @@ package com.topjohnwu.magisk.model.download import android.app.Activity import android.content.Intent import androidx.core.app.NotificationCompat -import com.skoumal.teanity.extensions.subscribeK import com.topjohnwu.magisk.R import com.topjohnwu.magisk.data.network.GithubRawServices import com.topjohnwu.magisk.di.NullActivity import com.topjohnwu.magisk.extensions.get +import com.topjohnwu.magisk.extensions.subscribeK import com.topjohnwu.magisk.extensions.writeTo import com.topjohnwu.magisk.model.entity.internal.DownloadSubject import com.topjohnwu.magisk.model.entity.internal.DownloadSubject.* diff --git a/app/src/main/java/com/topjohnwu/magisk/model/entity/Version.kt b/app/src/main/java/com/topjohnwu/magisk/model/entity/Version.kt deleted file mode 100644 index 5a7728a15..000000000 --- a/app/src/main/java/com/topjohnwu/magisk/model/entity/Version.kt +++ /dev/null @@ -1,3 +0,0 @@ -package com.topjohnwu.magisk.model.entity - -data class Version(val version: String, val versionCode: Int) \ No newline at end of file diff --git a/app/src/main/java/com/topjohnwu/magisk/model/entity/recycler/HideRvItem.kt b/app/src/main/java/com/topjohnwu/magisk/model/entity/recycler/HideRvItem.kt index 1ea743e92..9e36d401d 100644 --- a/app/src/main/java/com/topjohnwu/magisk/model/entity/recycler/HideRvItem.kt +++ b/app/src/main/java/com/topjohnwu/magisk/model/entity/recycler/HideRvItem.kt @@ -1,17 +1,17 @@ package com.topjohnwu.magisk.model.entity.recycler -import com.skoumal.teanity.databinding.ComparableRvItem -import com.skoumal.teanity.extensions.addOnPropertyChangedCallback -import com.skoumal.teanity.rxbus.RxBus -import com.skoumal.teanity.util.DiffObservableList -import com.skoumal.teanity.util.KObservableField +import com.topjohnwu.magisk.utils.RxBus import com.topjohnwu.magisk.R +import com.topjohnwu.magisk.databinding.ComparableRvItem +import com.topjohnwu.magisk.extensions.addOnPropertyChangedCallback import com.topjohnwu.magisk.extensions.inject import com.topjohnwu.magisk.extensions.toggle import com.topjohnwu.magisk.model.entity.HideAppInfo import com.topjohnwu.magisk.model.entity.HideTarget import com.topjohnwu.magisk.model.entity.state.IndeterminateState import com.topjohnwu.magisk.model.events.HideProcessEvent +import com.topjohnwu.magisk.utils.DiffObservableList +import com.topjohnwu.magisk.utils.KObservableField class HideRvItem(val item: HideAppInfo, targets: List) : ComparableRvItem() { diff --git a/app/src/main/java/com/topjohnwu/magisk/model/entity/recycler/LenientRvItem.kt b/app/src/main/java/com/topjohnwu/magisk/model/entity/recycler/LenientRvItem.kt index ddbd2fbec..f21df6b91 100644 --- a/app/src/main/java/com/topjohnwu/magisk/model/entity/recycler/LenientRvItem.kt +++ b/app/src/main/java/com/topjohnwu/magisk/model/entity/recycler/LenientRvItem.kt @@ -2,7 +2,7 @@ package com.topjohnwu.magisk.model.entity.recycler import androidx.databinding.ViewDataBinding import androidx.recyclerview.widget.RecyclerView -import com.skoumal.teanity.databinding.ComparableRvItem +import com.topjohnwu.magisk.databinding.ComparableRvItem /** * This item addresses issues where enclosing recycler has to be invalidated or generally diff --git a/app/src/main/java/com/topjohnwu/magisk/model/entity/recycler/LogRvItem.kt b/app/src/main/java/com/topjohnwu/magisk/model/entity/recycler/LogRvItem.kt index 8f467b8fd..ad278ef80 100644 --- a/app/src/main/java/com/topjohnwu/magisk/model/entity/recycler/LogRvItem.kt +++ b/app/src/main/java/com/topjohnwu/magisk/model/entity/recycler/LogRvItem.kt @@ -1,14 +1,14 @@ package com.topjohnwu.magisk.model.entity.recycler -import com.skoumal.teanity.databinding.ComparableRvItem -import com.skoumal.teanity.util.DiffObservableList -import com.skoumal.teanity.util.KObservableField import com.topjohnwu.magisk.R +import com.topjohnwu.magisk.databinding.ComparableRvItem import com.topjohnwu.magisk.extensions.timeFormatMedium import com.topjohnwu.magisk.extensions.toTime import com.topjohnwu.magisk.extensions.toggle import com.topjohnwu.magisk.model.entity.MagiskLog import com.topjohnwu.magisk.model.entity.WrappedMagiskLog +import com.topjohnwu.magisk.utils.DiffObservableList +import com.topjohnwu.magisk.utils.KObservableField class LogRvItem : ComparableRvItem() { override val layoutRes: Int = R.layout.item_page_log diff --git a/app/src/main/java/com/topjohnwu/magisk/model/entity/recycler/ModuleRvItem.kt b/app/src/main/java/com/topjohnwu/magisk/model/entity/recycler/ModuleRvItem.kt index 57caade72..1af52be7b 100644 --- a/app/src/main/java/com/topjohnwu/magisk/model/entity/recycler/ModuleRvItem.kt +++ b/app/src/main/java/com/topjohnwu/magisk/model/entity/recycler/ModuleRvItem.kt @@ -2,14 +2,14 @@ package com.topjohnwu.magisk.model.entity.recycler import android.content.res.Resources import androidx.annotation.StringRes -import com.skoumal.teanity.databinding.ComparableRvItem -import com.skoumal.teanity.extensions.addOnPropertyChangedCallback -import com.skoumal.teanity.util.KObservableField import com.topjohnwu.magisk.R +import com.topjohnwu.magisk.databinding.ComparableRvItem +import com.topjohnwu.magisk.extensions.addOnPropertyChangedCallback import com.topjohnwu.magisk.extensions.get import com.topjohnwu.magisk.extensions.toggle import com.topjohnwu.magisk.model.entity.module.Module import com.topjohnwu.magisk.model.entity.module.Repo +import com.topjohnwu.magisk.utils.KObservableField class ModuleRvItem(val item: Module) : ComparableRvItem() { diff --git a/app/src/main/java/com/topjohnwu/magisk/model/entity/recycler/PolicyRvItem.kt b/app/src/main/java/com/topjohnwu/magisk/model/entity/recycler/PolicyRvItem.kt index e72437fe6..5006ef115 100644 --- a/app/src/main/java/com/topjohnwu/magisk/model/entity/recycler/PolicyRvItem.kt +++ b/app/src/main/java/com/topjohnwu/magisk/model/entity/recycler/PolicyRvItem.kt @@ -1,16 +1,16 @@ package com.topjohnwu.magisk.model.entity.recycler import android.graphics.drawable.Drawable -import com.skoumal.teanity.databinding.ComparableRvItem -import com.skoumal.teanity.extensions.addOnPropertyChangedCallback -import com.skoumal.teanity.rxbus.RxBus -import com.skoumal.teanity.util.KObservableField +import com.topjohnwu.magisk.utils.RxBus import com.topjohnwu.magisk.R +import com.topjohnwu.magisk.databinding.ComparableRvItem +import com.topjohnwu.magisk.extensions.addOnPropertyChangedCallback import com.topjohnwu.magisk.extensions.inject import com.topjohnwu.magisk.extensions.toggle import com.topjohnwu.magisk.model.entity.MagiskPolicy import com.topjohnwu.magisk.model.events.PolicyEnableEvent import com.topjohnwu.magisk.model.events.PolicyUpdateEvent +import com.topjohnwu.magisk.utils.KObservableField class PolicyRvItem(val item: MagiskPolicy, val icon: Drawable) : ComparableRvItem() { diff --git a/app/src/main/java/com/topjohnwu/magisk/model/entity/recycler/SectionRvItem.kt b/app/src/main/java/com/topjohnwu/magisk/model/entity/recycler/SectionRvItem.kt index 11fd9ebe3..9aad6d59e 100644 --- a/app/src/main/java/com/topjohnwu/magisk/model/entity/recycler/SectionRvItem.kt +++ b/app/src/main/java/com/topjohnwu/magisk/model/entity/recycler/SectionRvItem.kt @@ -1,7 +1,7 @@ package com.topjohnwu.magisk.model.entity.recycler -import com.skoumal.teanity.databinding.ComparableRvItem import com.topjohnwu.magisk.R +import com.topjohnwu.magisk.databinding.ComparableRvItem class SectionRvItem(val text: String) : ComparableRvItem() { override val layoutRes: Int = R.layout.item_section diff --git a/app/src/main/java/com/topjohnwu/magisk/model/entity/recycler/SpinnerRvItem.kt b/app/src/main/java/com/topjohnwu/magisk/model/entity/recycler/SpinnerRvItem.kt index 92dcbf08f..7a69f924e 100644 --- a/app/src/main/java/com/topjohnwu/magisk/model/entity/recycler/SpinnerRvItem.kt +++ b/app/src/main/java/com/topjohnwu/magisk/model/entity/recycler/SpinnerRvItem.kt @@ -1,7 +1,7 @@ package com.topjohnwu.magisk.model.entity.recycler -import com.skoumal.teanity.databinding.ComparableRvItem import com.topjohnwu.magisk.R +import com.topjohnwu.magisk.databinding.ComparableRvItem class SpinnerRvItem(val item: String) : ComparableRvItem() { diff --git a/app/src/main/java/com/topjohnwu/magisk/model/events/EventHandler.kt b/app/src/main/java/com/topjohnwu/magisk/model/events/EventHandler.kt new file mode 100644 index 000000000..3823881cb --- /dev/null +++ b/app/src/main/java/com/topjohnwu/magisk/model/events/EventHandler.kt @@ -0,0 +1,18 @@ +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) {} +} diff --git a/app/src/main/java/com/topjohnwu/magisk/model/events/RxEvents.kt b/app/src/main/java/com/topjohnwu/magisk/model/events/RxEvents.kt index 5f6335bfe..b03814b54 100644 --- a/app/src/main/java/com/topjohnwu/magisk/model/events/RxEvents.kt +++ b/app/src/main/java/com/topjohnwu/magisk/model/events/RxEvents.kt @@ -1,6 +1,6 @@ package com.topjohnwu.magisk.model.events -import com.skoumal.teanity.rxbus.RxBus +import com.topjohnwu.magisk.utils.RxBus import com.topjohnwu.magisk.model.entity.MagiskPolicy import com.topjohnwu.magisk.model.entity.recycler.HideProcessRvItem import com.topjohnwu.magisk.model.entity.recycler.ModuleRvItem diff --git a/app/src/main/java/com/topjohnwu/magisk/model/events/SimpleViewEvent.kt b/app/src/main/java/com/topjohnwu/magisk/model/events/SimpleViewEvent.kt new file mode 100644 index 000000000..8b0b22bdd --- /dev/null +++ b/app/src/main/java/com/topjohnwu/magisk/model/events/SimpleViewEvent.kt @@ -0,0 +1,7 @@ +package com.topjohnwu.magisk.model.events + +import com.topjohnwu.magisk.model.events.ViewEvent + +class SimpleViewEvent( + val event: Int +) : ViewEvent() \ No newline at end of file diff --git a/app/src/main/java/com/topjohnwu/magisk/model/events/SnackbarEvent.kt b/app/src/main/java/com/topjohnwu/magisk/model/events/SnackbarEvent.kt new file mode 100644 index 000000000..59ee28af2 --- /dev/null +++ b/app/src/main/java/com/topjohnwu/magisk/model/events/SnackbarEvent.kt @@ -0,0 +1,28 @@ +package com.topjohnwu.magisk.model.events + +import android.content.Context +import androidx.annotation.StringRes +import com.google.android.material.snackbar.Snackbar +import com.topjohnwu.magisk.model.events.ViewEvent + +class SnackbarEvent private constructor( + @StringRes private val messageRes: Int, + private val messageString: String?, + val length: Int, + val f: Snackbar.() -> Unit +) : ViewEvent() { + + constructor( + @StringRes messageRes: Int, + length: Int = Snackbar.LENGTH_SHORT, + f: Snackbar.() -> Unit = {} + ) : this(messageRes, null, length, f) + + constructor( + message: String, + length: Int = Snackbar.LENGTH_SHORT, + f: Snackbar.() -> Unit = {} + ) : this(-1, message, length, f) + + fun message(context: Context): String = messageString ?: context.getString(messageRes) +} diff --git a/app/src/main/java/com/topjohnwu/magisk/model/events/ViewEventObserver.kt b/app/src/main/java/com/topjohnwu/magisk/model/events/ViewEventObserver.kt new file mode 100644 index 000000000..7f98f966b --- /dev/null +++ b/app/src/main/java/com/topjohnwu/magisk/model/events/ViewEventObserver.kt @@ -0,0 +1,17 @@ +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 { + override fun onChanged(event: ViewEvent?) { + event?.let { + if (!it.handled) { + it.handled = true + onEventUnhandled(it) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/topjohnwu/magisk/model/events/ViewEvents.kt b/app/src/main/java/com/topjohnwu/magisk/model/events/ViewEvents.kt index 206e765d1..f8e273bd7 100644 --- a/app/src/main/java/com/topjohnwu/magisk/model/events/ViewEvents.kt +++ b/app/src/main/java/com/topjohnwu/magisk/model/events/ViewEvents.kt @@ -1,10 +1,19 @@ package com.topjohnwu.magisk.model.events import android.app.Activity -import com.skoumal.teanity.viewevents.ViewEvent import com.topjohnwu.magisk.model.entity.module.Repo import io.reactivex.subjects.PublishSubject +/** + * 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 +} data class OpenLinkEvent(val url: String) : ViewEvent() @@ -35,4 +44,4 @@ class PermissionEvent( class BackPressEvent : ViewEvent() -class DieEvent : ViewEvent() \ No newline at end of file +class DieEvent : ViewEvent() diff --git a/app/src/main/java/com/topjohnwu/magisk/model/navigation/MagiskNavigationEvent.kt b/app/src/main/java/com/topjohnwu/magisk/model/navigation/MagiskNavigationEvent.kt index 793c2a4c2..fb11e1ad0 100644 --- a/app/src/main/java/com/topjohnwu/magisk/model/navigation/MagiskNavigationEvent.kt +++ b/app/src/main/java/com/topjohnwu/magisk/model/navigation/MagiskNavigationEvent.kt @@ -4,10 +4,12 @@ import android.os.Bundle import androidx.annotation.AnimRes import androidx.annotation.AnimatorRes import androidx.fragment.app.Fragment -import com.skoumal.teanity.viewevents.NavigationDslMarker -import com.skoumal.teanity.viewevents.ViewEvent +import com.topjohnwu.magisk.model.events.ViewEvent import kotlin.reflect.KClass +@DslMarker +annotation class NavigationDslMarker + class MagiskNavigationEvent( val navDirections: MagiskNavDirectionsBuilder, val navOptions: MagiskNavOptions, diff --git a/app/src/main/java/com/topjohnwu/magisk/tasks/FlashZip.kt b/app/src/main/java/com/topjohnwu/magisk/tasks/FlashZip.kt index 3ae10b1a3..2e9e9ac36 100644 --- a/app/src/main/java/com/topjohnwu/magisk/tasks/FlashZip.kt +++ b/app/src/main/java/com/topjohnwu/magisk/tasks/FlashZip.kt @@ -2,11 +2,11 @@ package com.topjohnwu.magisk.tasks import android.content.Context import android.net.Uri -import com.skoumal.teanity.extensions.subscribeK import com.topjohnwu.magisk.Const import com.topjohnwu.magisk.extensions.fileName import com.topjohnwu.magisk.extensions.inject import com.topjohnwu.magisk.extensions.readUri +import com.topjohnwu.magisk.extensions.subscribeK import com.topjohnwu.magisk.utils.unzip import com.topjohnwu.superuser.Shell import io.reactivex.Single diff --git a/app/src/main/java/com/topjohnwu/magisk/tasks/MagiskInstaller.kt b/app/src/main/java/com/topjohnwu/magisk/tasks/MagiskInstaller.kt index 9ef1c8d72..9547adfb2 100644 --- a/app/src/main/java/com/topjohnwu/magisk/tasks/MagiskInstaller.kt +++ b/app/src/main/java/com/topjohnwu/magisk/tasks/MagiskInstaller.kt @@ -6,7 +6,6 @@ import android.os.Build import android.text.TextUtils import androidx.annotation.MainThread import androidx.annotation.WorkerThread -import com.skoumal.teanity.extensions.subscribeK import com.topjohnwu.magisk.Config import com.topjohnwu.magisk.Info import com.topjohnwu.magisk.data.network.GithubRawServices diff --git a/app/src/main/java/com/topjohnwu/magisk/ui/MainActivity.kt b/app/src/main/java/com/topjohnwu/magisk/ui/MainActivity.kt index 86d13c915..a4c623faa 100644 --- a/app/src/main/java/com/topjohnwu/magisk/ui/MainActivity.kt +++ b/app/src/main/java/com/topjohnwu/magisk/ui/MainActivity.kt @@ -4,13 +4,13 @@ import android.content.Intent import android.os.Bundle import androidx.core.view.GravityCompat import androidx.fragment.app.Fragment -import com.skoumal.teanity.extensions.addOnPropertyChangedCallback import com.topjohnwu.magisk.ClassMap import com.topjohnwu.magisk.Config import com.topjohnwu.magisk.Const.Key.OPEN_SECTION import com.topjohnwu.magisk.Info import com.topjohnwu.magisk.R import com.topjohnwu.magisk.databinding.ActivityMainBinding +import com.topjohnwu.magisk.extensions.addOnPropertyChangedCallback import com.topjohnwu.magisk.model.navigation.Navigation import com.topjohnwu.magisk.ui.base.MagiskActivity import com.topjohnwu.magisk.ui.hide.MagiskHideFragment @@ -43,10 +43,6 @@ open class MainActivity : MagiskActivity() { SettingsFragment::class ) - /*override fun getDarkTheme(): Int { - return R.style.AppTheme_Dark - }*/ - override fun onCreate(savedInstanceState: Bundle?) { if (!SplashActivity.DONE) { startActivity(Intent(this, ClassMap[SplashActivity::class.java])) diff --git a/app/src/main/java/com/topjohnwu/magisk/ui/base/MagiskActivity.kt b/app/src/main/java/com/topjohnwu/magisk/ui/base/MagiskActivity.kt index f47d314db..797c99a0f 100644 --- a/app/src/main/java/com/topjohnwu/magisk/ui/base/MagiskActivity.kt +++ b/app/src/main/java/com/topjohnwu/magisk/ui/base/MagiskActivity.kt @@ -6,9 +6,11 @@ import android.content.Intent import android.content.res.Configuration import android.os.Bundle import androidx.annotation.CallSuper +import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatDelegate import androidx.collection.SparseArrayCompat import androidx.core.net.toUri +import androidx.databinding.DataBindingUtil import androidx.databinding.ViewDataBinding import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentTransaction @@ -19,13 +21,12 @@ import com.karumi.dexter.listener.PermissionRequest import com.karumi.dexter.listener.multi.MultiplePermissionsListener import com.ncapdevi.fragnav.FragNavController import com.ncapdevi.fragnav.FragNavTransactionOptions -import com.skoumal.teanity.view.TeanityActivity -import com.skoumal.teanity.viewevents.ViewEvent +import com.topjohnwu.magisk.model.events.EventHandler +import com.topjohnwu.magisk.BR import com.topjohnwu.magisk.Config import com.topjohnwu.magisk.extensions.set -import com.topjohnwu.magisk.model.events.BackPressEvent -import com.topjohnwu.magisk.model.events.PermissionEvent -import com.topjohnwu.magisk.model.events.ViewActionEvent +import com.topjohnwu.magisk.extensions.snackbar +import com.topjohnwu.magisk.model.events.* import com.topjohnwu.magisk.model.navigation.MagiskAnimBuilder import com.topjohnwu.magisk.model.navigation.MagiskNavigationEvent import com.topjohnwu.magisk.model.navigation.Navigator @@ -39,17 +40,26 @@ import kotlin.reflect.KClass typealias RequestCallback = MagiskActivity<*, *>.(Int, Intent?) -> Unit abstract class MagiskActivity : - TeanityActivity(), FragNavController.RootFragmentListener, + AppCompatActivity(), FragNavController.RootFragmentListener, EventHandler, Navigator, FragNavController.TransactionListener { - override val numberOfRootFragments: Int get() = baseFragments.size - override val baseFragments: List> = listOf() - private val resultCallbacks = SparseArrayCompat() + protected lateinit var binding: Binding + protected abstract val layoutRes: Int + protected abstract val viewModel: ViewModel + protected open val snackbarView get() = binding.root + protected open val navHostId: Int = 0 + private val viewEventObserver = ViewEventObserver { + onEventDispatched(it) + if (it is SimpleViewEvent) { + onSimpleEventDispatched(it.event) + } + } + private val resultCallbacks = SparseArrayCompat() protected open val defaultPosition: Int = 0 - protected val navigationController get() = if (navHostId == 0) null else _navigationController + private val navigationController get() = if (navHostId == 0) null else _navigationController private val _navigationController by lazy { if (navHostId == 0) throw IllegalStateException("Did you forget to override \"navHostId\"?") FragNavController(supportFragmentManager, navHostId) @@ -58,6 +68,9 @@ abstract class MagiskActivity> = listOf() + init { val theme = if (Config.darkTheme) { AppCompatDelegate.MODE_NIGHT_YES @@ -79,6 +92,14 @@ abstract class MagiskActivity(this, layoutRes).apply { + setVariable(BR.viewModel, viewModel) + lifecycleOwner = this@MagiskActivity + } + + viewModel.viewEvents.observe(this, viewEventObserver) + navigationController?.apply { rootFragmentListener = this@MagiskActivity transactionListener = this@MagiskActivity @@ -95,6 +116,7 @@ abstract class MagiskActivity snackbar(snackbarView, event.message(this), event.length, event.f) is BackPressEvent -> onBackPressed() is MagiskNavigationEvent -> navigateTo(event) is ViewActionEvent -> event.action(this) @@ -230,11 +252,7 @@ abstract class MagiskActivity : - TeanityFragment(), Navigator { + Fragment(), Navigator, EventHandler { protected val activity get() = requireActivity() as MagiskActivity<*, *> + protected lateinit var binding: Binding + protected abstract val layoutRes: Int + protected abstract val viewModel: ViewModel + protected open val snackbarView get() = binding.root + protected val navController get() = binding.root.findNavController() + private val viewEventObserver = ViewEventObserver { + onEventDispatched(it) + if (it is SimpleViewEvent) { + onSimpleEventDispatched(it.event) + } + } // We don't need nested fragments override val baseFragments: List> = listOf() override fun navigateTo(event: MagiskNavigationEvent) = activity.navigateTo(event) + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + viewModel.viewEvents.observe(this, viewEventObserver) + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + binding = DataBindingUtil.inflate(inflater, layoutRes, container, false).apply { + setVariable(BR.viewModel, viewModel) + lifecycleOwner = this@MagiskFragment + } + + return binding.root + } + @CallSuper override fun onEventDispatched(event: ViewEvent) { super.onEventDispatched(event) when (event) { + is SnackbarEvent -> snackbar(snackbarView, event.message(requireContext()), event.length, event.f) is BackPressEvent -> activity.onBackPressed() is MagiskNavigationEvent -> navigateTo(event) is ViewActionEvent -> event.action(requireActivity()) diff --git a/app/src/main/java/com/topjohnwu/magisk/ui/base/MagiskViewModel.kt b/app/src/main/java/com/topjohnwu/magisk/ui/base/MagiskViewModel.kt index c1906cbd9..2cee6a32f 100644 --- a/app/src/main/java/com/topjohnwu/magisk/ui/base/MagiskViewModel.kt +++ b/app/src/main/java/com/topjohnwu/magisk/ui/base/MagiskViewModel.kt @@ -2,14 +2,14 @@ package com.topjohnwu.magisk.ui.base import android.app.Activity import com.github.pwittchen.reactivenetwork.library.rx2.ReactiveNetwork -import com.skoumal.teanity.extensions.doOnSubscribeUi -import com.skoumal.teanity.extensions.subscribeK -import com.skoumal.teanity.util.KObservableField -import com.skoumal.teanity.viewmodel.LoadingViewModel +import com.topjohnwu.magisk.extensions.doOnSubscribeUi import com.topjohnwu.magisk.extensions.get +import com.topjohnwu.magisk.extensions.subscribeK import com.topjohnwu.magisk.model.events.BackPressEvent import com.topjohnwu.magisk.model.events.PermissionEvent import com.topjohnwu.magisk.model.events.ViewActionEvent +import com.topjohnwu.magisk.utils.KObservableField +import com.topjohnwu.magisk.viewmodel.LoadingViewModel import io.reactivex.Observable import io.reactivex.subjects.PublishSubject diff --git a/app/src/main/java/com/topjohnwu/magisk/ui/flash/FlashViewModel.kt b/app/src/main/java/com/topjohnwu/magisk/ui/flash/FlashViewModel.kt index 92fe4210c..c51c2883d 100644 --- a/app/src/main/java/com/topjohnwu/magisk/ui/flash/FlashViewModel.kt +++ b/app/src/main/java/com/topjohnwu/magisk/ui/flash/FlashViewModel.kt @@ -7,21 +7,20 @@ import android.net.Uri import android.os.Handler import androidx.core.os.postDelayed import androidx.databinding.ObservableArrayList -import com.skoumal.teanity.databinding.ComparableRvItem -import com.skoumal.teanity.extensions.subscribeK -import com.skoumal.teanity.util.DiffObservableList -import com.skoumal.teanity.util.KObservableField -import com.skoumal.teanity.viewevents.SnackbarEvent import com.topjohnwu.magisk.BR import com.topjohnwu.magisk.Config import com.topjohnwu.magisk.Const import com.topjohnwu.magisk.R +import com.topjohnwu.magisk.databinding.ComparableRvItem import com.topjohnwu.magisk.extensions.* import com.topjohnwu.magisk.model.entity.recycler.ConsoleRvItem +import com.topjohnwu.magisk.model.events.SnackbarEvent import com.topjohnwu.magisk.model.flash.FlashResultListener import com.topjohnwu.magisk.model.flash.Flashing import com.topjohnwu.magisk.model.flash.Patching import com.topjohnwu.magisk.ui.base.MagiskViewModel +import com.topjohnwu.magisk.utils.DiffObservableList +import com.topjohnwu.magisk.utils.KObservableField import com.topjohnwu.superuser.Shell import me.tatarka.bindingcollectionadapter2.ItemBinding import java.io.File diff --git a/app/src/main/java/com/topjohnwu/magisk/ui/hide/HideViewModel.kt b/app/src/main/java/com/topjohnwu/magisk/ui/hide/HideViewModel.kt index 5e5a08992..762db9667 100644 --- a/app/src/main/java/com/topjohnwu/magisk/ui/hide/HideViewModel.kt +++ b/app/src/main/java/com/topjohnwu/magisk/ui/hide/HideViewModel.kt @@ -1,14 +1,12 @@ package com.topjohnwu.magisk.ui.hide import android.content.pm.ApplicationInfo -import com.skoumal.teanity.databinding.ComparableRvItem -import com.skoumal.teanity.extensions.addOnPropertyChangedCallback -import com.skoumal.teanity.extensions.subscribeK -import com.skoumal.teanity.rxbus.RxBus -import com.skoumal.teanity.util.DiffObservableList -import com.skoumal.teanity.util.KObservableField +import com.topjohnwu.magisk.utils.RxBus import com.topjohnwu.magisk.BR import com.topjohnwu.magisk.data.repository.MagiskRepository +import com.topjohnwu.magisk.databinding.ComparableRvItem +import com.topjohnwu.magisk.extensions.addOnPropertyChangedCallback +import com.topjohnwu.magisk.extensions.subscribeK import com.topjohnwu.magisk.extensions.toSingle import com.topjohnwu.magisk.extensions.update import com.topjohnwu.magisk.model.entity.recycler.HideProcessRvItem @@ -16,6 +14,8 @@ import com.topjohnwu.magisk.model.entity.recycler.HideRvItem import com.topjohnwu.magisk.model.entity.state.IndeterminateState import com.topjohnwu.magisk.model.events.HideProcessEvent import com.topjohnwu.magisk.ui.base.MagiskViewModel +import com.topjohnwu.magisk.utils.DiffObservableList +import com.topjohnwu.magisk.utils.KObservableField import me.tatarka.bindingcollectionadapter2.OnItemBind import timber.log.Timber diff --git a/app/src/main/java/com/topjohnwu/magisk/ui/home/HomeFragment.kt b/app/src/main/java/com/topjohnwu/magisk/ui/home/HomeFragment.kt index 3d5ca84fa..2794205b6 100644 --- a/app/src/main/java/com/topjohnwu/magisk/ui/home/HomeFragment.kt +++ b/app/src/main/java/com/topjohnwu/magisk/ui/home/HomeFragment.kt @@ -1,8 +1,7 @@ package com.topjohnwu.magisk.ui.home import android.content.Context -import com.skoumal.teanity.extensions.subscribeK -import com.skoumal.teanity.viewevents.ViewEvent +import com.topjohnwu.magisk.model.events.ViewEvent import com.topjohnwu.magisk.BuildConfig import com.topjohnwu.magisk.Const import com.topjohnwu.magisk.Info @@ -10,6 +9,7 @@ import com.topjohnwu.magisk.R import com.topjohnwu.magisk.data.repository.MagiskRepository import com.topjohnwu.magisk.databinding.FragmentMagiskBinding import com.topjohnwu.magisk.extensions.inject +import com.topjohnwu.magisk.extensions.subscribeK import com.topjohnwu.magisk.extensions.writeTo import com.topjohnwu.magisk.model.events.* import com.topjohnwu.magisk.ui.base.MagiskActivity diff --git a/app/src/main/java/com/topjohnwu/magisk/ui/home/HomeViewModel.kt b/app/src/main/java/com/topjohnwu/magisk/ui/home/HomeViewModel.kt index 2c98a4190..6b6c902f2 100644 --- a/app/src/main/java/com/topjohnwu/magisk/ui/home/HomeViewModel.kt +++ b/app/src/main/java/com/topjohnwu/magisk/ui/home/HomeViewModel.kt @@ -1,19 +1,13 @@ package com.topjohnwu.magisk.ui.home import android.content.pm.PackageManager -import com.skoumal.teanity.extensions.addOnPropertyChangedCallback -import com.skoumal.teanity.extensions.doOnSubscribeUi -import com.skoumal.teanity.extensions.subscribeK -import com.skoumal.teanity.util.KObservableField import com.topjohnwu.magisk.* import com.topjohnwu.magisk.data.repository.MagiskRepository -import com.topjohnwu.magisk.extensions.get -import com.topjohnwu.magisk.extensions.packageName -import com.topjohnwu.magisk.extensions.res -import com.topjohnwu.magisk.extensions.toggle +import com.topjohnwu.magisk.extensions.* import com.topjohnwu.magisk.model.events.* import com.topjohnwu.magisk.model.observer.Observer import com.topjohnwu.magisk.ui.base.MagiskViewModel +import com.topjohnwu.magisk.utils.KObservableField import com.topjohnwu.magisk.utils.SafetyNetHelper import com.topjohnwu.superuser.Shell diff --git a/app/src/main/java/com/topjohnwu/magisk/ui/log/LogFragment.kt b/app/src/main/java/com/topjohnwu/magisk/ui/log/LogFragment.kt index 80c98c253..e0fb8aef7 100644 --- a/app/src/main/java/com/topjohnwu/magisk/ui/log/LogFragment.kt +++ b/app/src/main/java/com/topjohnwu/magisk/ui/log/LogFragment.kt @@ -6,7 +6,7 @@ import android.view.Menu import android.view.MenuInflater import android.view.MenuItem import android.view.View -import com.skoumal.teanity.viewevents.ViewEvent +import com.topjohnwu.magisk.model.events.ViewEvent import com.topjohnwu.magisk.R import com.topjohnwu.magisk.databinding.FragmentLogBinding import com.topjohnwu.magisk.model.events.PageChangedEvent diff --git a/app/src/main/java/com/topjohnwu/magisk/ui/log/LogViewModel.kt b/app/src/main/java/com/topjohnwu/magisk/ui/log/LogViewModel.kt index 279ac5036..6493d5fde 100644 --- a/app/src/main/java/com/topjohnwu/magisk/ui/log/LogViewModel.kt +++ b/app/src/main/java/com/topjohnwu/magisk/ui/log/LogViewModel.kt @@ -1,18 +1,16 @@ package com.topjohnwu.magisk.ui.log import android.content.res.Resources -import com.skoumal.teanity.databinding.ComparableRvItem -import com.skoumal.teanity.extensions.addOnPropertyChangedCallback -import com.skoumal.teanity.extensions.doOnSubscribeUi -import com.skoumal.teanity.extensions.subscribeK -import com.skoumal.teanity.util.DiffObservableList -import com.skoumal.teanity.util.KObservableField -import com.skoumal.teanity.viewevents.SnackbarEvent +import com.topjohnwu.magisk.model.events.SnackbarEvent import com.topjohnwu.magisk.BR import com.topjohnwu.magisk.Config import com.topjohnwu.magisk.Const import com.topjohnwu.magisk.R import com.topjohnwu.magisk.data.repository.LogRepository +import com.topjohnwu.magisk.databinding.ComparableRvItem +import com.topjohnwu.magisk.extensions.addOnPropertyChangedCallback +import com.topjohnwu.magisk.extensions.doOnSubscribeUi +import com.topjohnwu.magisk.extensions.subscribeK import com.topjohnwu.magisk.model.binding.BindingAdapter import com.topjohnwu.magisk.model.entity.recycler.ConsoleRvItem import com.topjohnwu.magisk.model.entity.recycler.LogItemRvItem @@ -20,6 +18,8 @@ import com.topjohnwu.magisk.model.entity.recycler.LogRvItem import com.topjohnwu.magisk.model.entity.recycler.MagiskLogRvItem import com.topjohnwu.magisk.model.events.PageChangedEvent import com.topjohnwu.magisk.ui.base.MagiskViewModel +import com.topjohnwu.magisk.utils.DiffObservableList +import com.topjohnwu.magisk.utils.KObservableField import com.topjohnwu.superuser.Shell import me.tatarka.bindingcollectionadapter2.BindingViewPagerAdapter import me.tatarka.bindingcollectionadapter2.OnItemBind diff --git a/app/src/main/java/com/topjohnwu/magisk/ui/module/ModuleViewModel.kt b/app/src/main/java/com/topjohnwu/magisk/ui/module/ModuleViewModel.kt index f83af2c5a..35d74aefd 100644 --- a/app/src/main/java/com/topjohnwu/magisk/ui/module/ModuleViewModel.kt +++ b/app/src/main/java/com/topjohnwu/magisk/ui/module/ModuleViewModel.kt @@ -1,17 +1,11 @@ package com.topjohnwu.magisk.ui.module import android.content.res.Resources -import com.skoumal.teanity.databinding.ComparableRvItem -import com.skoumal.teanity.extensions.addOnPropertyChangedCallback -import com.skoumal.teanity.extensions.doOnSuccessUi -import com.skoumal.teanity.extensions.subscribeK -import com.skoumal.teanity.util.DiffObservableList -import com.skoumal.teanity.util.KObservableField import com.topjohnwu.magisk.BR import com.topjohnwu.magisk.R import com.topjohnwu.magisk.data.database.RepoDao -import com.topjohnwu.magisk.extensions.toSingle -import com.topjohnwu.magisk.extensions.update +import com.topjohnwu.magisk.databinding.ComparableRvItem +import com.topjohnwu.magisk.extensions.* import com.topjohnwu.magisk.model.entity.module.Module import com.topjohnwu.magisk.model.entity.recycler.ModuleRvItem import com.topjohnwu.magisk.model.entity.recycler.RepoRvItem @@ -21,6 +15,8 @@ import com.topjohnwu.magisk.model.events.OpenChangelogEvent import com.topjohnwu.magisk.model.events.OpenFilePickerEvent import com.topjohnwu.magisk.tasks.RepoUpdater import com.topjohnwu.magisk.ui.base.MagiskViewModel +import com.topjohnwu.magisk.utils.DiffObservableList +import com.topjohnwu.magisk.utils.KObservableField import io.reactivex.Single import io.reactivex.disposables.Disposable import me.tatarka.bindingcollectionadapter2.OnItemBind diff --git a/app/src/main/java/com/topjohnwu/magisk/ui/module/ModulesFragment.kt b/app/src/main/java/com/topjohnwu/magisk/ui/module/ModulesFragment.kt index 17a1e16fa..7c9799abb 100644 --- a/app/src/main/java/com/topjohnwu/magisk/ui/module/ModulesFragment.kt +++ b/app/src/main/java/com/topjohnwu/magisk/ui/module/ModulesFragment.kt @@ -8,7 +8,7 @@ import android.view.MenuInflater import android.view.MenuItem import android.view.View import androidx.recyclerview.widget.RecyclerView -import com.skoumal.teanity.viewevents.ViewEvent +import com.topjohnwu.magisk.model.events.ViewEvent import com.topjohnwu.magisk.ClassMap import com.topjohnwu.magisk.Const import com.topjohnwu.magisk.R diff --git a/app/src/main/java/com/topjohnwu/magisk/ui/module/ReposFragment.kt b/app/src/main/java/com/topjohnwu/magisk/ui/module/ReposFragment.kt index 6fa93d20a..c10d0f567 100644 --- a/app/src/main/java/com/topjohnwu/magisk/ui/module/ReposFragment.kt +++ b/app/src/main/java/com/topjohnwu/magisk/ui/module/ReposFragment.kt @@ -6,7 +6,7 @@ import android.view.Menu import android.view.MenuInflater import android.view.MenuItem import android.widget.SearchView -import com.skoumal.teanity.viewevents.ViewEvent +import com.topjohnwu.magisk.model.events.ViewEvent import com.topjohnwu.magisk.Config import com.topjohnwu.magisk.R import com.topjohnwu.magisk.databinding.FragmentReposBinding diff --git a/app/src/main/java/com/topjohnwu/magisk/ui/settings/SettingsFragment.kt b/app/src/main/java/com/topjohnwu/magisk/ui/settings/SettingsFragment.kt index 0f5f82aba..ac11098f0 100644 --- a/app/src/main/java/com/topjohnwu/magisk/ui/settings/SettingsFragment.kt +++ b/app/src/main/java/com/topjohnwu/magisk/ui/settings/SettingsFragment.kt @@ -13,14 +13,13 @@ import androidx.preference.ListPreference import androidx.preference.Preference import androidx.preference.PreferenceCategory import androidx.preference.SwitchPreferenceCompat -import com.skoumal.teanity.extensions.subscribeK -import com.skoumal.teanity.util.KObservableField import com.topjohnwu.magisk.BuildConfig import com.topjohnwu.magisk.Config import com.topjohnwu.magisk.Const import com.topjohnwu.magisk.R import com.topjohnwu.magisk.data.database.RepoDao import com.topjohnwu.magisk.databinding.CustomDownloadDialogBinding +import com.topjohnwu.magisk.extensions.subscribeK import com.topjohnwu.magisk.extensions.toLangTag import com.topjohnwu.magisk.model.download.DownloadService import com.topjohnwu.magisk.model.entity.internal.Configuration diff --git a/app/src/main/java/com/topjohnwu/magisk/ui/superuser/SuperuserViewModel.kt b/app/src/main/java/com/topjohnwu/magisk/ui/superuser/SuperuserViewModel.kt index 954696716..be17f49bb 100644 --- a/app/src/main/java/com/topjohnwu/magisk/ui/superuser/SuperuserViewModel.kt +++ b/app/src/main/java/com/topjohnwu/magisk/ui/superuser/SuperuserViewModel.kt @@ -2,21 +2,21 @@ package com.topjohnwu.magisk.ui.superuser import android.content.pm.PackageManager import android.content.res.Resources -import com.skoumal.teanity.databinding.ComparableRvItem -import com.skoumal.teanity.extensions.applySchedulers -import com.skoumal.teanity.extensions.subscribeK -import com.skoumal.teanity.rxbus.RxBus -import com.skoumal.teanity.util.DiffObservableList -import com.skoumal.teanity.viewevents.SnackbarEvent +import com.topjohnwu.magisk.utils.RxBus +import com.topjohnwu.magisk.model.events.SnackbarEvent import com.topjohnwu.magisk.BR import com.topjohnwu.magisk.R import com.topjohnwu.magisk.data.database.PolicyDao +import com.topjohnwu.magisk.databinding.ComparableRvItem +import com.topjohnwu.magisk.extensions.applySchedulers +import com.topjohnwu.magisk.extensions.subscribeK import com.topjohnwu.magisk.extensions.toggle import com.topjohnwu.magisk.model.entity.MagiskPolicy import com.topjohnwu.magisk.model.entity.recycler.PolicyRvItem import com.topjohnwu.magisk.model.events.PolicyEnableEvent import com.topjohnwu.magisk.model.events.PolicyUpdateEvent import com.topjohnwu.magisk.ui.base.MagiskViewModel +import com.topjohnwu.magisk.utils.DiffObservableList import com.topjohnwu.magisk.utils.FingerprintHelper import com.topjohnwu.magisk.view.dialogs.CustomAlertDialog import com.topjohnwu.magisk.view.dialogs.FingerprintAuthDialog diff --git a/app/src/main/java/com/topjohnwu/magisk/ui/surequest/SuRequestActivity.kt b/app/src/main/java/com/topjohnwu/magisk/ui/surequest/SuRequestActivity.kt index dc4f54b09..3ad04fa96 100644 --- a/app/src/main/java/com/topjohnwu/magisk/ui/surequest/SuRequestActivity.kt +++ b/app/src/main/java/com/topjohnwu/magisk/ui/surequest/SuRequestActivity.kt @@ -5,7 +5,7 @@ import android.os.Build import android.os.Bundle import android.text.TextUtils import android.view.Window -import com.skoumal.teanity.viewevents.ViewEvent +import com.topjohnwu.magisk.model.events.ViewEvent import com.topjohnwu.magisk.R import com.topjohnwu.magisk.databinding.ActivityRequestBinding import com.topjohnwu.magisk.model.entity.MagiskPolicy diff --git a/app/src/main/java/com/topjohnwu/magisk/ui/surequest/SuRequestViewModel.kt b/app/src/main/java/com/topjohnwu/magisk/ui/surequest/SuRequestViewModel.kt index 6cb43d425..e47412747 100644 --- a/app/src/main/java/com/topjohnwu/magisk/ui/surequest/SuRequestViewModel.kt +++ b/app/src/main/java/com/topjohnwu/magisk/ui/surequest/SuRequestViewModel.kt @@ -9,21 +9,21 @@ import android.graphics.drawable.Drawable import android.hardware.fingerprint.FingerprintManager import android.os.CountDownTimer import android.text.TextUtils -import com.skoumal.teanity.databinding.ComparableRvItem -import com.skoumal.teanity.extensions.addOnPropertyChangedCallback -import com.skoumal.teanity.util.DiffObservableList -import com.skoumal.teanity.util.KObservableField import com.topjohnwu.magisk.BuildConfig import com.topjohnwu.magisk.Config import com.topjohnwu.magisk.R import com.topjohnwu.magisk.data.database.PolicyDao +import com.topjohnwu.magisk.databinding.ComparableRvItem +import com.topjohnwu.magisk.extensions.addOnPropertyChangedCallback import com.topjohnwu.magisk.extensions.now import com.topjohnwu.magisk.model.entity.MagiskPolicy import com.topjohnwu.magisk.model.entity.recycler.SpinnerRvItem import com.topjohnwu.magisk.model.entity.toPolicy import com.topjohnwu.magisk.model.events.DieEvent import com.topjohnwu.magisk.ui.base.MagiskViewModel +import com.topjohnwu.magisk.utils.DiffObservableList import com.topjohnwu.magisk.utils.FingerprintHelper +import com.topjohnwu.magisk.utils.KObservableField import com.topjohnwu.magisk.utils.SuConnector import me.tatarka.bindingcollectionadapter2.BindingListViewAdapter import me.tatarka.bindingcollectionadapter2.ItemBinding diff --git a/app/src/main/java/com/topjohnwu/magisk/utils/DataBindingAdapters.kt b/app/src/main/java/com/topjohnwu/magisk/utils/DataBindingAdapters.kt index ad61131f8..17b0a2552 100644 --- a/app/src/main/java/com/topjohnwu/magisk/utils/DataBindingAdapters.kt +++ b/app/src/main/java/com/topjohnwu/magisk/utils/DataBindingAdapters.kt @@ -16,9 +16,9 @@ import androidx.recyclerview.widget.RecyclerView import androidx.viewpager.widget.ViewPager import com.google.android.material.floatingactionbutton.FloatingActionButton import com.google.android.material.navigation.NavigationView -import com.skoumal.teanity.extensions.subscribeK import com.topjohnwu.magisk.R import com.topjohnwu.magisk.extensions.replaceRandomWithSpecial +import com.topjohnwu.magisk.extensions.subscribeK import com.topjohnwu.magisk.model.entity.state.IndeterminateState import io.reactivex.Observable import io.reactivex.disposables.Disposable diff --git a/app/src/main/java/com/topjohnwu/magisk/utils/DiffObservableList.kt b/app/src/main/java/com/topjohnwu/magisk/utils/DiffObservableList.kt new file mode 100644 index 000000000..c7c61d968 --- /dev/null +++ b/app/src/main/java/com/topjohnwu/magisk/utils/DiffObservableList.kt @@ -0,0 +1,234 @@ +package com.topjohnwu.magisk.utils + +import androidx.annotation.MainThread +import androidx.databinding.ListChangeRegistry +import androidx.databinding.ObservableList +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListUpdateCallback +import java.util.* +import kotlin.collections.ArrayList + +/** + * @param callback The callback that controls the behavior of the DiffObservableList. + * @param detectMoves True if DiffUtil should try to detect moved items, false otherwise. + */ +open class DiffObservableList( + private val callback: Callback, + private val detectMoves: Boolean = true +) : AbstractList(), ObservableList { + + private val LIST_LOCK = Object() + private var list: MutableList = ArrayList() + private val listeners = ListChangeRegistry() + private val listCallback = ObservableListUpdateCallback() + + override val size: Int get() = list.size + + /** + * Calculates the list of update operations that can convert this list into the given one. + * + * @param newItems The items that this list will be set to. + * @return A DiffResult that contains the information about the edit sequence to covert this + * list into the given one. + */ + fun calculateDiff(newItems: List): DiffUtil.DiffResult { + val frozenList = synchronized(LIST_LOCK) { + ArrayList(list) + } + return doCalculateDiff(frozenList, newItems) + } + + private fun doCalculateDiff(oldItems: List, newItems: List?): DiffUtil.DiffResult { + return DiffUtil.calculateDiff(object : DiffUtil.Callback() { + override fun getOldListSize() = oldItems.size + + override fun getNewListSize() = newItems?.size ?: 0 + + override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { + val oldItem = oldItems[oldItemPosition] + val newItem = newItems!![newItemPosition] + return callback.areItemsTheSame(oldItem, newItem) + } + + override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { + val oldItem = oldItems[oldItemPosition] + val newItem = newItems!![newItemPosition] + return callback.areContentsTheSame(oldItem, newItem) + } + }, detectMoves) + } + + /** + * Updates the contents of this list to the given one using the DiffResults to dispatch change + * notifications. + * + * @param newItems The items to set this list to. + * @param diffResult The diff results to dispatch change notifications. + */ + @MainThread + fun update(newItems: List, diffResult: DiffUtil.DiffResult) { + synchronized(LIST_LOCK) { + list = newItems.toMutableList() + } + diffResult.dispatchUpdatesTo(listCallback) + } + + /** + * Sets this list to the given items. This is a convenience method for calling [ ][.calculateDiff] followed by [.update]. + * + * + * **Warning!** If the lists are large this operation may be too slow for the main thread. In + * that case, you should call [.calculateDiff] on a background thread and then + * [.update] on the main thread. + * + * @param newItems The items to set this list to. + */ + @MainThread + fun update(newItems: List) { + val diffResult = doCalculateDiff(list, newItems) + list = newItems.toMutableList() + diffResult.dispatchUpdatesTo(listCallback) + } + + override fun addOnListChangedCallback(listener: ObservableList.OnListChangedCallback>) { + listeners.add(listener) + } + + override fun removeOnListChangedCallback(listener: ObservableList.OnListChangedCallback>) { + listeners.remove(listener) + } + + override fun get(index: Int): T { + return list[index] + } + + override fun add(element: T): Boolean { + list.add(element) + notifyAdd(size - 1, 1) + return true + } + + override fun add(index: Int, element: T) { + list.add(index, element) + notifyAdd(index, 1) + } + + override fun addAll(elements: Collection): Boolean { + val oldSize = size + val added = list.addAll(elements) + if (added) { + notifyAdd(oldSize, size - oldSize) + } + return added + } + + override fun addAll(index: Int, elements: Collection): Boolean { + val added = list.addAll(index, elements) + if (added) { + notifyAdd(index, elements.size) + } + return added + } + + override fun clear() { + val oldSize = size + list.clear() + if (oldSize != 0) { + notifyRemove(0, oldSize) + } + } + + override fun remove(element: T): Boolean { + val index = indexOf(element) + return if (index >= 0) { + removeAt(index) + true + } else { + false + } + } + + override fun removeAt(index: Int): T { + val element = list.removeAt(index) + notifyRemove(index, 1) + return element + } + + fun removeLast(): T? { + if (size > 0) { + val index = size - 1 + return removeAt(index) + } + return null + } + + override fun set(index: Int, element: T): T { + val old = list.set(index, element) + listeners.notifyChanged(this, index, 1) + return old + } + + private fun notifyAdd(start: Int, count: Int) { + listeners.notifyInserted(this, start, count) + } + + private fun notifyRemove(start: Int, count: Int) { + listeners.notifyRemoved(this, start, count) + } + + /** + * A Callback class used by DiffUtil while calculating the diff between two lists. + */ + interface Callback { + /** + * Called by the DiffUtil to decide whether two object represent the same Item. + * + * + * For example, if your items have unique ids, this method should check their id equality. + * + * @param oldItem The old item. + * @param newItem The new item. + * @return True if the two items represent the same object or false if they are different. + */ + fun areItemsTheSame(oldItem: T, newItem: T): Boolean + + /** + * Called by the DiffUtil when it wants to check whether two items have the same data. + * DiffUtil uses this information to detect if the contents of an item has changed. + * + * + * DiffUtil uses this method to check equality instead of [Object.equals] so + * that you can change its behavior depending on your UI. + * + * + * This method is called only if [.areItemsTheSame] returns `true` for + * these items. + * + * @param oldItem The old item. + * @param newItem The new item which replaces the old item. + * @return True if the contents of the items are the same or false if they are different. + */ + fun areContentsTheSame(oldItem: T, newItem: T): Boolean + } + + inner class ObservableListUpdateCallback : ListUpdateCallback { + override fun onChanged(position: Int, count: Int, payload: Any?) { + listeners.notifyChanged(this@DiffObservableList, position, count) + } + + override fun onMoved(fromPosition: Int, toPosition: Int) { + listeners.notifyMoved(this@DiffObservableList, fromPosition, toPosition, 1) + } + + override fun onInserted(position: Int, count: Int) { + modCount += 1 + listeners.notifyInserted(this@DiffObservableList, position, count) + } + + override fun onRemoved(position: Int, count: Int) { + modCount += 1 + listeners.notifyRemoved(this@DiffObservableList, position, count) + } + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/topjohnwu/magisk/utils/KItemDecoration.kt b/app/src/main/java/com/topjohnwu/magisk/utils/KItemDecoration.kt new file mode 100644 index 000000000..0fc5c4f5c --- /dev/null +++ b/app/src/main/java/com/topjohnwu/magisk/utils/KItemDecoration.kt @@ -0,0 +1,117 @@ +package com.topjohnwu.magisk.utils + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Rect +import android.graphics.drawable.Drawable +import android.view.View +import androidx.annotation.DrawableRes +import androidx.core.view.get +import androidx.recyclerview.widget.DividerItemDecoration +import androidx.recyclerview.widget.RecyclerView +import com.topjohnwu.magisk.extensions.drawableCompat + +class KItemDecoration( + private val context: Context, + @RecyclerView.Orientation private val orientation: Int +) : + RecyclerView.ItemDecoration() { + + private val bounds = Rect() + private var divider: Drawable? = null + var showAfterLast = true + + fun setDeco(@DrawableRes drawable: Int) = apply { + setDeco(context.drawableCompat(drawable)) + } + + fun setDeco(drawable: Drawable?) = apply { + divider = drawable + } + + override fun onDraw(canvas: Canvas, parent: RecyclerView, state: RecyclerView.State) { + parent.layoutManager ?: return + + divider?.let { + if (orientation == DividerItemDecoration.VERTICAL) { + drawVertical(canvas, parent, it) + } else { + drawHorizontal(canvas, parent, it) + } + } + } + + private fun drawVertical(canvas: Canvas, parent: RecyclerView, drawable: Drawable) { + canvas.save() + val left: Int + val right: Int + if (parent.clipToPadding) { + left = parent.paddingLeft + right = parent.width - parent.paddingRight + canvas.clipRect( + left, parent.paddingTop, right, + parent.height - parent.paddingBottom + ) + } else { + left = 0 + right = parent.width + } + + val to = if (showAfterLast) parent.childCount else parent.childCount - 1 + + (0 until to) + .map { parent[it] } + .forEach { child -> + parent.getDecoratedBoundsWithMargins(child, bounds) + val bottom = bounds.bottom + Math.round(child.translationY) + val top = bottom - drawable.intrinsicHeight + drawable.setBounds(left, top, right, bottom) + drawable.draw(canvas) + } + canvas.restore() + } + + private fun drawHorizontal(canvas: Canvas, parent: RecyclerView, drawable: Drawable) { + canvas.save() + val top: Int + val bottom: Int + if (parent.clipToPadding) { + top = parent.paddingTop + bottom = parent.height - parent.paddingBottom + canvas.clipRect( + parent.paddingLeft, top, + parent.width - parent.paddingRight, bottom + ) + } else { + top = 0 + bottom = parent.height + } + + val to = if (showAfterLast) parent.childCount else parent.childCount - 1 + + (0 until to) + .map { parent[it] } + .forEach { child -> + parent.layoutManager!!.getDecoratedBoundsWithMargins(child, bounds) + val right = bounds.right + Math.round(child.translationX) + val left = right - drawable.intrinsicWidth + drawable.setBounds(left, top, right, bottom) + drawable.draw(canvas) + } + canvas.restore() + } + + override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) { + if (parent.getChildAdapterPosition(view) == state.itemCount - 1) { + outRect.setEmpty() + return + } + + if (orientation == RecyclerView.VERTICAL) { + outRect.set(0, 0, 0, divider?.intrinsicHeight ?: 0) + } else { + outRect.set(0, 0, divider?.intrinsicWidth ?: 0, 0) + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/topjohnwu/magisk/utils/KObservableField.kt b/app/src/main/java/com/topjohnwu/magisk/utils/KObservableField.kt new file mode 100644 index 000000000..7ecf125f4 --- /dev/null +++ b/app/src/main/java/com/topjohnwu/magisk/utils/KObservableField.kt @@ -0,0 +1,49 @@ +package com.topjohnwu.magisk.utils + +import androidx.databinding.Observable +import androidx.databinding.ObservableField +import java.io.Serializable + +/** + * Kotlin version of [ObservableField]. + * You can define if wrapped type is Nullable or not. + * You can use kotlin get/set syntax for value + */ +class KObservableField : ObservableField, Serializable { + + var value: T + set(value) { + if (field != value) { + field = value + notifyChange() + } + } + + constructor(init: T) { + value = init + } + + constructor(init: T, vararg dependencies: Observable) : super(*dependencies) { + value = init + } + + @Deprecated( + message = "Needed for data binding, use KObservableField.value syntax from code", + replaceWith = ReplaceWith("value") + ) + override fun get(): T { + return value + } + + @Deprecated( + message = "Needed for data binding, use KObservableField.value = ... syntax from code", + replaceWith = ReplaceWith("value = newValue") + ) + override fun set(newValue: T) { + value = newValue + } + + override fun toString(): String { + return "KObservableField(value=$value)" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/topjohnwu/magisk/utils/PatchAPK.kt b/app/src/main/java/com/topjohnwu/magisk/utils/PatchAPK.kt index 74996f23a..5f45dc8a7 100644 --- a/app/src/main/java/com/topjohnwu/magisk/utils/PatchAPK.kt +++ b/app/src/main/java/com/topjohnwu/magisk/utils/PatchAPK.kt @@ -3,8 +3,8 @@ package com.topjohnwu.magisk.utils import android.content.ComponentName import android.content.Context import android.widget.Toast -import com.skoumal.teanity.extensions.subscribeK import com.topjohnwu.magisk.* +import com.topjohnwu.magisk.extensions.subscribeK import com.topjohnwu.magisk.ui.SplashActivity import com.topjohnwu.magisk.view.Notifications import com.topjohnwu.signing.JarMap diff --git a/app/src/main/java/com/topjohnwu/magisk/utils/RxBus.kt b/app/src/main/java/com/topjohnwu/magisk/utils/RxBus.kt new file mode 100644 index 000000000..c27d898b8 --- /dev/null +++ b/app/src/main/java/com/topjohnwu/magisk/utils/RxBus.kt @@ -0,0 +1,36 @@ +package com.topjohnwu.magisk.utils + +import io.reactivex.Observable +import io.reactivex.subjects.PublishSubject + +class RxBus { + + private val _bus = PublishSubject.create() + + val bus: Observable get() = _bus + + fun post(event: Event) { + _bus.onNext(event) + } + + fun post(event: Int) { + _bus.onNext(SimpleEvent(event)) + } + + inline fun register(noinline predicate: (T) -> Boolean = { true }): Observable { + return bus + .ofType(T::class.java) + .filter(predicate) + } + + fun register(eventId: Int): Observable { + return bus + .ofType(SimpleEvent::class.java) + .map { it.eventId } + .filter { it == eventId } + } + + interface Event + + private class SimpleEvent(val eventId: Int) : Event +} \ No newline at end of file diff --git a/app/src/main/java/com/topjohnwu/magisk/view/MagiskDialog.kt b/app/src/main/java/com/topjohnwu/magisk/view/MagiskDialog.kt deleted file mode 100644 index a9ad818ab..000000000 --- a/app/src/main/java/com/topjohnwu/magisk/view/MagiskDialog.kt +++ /dev/null @@ -1,155 +0,0 @@ -package com.topjohnwu.magisk.view - -import android.content.Context -import android.content.DialogInterface -import android.graphics.Color -import android.graphics.drawable.ColorDrawable -import android.graphics.drawable.Drawable -import android.os.Bundle -import android.view.LayoutInflater -import androidx.annotation.DrawableRes -import androidx.annotation.StringRes -import androidx.appcompat.app.AlertDialog -import androidx.databinding.DataBindingUtil -import androidx.databinding.ViewDataBinding -import com.skoumal.teanity.util.KObservableField -import com.topjohnwu.magisk.BR -import com.topjohnwu.magisk.R -import com.topjohnwu.magisk.databinding.DialogMagiskBaseBinding - -class MagiskDialog @JvmOverloads constructor( - context: Context, theme: Int = 0 -) : AlertDialog(context, theme) { - - private val binding: DialogMagiskBaseBinding - private val data = Data() - - init { - val layoutInflater = LayoutInflater.from(context) - binding = DataBindingUtil.inflate(layoutInflater, R.layout.dialog_magisk_base, null, false) - binding.setVariable(BR.data, data) - super.setView(binding.root) - } - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) - } - - inner class Data { - val icon = KObservableField(0) - val iconRaw = KObservableField(null) - val title = KObservableField("") - val message = KObservableField("") - - val buttonPositive = Button() - val buttonNeutral = Button() - val buttonNegative = Button() - val buttonIDGAF = Button() - } - - enum class ButtonType { - POSITIVE, NEUTRAL, NEGATIVE, IDGAF - } - - inner class Button { - val icon = KObservableField(0) - val title = KObservableField("") - val isEnabled = KObservableField(true) - - var onClickAction: OnDialogButtonClickListener = {} - - fun clicked() { - onClickAction(this@MagiskDialog) - dismiss() - } - } - - inner class ButtonBuilder(private val button: Button) { - var icon: Int - get() = button.icon.value - set(value) { - button.icon.value = value - } - var title: CharSequence - get() = button.title.value - set(value) { - button.title.value = value - } - var titleRes: Int - get() = 0 - set(value) { - button.title.value = context.getString(value) - } - var isEnabled: Boolean - get() = button.isEnabled.value - set(value) { - button.isEnabled.value = value - } - - fun onClick(listener: OnDialogButtonClickListener) { - button.onClickAction = listener - } - } - - fun applyTitle(@StringRes stringRes: Int) = - apply { data.title.value = context.getString(stringRes) } - - fun applyTitle(title: CharSequence) = - apply { data.title.value = title } - - fun applyMessage(@StringRes stringRes: Int) = - apply { data.message.value = context.getString(stringRes) } - - fun applyMessage(message: CharSequence) = - apply { data.message.value = message } - - fun applyIcon(@DrawableRes drawableRes: Int) = - apply { data.icon.value = drawableRes } - - fun applyIcon(drawable: Drawable) = - apply { data.iconRaw.value = drawable } - - fun applyButton(buttonType: ButtonType, builder: ButtonBuilder.() -> Unit) = apply { - val button = when (buttonType) { - ButtonType.POSITIVE -> data.buttonPositive - ButtonType.NEUTRAL -> data.buttonNeutral - ButtonType.NEGATIVE -> data.buttonNegative - ButtonType.IDGAF -> data.buttonIDGAF - } - ButtonBuilder(button).apply(builder) - } - - fun cancellable(isCancellable: Boolean) = apply { - setCancelable(isCancellable) - } - - fun applyView(binding: Binding, body: Binding.() -> Unit) = - apply { - this.binding.dialogBaseContainer.removeAllViews() - this.binding.dialogBaseContainer.addView(binding.root) - binding.apply(body) - } - - fun onDismiss(callback: OnDialogButtonClickListener) = - apply { setOnDismissListener(callback) } - - fun onShow(callback: OnDialogButtonClickListener) = - apply { setOnShowListener(callback) } - - fun reveal() = apply { super.show() } - - //region Deprecated Members - @Deprecated("Use applyTitle instead", ReplaceWith("applyTitle")) - override fun setTitle(title: CharSequence?) = Unit - - @Deprecated("Use applyTitle instead", ReplaceWith("applyTitle")) - override fun setTitle(titleId: Int) = Unit - - @Deprecated("Use reveal()", ReplaceWith("reveal()")) - override fun show() { - } - //endregion -} - -typealias OnDialogButtonClickListener = (DialogInterface) -> Unit \ No newline at end of file diff --git a/app/src/main/java/com/topjohnwu/magisk/view/MarkDownWindow.kt b/app/src/main/java/com/topjohnwu/magisk/view/MarkDownWindow.kt index 4a814104a..a7f16318e 100644 --- a/app/src/main/java/com/topjohnwu/magisk/view/MarkDownWindow.kt +++ b/app/src/main/java/com/topjohnwu/magisk/view/MarkDownWindow.kt @@ -4,10 +4,10 @@ import android.content.Context import android.view.LayoutInflater import android.widget.TextView import androidx.appcompat.app.AlertDialog -import com.skoumal.teanity.extensions.subscribeK import com.topjohnwu.magisk.R import com.topjohnwu.magisk.data.repository.StringRepository import com.topjohnwu.magisk.extensions.inject +import com.topjohnwu.magisk.extensions.subscribeK import io.reactivex.Completable import io.reactivex.Single import ru.noties.markwon.Markwon diff --git a/app/src/main/java/com/topjohnwu/magisk/view/dialogs/EnvFixDialog.kt b/app/src/main/java/com/topjohnwu/magisk/view/dialogs/EnvFixDialog.kt index 35f790d23..8eb37107b 100644 --- a/app/src/main/java/com/topjohnwu/magisk/view/dialogs/EnvFixDialog.kt +++ b/app/src/main/java/com/topjohnwu/magisk/view/dialogs/EnvFixDialog.kt @@ -8,9 +8,9 @@ import com.topjohnwu.magisk.Info import com.topjohnwu.magisk.R import com.topjohnwu.magisk.extensions.cachedFile import com.topjohnwu.magisk.extensions.reboot +import com.topjohnwu.magisk.net.Networking import com.topjohnwu.magisk.tasks.MagiskInstaller import com.topjohnwu.magisk.utils.Utils -import com.topjohnwu.magisk.net.Networking import com.topjohnwu.superuser.Shell import com.topjohnwu.superuser.ShellUtils import com.topjohnwu.superuser.internal.UiThreadHandler diff --git a/app/src/main/java/com/topjohnwu/magisk/viewmodel/LoadingViewModel.kt b/app/src/main/java/com/topjohnwu/magisk/viewmodel/LoadingViewModel.kt new file mode 100644 index 000000000..4ff9deb62 --- /dev/null +++ b/app/src/main/java/com/topjohnwu/magisk/viewmodel/LoadingViewModel.kt @@ -0,0 +1,78 @@ +package com.topjohnwu.magisk.viewmodel + +import androidx.databinding.Bindable +import com.topjohnwu.magisk.BR +import io.reactivex.* + +abstract class LoadingViewModel(defaultState: State = State.LOADING) : + StatefulViewModel(defaultState) { + + val loading @Bindable get() = state == State.LOADING + val loaded @Bindable get() = state == State.LOADED + val loadingFailed @Bindable get() = state == State.LOADING_FAILED + + @Deprecated( + "Direct access is recommended since 0.2. This access method will be removed in 1.0", + ReplaceWith("state = State.LOADING", "com.topjohnwu.magisk.viewmodel.LoadingViewModel.State"), + DeprecationLevel.WARNING + ) + fun setLoading() { + state = State.LOADING + } + + @Deprecated( + "Direct access is recommended since 0.2. This access method will be removed in 1.0", + ReplaceWith("state = State.LOADED", "com.topjohnwu.magisk.viewmodel.LoadingViewModel.State"), + DeprecationLevel.WARNING + ) + fun setLoaded() { + state = State.LOADED + } + + @Deprecated( + "Direct access is recommended since 0.2. This access method will be removed in 1.0", + ReplaceWith("state = State.LOADING_FAILED", "com.topjohnwu.magisk.viewmodel.LoadingViewModel.State"), + DeprecationLevel.WARNING + ) + fun setLoadingFailed() { + state = State.LOADING_FAILED + } + + override fun notifyStateChanged() { + notifyPropertyChanged(BR.loading) + notifyPropertyChanged(BR.loaded) + notifyPropertyChanged(BR.loadingFailed) + } + + enum class State { + LOADED, LOADING, LOADING_FAILED + } + + //region Rx + protected fun Observable.applyViewModel(viewModel: LoadingViewModel, allowFinishing: Boolean = true) = + doOnSubscribe { viewModel.state = State.LOADING } + .doOnError { viewModel.state = State.LOADING_FAILED } + .doOnNext { if (allowFinishing) viewModel.state = State.LOADED } + + protected fun Single.applyViewModel(viewModel: LoadingViewModel, allowFinishing: Boolean = true) = + doOnSubscribe { viewModel.state = State.LOADING } + .doOnError { viewModel.state = State.LOADING_FAILED } + .doOnSuccess { if (allowFinishing) viewModel.state = State.LOADED } + + protected fun Maybe.applyViewModel(viewModel: LoadingViewModel, allowFinishing: Boolean = true) = + doOnSubscribe { viewModel.state = State.LOADING } + .doOnError { viewModel.state = State.LOADING_FAILED } + .doOnComplete { if (allowFinishing) viewModel.state = State.LOADED } + .doOnSuccess { if (allowFinishing) viewModel.state = State.LOADED } + + protected fun Flowable.applyViewModel(viewModel: LoadingViewModel, allowFinishing: Boolean = true) = + doOnSubscribe { viewModel.state = State.LOADING } + .doOnError { viewModel.state = State.LOADING_FAILED } + .doOnNext { if (allowFinishing) viewModel.state = State.LOADED } + + protected fun Completable.applyViewModel(viewModel: LoadingViewModel, allowFinishing: Boolean = true) = + doOnSubscribe { viewModel.state = State.LOADING } + .doOnError { viewModel.state = State.LOADING_FAILED } + .doOnComplete { if (allowFinishing) viewModel.state = State.LOADED } + //endregion +} \ No newline at end of file diff --git a/app/src/main/java/com/topjohnwu/magisk/viewmodel/ObservableViewModel.kt b/app/src/main/java/com/topjohnwu/magisk/viewmodel/ObservableViewModel.kt new file mode 100644 index 000000000..1299836d8 --- /dev/null +++ b/app/src/main/java/com/topjohnwu/magisk/viewmodel/ObservableViewModel.kt @@ -0,0 +1,46 @@ +package com.topjohnwu.magisk.viewmodel + +import androidx.databinding.Observable +import androidx.databinding.PropertyChangeRegistry +import androidx.lifecycle.ViewModel + +/** + * Copy of [android.databinding.BaseObservable] which extends [ViewModel] + */ +abstract class ObservableViewModel : TeanityViewModel(), Observable { + + @Transient + private var callbacks: PropertyChangeRegistry? = null + + @Synchronized + override fun addOnPropertyChangedCallback(callback: Observable.OnPropertyChangedCallback) { + if (callbacks == null) { + callbacks = PropertyChangeRegistry() + } + callbacks?.add(callback) + } + + @Synchronized + override fun removeOnPropertyChangedCallback(callback: Observable.OnPropertyChangedCallback) { + callbacks?.remove(callback) + } + + /** + * Notifies listeners that all properties of this instance have changed. + */ + @Synchronized + fun notifyChange() { + callbacks?.notifyCallbacks(this, 0, null) + } + + /** + * Notifies listeners that a specific property has changed. The getter for the property + * that changes should be marked with [android.databinding.Bindable] to generate a field in + * `BR` to be used as `fieldId`. + * + * @param fieldId The generated BR id for the Bindable field. + */ + fun notifyPropertyChanged(fieldId: Int) { + callbacks?.notifyCallbacks(this, fieldId, null) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/topjohnwu/magisk/viewmodel/StatefulViewModel.kt b/app/src/main/java/com/topjohnwu/magisk/viewmodel/StatefulViewModel.kt new file mode 100644 index 000000000..5bf6229ff --- /dev/null +++ b/app/src/main/java/com/topjohnwu/magisk/viewmodel/StatefulViewModel.kt @@ -0,0 +1,15 @@ +package com.topjohnwu.magisk.viewmodel + +abstract class StatefulViewModel>( + val defaultState: State +) : ObservableViewModel() { + + var state: State = defaultState + set(value) { + field = value + notifyStateChanged() + } + + open fun notifyStateChanged() = Unit + +} \ No newline at end of file diff --git a/app/src/main/java/com/topjohnwu/magisk/viewmodel/TeanityViewModel.kt b/app/src/main/java/com/topjohnwu/magisk/viewmodel/TeanityViewModel.kt new file mode 100644 index 000000000..799a2305e --- /dev/null +++ b/app/src/main/java/com/topjohnwu/magisk/viewmodel/TeanityViewModel.kt @@ -0,0 +1,33 @@ +package com.topjohnwu.magisk.viewmodel + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import com.topjohnwu.magisk.model.events.SimpleViewEvent +import com.topjohnwu.magisk.model.events.ViewEvent +import io.reactivex.disposables.CompositeDisposable +import io.reactivex.disposables.Disposable + +abstract class TeanityViewModel : ViewModel() { + + private val disposables = CompositeDisposable() + private val _viewEvents = MutableLiveData() + val viewEvents: LiveData get() = _viewEvents + + override fun onCleared() { + super.onCleared() + disposables.clear() + } + + fun Event.publish() { + _viewEvents.value = this + } + + fun Int.publish() { + _viewEvents.value = SimpleViewEvent(this) + } + + fun Disposable.add() { + disposables.add(this) + } +} \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_magisk_base.xml b/app/src/main/res/layout/dialog_magisk_base.xml deleted file mode 100644 index 134113f17..000000000 --- a/app/src/main/res/layout/dialog_magisk_base.xml +++ /dev/null @@ -1,276 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_magisk.xml b/app/src/main/res/layout/fragment_magisk.xml index e72cecda2..b762c87cf 100644 --- a/app/src/main/res/layout/fragment_magisk.xml +++ b/app/src/main/res/layout/fragment_magisk.xml @@ -5,7 +5,7 @@ - + diff --git a/build.gradle b/build.gradle index 7d5f95138..4d37e4df7 100644 --- a/build.gradle +++ b/build.gradle @@ -7,7 +7,7 @@ if (configPath.exists()) configPath.withInputStream { is -> props.load(is) } buildscript { - ext.kotlinVer = '1.3.50' + ext.vKotlin = '1.3.50' repositories { google() @@ -17,7 +17,7 @@ buildscript { } dependencies { classpath 'com.android.tools.build:gradle:3.5.0' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${kotlinVer}" + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${vKotlin}" // NOTE: Do not place your application dependencies here; they belong