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

View File

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

View File

@ -32,13 +32,14 @@
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
android:layout_height="wrap_content"
android:animateLayoutChanges="true">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/module_state_icon"
style="?styleImageSmall"
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:background="@null"
app:layout_constraintBottom_toBottomOf="@+id/module_version_author"
@ -101,7 +102,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
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"
app:icon="@{item.removed ? R.drawable.ic_restart : R.drawable.ic_delete_md2}"
app:iconGravity="textEnd"

View File

@ -8,10 +8,15 @@
name="item"
type="com.topjohnwu.magisk.model.entity.recycler.SectionTitle" />
<variable
name="viewModel"
type="com.topjohnwu.magisk.redesign.module.ModuleViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:animateLayoutChanges="true"
android:layout_height="wrap_content">
<androidx.appcompat.widget.AppCompatTextView
@ -30,7 +35,8 @@
<com.google.android.material.button.MaterialButton
android:id="@+id/module_button"
style="?styleButtonText"
gone="@{item.button == 0 || item.icon == 0}"
gone="@{!item.hasButton}"
android:onClick="@{() -> viewModel.sectionPressed(item)}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
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_version_author">%1$s by %2$s</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_state_remove">Remove</string>
<string name="module_state_restore">Restore</string>
<string name="superuser_toggle_log">Toggles logging</string>
<string name="superuser_toggle_notification">Toggles “toast” notifications</string>