Updated module sections so it looks more consistent

This commit is contained in:
Viktor De Pasquale 2019-11-11 19:36:40 +01:00
parent 82120cf47f
commit 495e734428
5 changed files with 40 additions and 99 deletions

View File

@ -88,6 +88,8 @@ class SectionTitle(
) : ComparableRvItem<SectionTitle>() { ) : ComparableRvItem<SectionTitle>() {
override val layoutRes = R.layout.item_section_md2 override val layoutRes = R.layout.item_section_md2
val hasButton = KObservableField(button != 0 || icon != 0)
override fun onBindingBound(binding: ViewDataBinding) { override fun onBindingBound(binding: ViewDataBinding) {
super.onBindingBound(binding) super.onBindingBound(binding)
val params = binding.root.layoutParams as StaggeredGridLayoutManager.LayoutParams val params = binding.root.layoutParams as StaggeredGridLayoutManager.LayoutParams
@ -136,7 +138,7 @@ class ModuleItem(val item: Module) : ObservableItem<ModuleItem>(), Observable {
fun delete(viewModel: ModuleViewModel) { fun delete(viewModel: ModuleViewModel) {
isRemoved = !isRemoved isRemoved = !isRemoved
viewModel.moveToState(this) viewModel.moveToState()
} }
override fun contentSameAs(other: ModuleItem): Boolean = item.version == other.item.version override fun contentSameAs(other: ModuleItem): Boolean = item.version == other.item.version

View File

@ -1,6 +1,5 @@
package com.topjohnwu.magisk.redesign.module package com.topjohnwu.magisk.redesign.module
import androidx.annotation.UiThread
import androidx.annotation.WorkerThread import androidx.annotation.WorkerThread
import androidx.databinding.ViewDataBinding import androidx.databinding.ViewDataBinding
import com.topjohnwu.magisk.BR import com.topjohnwu.magisk.BR
@ -9,6 +8,7 @@ import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.data.database.RepoByNameDao import com.topjohnwu.magisk.data.database.RepoByNameDao
import com.topjohnwu.magisk.data.database.RepoByUpdatedDao import com.topjohnwu.magisk.data.database.RepoByUpdatedDao
import com.topjohnwu.magisk.databinding.ComparableRvItem import com.topjohnwu.magisk.databinding.ComparableRvItem
import com.topjohnwu.magisk.extensions.reboot
import com.topjohnwu.magisk.extensions.subscribeK import com.topjohnwu.magisk.extensions.subscribeK
import com.topjohnwu.magisk.model.download.RemoteFileService import com.topjohnwu.magisk.model.download.RemoteFileService
import com.topjohnwu.magisk.model.entity.internal.DownloadSubject import com.topjohnwu.magisk.model.entity.internal.DownloadSubject
@ -24,9 +24,7 @@ import com.topjohnwu.magisk.redesign.home.itemBindingOf
import com.topjohnwu.magisk.redesign.superuser.diffListOf import com.topjohnwu.magisk.redesign.superuser.diffListOf
import com.topjohnwu.magisk.tasks.RepoUpdater import com.topjohnwu.magisk.tasks.RepoUpdater
import com.topjohnwu.magisk.utils.currentLocale import com.topjohnwu.magisk.utils.currentLocale
import io.reactivex.Completable
import io.reactivex.Single import io.reactivex.Single
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable import io.reactivex.disposables.Disposable
import me.tatarka.bindingcollectionadapter2.BindingRecyclerViewAdapter import me.tatarka.bindingcollectionadapter2.BindingRecyclerViewAdapter
import timber.log.Timber import timber.log.Timber
@ -46,22 +44,18 @@ class ModuleViewModel(
companion object { companion object {
private val sectionRemote = SectionTitle(R.string.module_section_remote) private val sectionRemote = SectionTitle(R.string.module_section_remote)
private val sectionActive = SectionTitle(R.string.module_section_active) private val sectionActive = SectionTitle(
private val sectionPending = R.string.module_section_installed,
SectionTitle(R.string.module_section_pending, R.string.reboot, R.drawable.ic_restart) R.string.reboot,
R.drawable.ic_restart
).also { it.hasButton.value = false }
} }
// --- // ---
private val itemsPending private val itemsInstalled
@WorkerThread get() = items.asSequence() @WorkerThread get() = items.asSequence()
.filterIsInstance<ModuleItem>() .filterIsInstance<ModuleItem>()
.filter { it.isModified }
.toList()
private val itemsActive
@WorkerThread get() = items.asSequence()
.filterIsInstance<ModuleItem>()
.filter { !it.isModified }
.toList() .toList()
private val itemsRemote private val itemsRemote
@WorkerThread get() = items.filterIsInstance<RepoItem>() @WorkerThread get() = items.filterIsInstance<RepoItem>()
@ -92,29 +86,25 @@ class ModuleViewModel(
override fun refresh() = Single.fromCallable { Module.loadModules() } override fun refresh() = Single.fromCallable { Module.loadModules() }
.map { it.map { ModuleItem(it) } } .map { it.map { ModuleItem(it) } }
.map { it.order() } .map { it.order() }
.map { .map { build(active = it) }
val pending = it.getValue(ModuleState.Modified)
val active = it.getValue(ModuleState.Normal)
build(pending = pending, active = active)
}
.map { it to items.calculateDiff(it) } .map { it to items.calculateDiff(it) }
.subscribeK { .subscribeK {
items.update(it.first, it.second) items.update(it.first, it.second)
if (!items.contains(sectionRemote)) { if (!items.contains(sectionRemote)) {
loadRemote() loadRemote()
} }
moveToState()
} }
@Synchronized @Synchronized
fun loadRemote() { fun loadRemote() {
// check for existing jobs // check for existing jobs
val size = itemsRemote.size if (remoteJob?.isDisposed?.not() == true) {
if (remoteJob?.isDisposed?.not() == true || size % 10 != 0) {
return return
} }
remoteJob = loadRepos(offset = size) remoteJob = Single.fromCallable { itemsRemote.size }
.flatMap { loadRepos(offset = it) }
.map { it.map { RepoItem(it) } } .map { it.map { RepoItem(it) } }
.applyViewModel(this)
.subscribeK(onError = { .subscribeK(onError = {
Timber.e(it) Timber.e(it)
items.remove(LoadingItem) items.remove(LoadingItem)
@ -125,6 +115,7 @@ class ModuleViewModel(
} }
items.addAll(it) items.addAll(it)
} }
// do on subscribe doesn't perform the action on main thread, so this is perfectly fine
items.add(LoadingItem) items.add(LoadingItem)
} }
@ -149,23 +140,7 @@ class ModuleViewModel(
@WorkerThread @WorkerThread
private fun List<ModuleItem>.order() = asSequence() private fun List<ModuleItem>.order() = asSequence()
.sortedBy { it.item.name.toLowerCase(currentLocale) } .sortedBy { it.item.name.toLowerCase(currentLocale) }
.groupBy { .toList()
when {
it.isModified -> ModuleState.Modified
else -> ModuleState.Normal
}
}
.ensureAllStates()
private fun Map<ModuleState, List<ModuleItem>>.ensureAllStates(): Map<ModuleState, List<ModuleItem>> {
val me = this as? MutableMap<ModuleState, List<ModuleItem>> ?: this.toMutableMap()
ModuleState.values().forEach {
if (me.none { rit -> it == rit.key }) {
me[it] = listOf()
}
}
return me
}
private fun update(repo: Repo, progress: Int) = Single.fromCallable { itemsRemote } private fun update(repo: Repo, progress: Int) = Single.fromCallable { itemsRemote }
.map { it.first { it.item.id == repo.id } } .map { it.first { it.item.id == repo.id } }
@ -174,58 +149,15 @@ class ModuleViewModel(
// --- // ---
@UiThread fun moveToState() = Single.fromCallable { itemsInstalled.any { it.isModified } }
fun moveToState(item: ModuleItem) { .subscribeK { sectionActive.hasButton.value = it }
items.removeAll { it.genericItemSameAs(item) } .add()
val isPending = item.isModified fun download(item: RepoItem) = ModuleInstallDialog(item.item).publish()
Single.fromCallable { if (isPending) itemsPending else itemsActive } fun sectionPressed(item: SectionTitle) = when (item) {
.map { (listOf(item) + it).toMutableList() } sectionActive -> reboot()
.map { it.apply { sortWith(compareBy { it.item.name.toLowerCase(currentLocale) }) } } else -> Unit
.map {
if (isPending) build(pending = it)
else build(active = it)
}
.map { it to items.calculateDiff(it) }
.observeOn(AndroidSchedulers.mainThread())
.doOnSuccess { items.update(it.first, it.second) }
.ignoreElement()
.andThen(cleanup())
.subscribeK()
}
fun download(item: RepoItem) {
ModuleInstallDialog(item.item).publish()
}
// ---
private fun cleanup() = Completable
.concat(listOf(cleanPending(), cleanActive(), cleanRemote()))
private fun cleanPending() = Single.fromCallable { itemsPending }
.filter { it.isEmpty() }
.observeOn(AndroidSchedulers.mainThread())
.doOnSuccess { items.remove(sectionPending) }
.ignoreElement()
private fun cleanActive() = Single.fromCallable { itemsActive }
.filter { it.isEmpty() }
.observeOn(AndroidSchedulers.mainThread())
.doOnSuccess { items.remove(sectionActive) }
.ignoreElement()
private fun cleanRemote() = Single.fromCallable { itemsRemote }
.filter { it.isEmpty() }
.observeOn(AndroidSchedulers.mainThread())
.doOnSuccess { items.remove(sectionRemote) }
.ignoreElement()
// ---
private enum class ModuleState {
Modified, Normal
} }
// --- // ---
@ -233,11 +165,9 @@ class ModuleViewModel(
/** Callable only from worker thread because of expensive list filtering */ /** Callable only from worker thread because of expensive list filtering */
@WorkerThread @WorkerThread
private fun build( private fun build(
pending: List<ModuleItem> = itemsPending, active: List<ModuleItem> = itemsInstalled,
active: List<ModuleItem> = itemsActive,
remote: List<RepoItem> = itemsRemote remote: List<RepoItem> = itemsRemote
) = pending.prependIfNotEmpty { sectionPending } + ) = active.prependIfNotEmpty { sectionActive } +
active.prependIfNotEmpty { sectionActive } +
remote.prependIfNotEmpty { sectionRemote } remote.prependIfNotEmpty { sectionRemote }
private fun <T> List<T>.prependIfNotEmpty(item: () -> T) = private fun <T> List<T>.prependIfNotEmpty(item: () -> T) =

View File

@ -32,13 +32,14 @@
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content"> android:layout_height="wrap_content"
android:animateLayoutChanges="true">
<androidx.appcompat.widget.AppCompatImageView <androidx.appcompat.widget.AppCompatImageView
android:id="@+id/module_state_icon" android:id="@+id/module_state_icon"
style="?styleImageSmall" style="?styleImageSmall"
gone="@{!item.removed &amp;&amp; !item.updated}" gone="@{!item.removed &amp;&amp; !item.updated}"
srcCompat="@{item.removed ? R.drawable.ic_delete_md2 : R.drawable.ic_update_md2}" srcCompat="@{item.removed ? R.drawable.ic_delete_md2 : (item.updated ? R.drawable.ic_update_md2 : 0)}"
android:layout_marginStart="@dimen/l1" android:layout_marginStart="@dimen/l1"
android:background="@null" android:background="@null"
app:layout_constraintBottom_toBottomOf="@+id/module_version_author" app:layout_constraintBottom_toBottomOf="@+id/module_version_author"
@ -101,7 +102,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:onClick="@{(v) -> item.delete(viewModel)}" android:onClick="@{(v) -> item.delete(viewModel)}"
android:text="@{item.removed ? `Restore` : `Remove`}" android:text="@{item.removed ? @string/module_state_restore : @string/module_state_remove}"
android:textAllCaps="false" android:textAllCaps="false"
app:icon="@{item.removed ? R.drawable.ic_restart : R.drawable.ic_delete_md2}" app:icon="@{item.removed ? R.drawable.ic_restart : R.drawable.ic_delete_md2}"
app:iconGravity="textEnd" app:iconGravity="textEnd"

View File

@ -8,10 +8,15 @@
name="item" name="item"
type="com.topjohnwu.magisk.model.entity.recycler.SectionTitle" /> type="com.topjohnwu.magisk.model.entity.recycler.SectionTitle" />
<variable
name="viewModel"
type="com.topjohnwu.magisk.redesign.module.ModuleViewModel" />
</data> </data>
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:animateLayoutChanges="true"
android:layout_height="wrap_content"> android:layout_height="wrap_content">
<androidx.appcompat.widget.AppCompatTextView <androidx.appcompat.widget.AppCompatTextView
@ -30,7 +35,8 @@
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/module_button" android:id="@+id/module_button"
style="?styleButtonText" style="?styleButtonText"
gone="@{item.button == 0 || item.icon == 0}" gone="@{!item.hasButton}"
android:onClick="@{() -> viewModel.sectionPressed(item)}"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="@dimen/l1" android:layout_marginStart="@dimen/l1"

View File

@ -76,8 +76,10 @@
<string name="module_safe_mode_message">You\'re in safe mode. None of user modules will work.\nThis message will disappear once safe mode is disabled.</string> <string name="module_safe_mode_message">You\'re in safe mode. None of user modules will work.\nThis message will disappear once safe mode is disabled.</string>
<string name="module_version_author">%1$s by %2$s</string> <string name="module_version_author">%1$s by %2$s</string>
<string name="module_section_pending">Pending changes</string> <string name="module_section_pending">Pending changes</string>
<string name="module_section_active">Active</string> <string name="module_section_installed">Installed</string>
<string name="module_section_remote">Remote</string> <string name="module_section_remote">Remote</string>
<string name="module_state_remove">Remove</string>
<string name="module_state_restore">Restore</string>
<string name="superuser_toggle_log">Toggles logging</string> <string name="superuser_toggle_log">Toggles logging</string>
<string name="superuser_toggle_notification">Toggles “toast” notifications</string> <string name="superuser_toggle_notification">Toggles “toast” notifications</string>