Updated modules so they are properly arranged to respective sections

Small updates to module UI
This commit is contained in:
Viktor De Pasquale 2019-11-06 17:09:00 +01:00
parent 1c8988d3f7
commit c7cad7e4aa
4 changed files with 112 additions and 68 deletions

View File

@ -1,8 +1,6 @@
package com.topjohnwu.magisk.model.entity.recycler package com.topjohnwu.magisk.model.entity.recycler
import android.content.res.Resources import android.content.res.Resources
import android.view.View
import android.view.ViewGroup
import androidx.annotation.StringRes import androidx.annotation.StringRes
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.databinding.ComparableRvItem import com.topjohnwu.magisk.databinding.ComparableRvItem
@ -13,8 +11,6 @@ import com.topjohnwu.magisk.model.entity.module.Module
import com.topjohnwu.magisk.model.entity.module.Repo import com.topjohnwu.magisk.model.entity.module.Repo
import com.topjohnwu.magisk.redesign.module.ModuleViewModel import com.topjohnwu.magisk.redesign.module.ModuleViewModel
import com.topjohnwu.magisk.utils.KObservableField import com.topjohnwu.magisk.utils.KObservableField
import com.topjohnwu.magisk.utils.rotationTo
import com.topjohnwu.magisk.utils.setRevealed
class ModuleRvItem(val item: Module) : ComparableRvItem<ModuleRvItem>() { class ModuleRvItem(val item: Module) : ComparableRvItem<ModuleRvItem>() {
@ -83,22 +79,29 @@ class ModuleItem(val item: Module) : ComparableRvItem<ModuleItem>() {
override val layoutRes = R.layout.item_module_md2 override val layoutRes = R.layout.item_module_md2
val isExpanded = KObservableField(false)
val isEnabled = KObservableField(item.enable) val isEnabled = KObservableField(item.enable)
val isRemoved = KObservableField(item.remove)
val isUpdated get() = item.updated
val isModified get() = item.enable != isEnabled.value val isModified get() = item.remove || item.updated
init {
isEnabled.addOnPropertyChangedCallback {
item.enable = it ?: return@addOnPropertyChangedCallback
}
isRemoved.addOnPropertyChangedCallback {
item.remove = it ?: return@addOnPropertyChangedCallback
}
}
fun toggle(viewModel: ModuleViewModel) { fun toggle(viewModel: ModuleViewModel) {
isEnabled.toggle() isEnabled.toggle()
viewModel.moveToState(this) viewModel.updateState()
} }
fun toggle(view: View) { fun delete(viewModel: ModuleViewModel) {
isExpanded.toggle() isRemoved.toggle()
view.rotationTo(if (isExpanded.value) 225 else 180) viewModel.updateState()
(view.parent as ViewGroup)
.findViewById<View>(R.id.module_expand_container)
.setRevealed(isExpanded.value)
} }
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,5 +1,7 @@
package com.topjohnwu.magisk.redesign.module package com.topjohnwu.magisk.redesign.module
import androidx.annotation.WorkerThread
import androidx.recyclerview.widget.DiffUtil
import com.topjohnwu.magisk.BR import com.topjohnwu.magisk.BR
import com.topjohnwu.magisk.extensions.subscribeK import com.topjohnwu.magisk.extensions.subscribeK
import com.topjohnwu.magisk.model.entity.module.Module import com.topjohnwu.magisk.model.entity.module.Module
@ -7,6 +9,8 @@ import com.topjohnwu.magisk.model.entity.recycler.ModuleItem
import com.topjohnwu.magisk.redesign.compat.CompatViewModel import com.topjohnwu.magisk.redesign.compat.CompatViewModel
import com.topjohnwu.magisk.redesign.home.itemBindingOf 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.utils.DiffObservableList
import com.topjohnwu.magisk.utils.currentLocale
import io.reactivex.Single import io.reactivex.Single
class ModuleViewModel : CompatViewModel() { class ModuleViewModel : CompatViewModel() {
@ -19,31 +23,65 @@ class ModuleViewModel : CompatViewModel() {
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 to items.calculateDiff(it) } .map { it.order() }
.subscribeK { .subscribeK { it.forEach { it.update() } }
items.update(it.first, it.second)
items.forEach { moveToState(it) }
}
fun moveToState(item: ModuleItem) { @WorkerThread
val isActive = items.indexOfFirst { it.itemSameAs(item) } != -1 private fun List<ModuleItem>.order() = sortedBy { it.item.name.toLowerCase(currentLocale) }
val isPending = itemsPending.indexOfFirst { it.itemSameAs(item) } != -1 .groupBy {
when {
when { it.isModified -> ModuleState.Modified
isActive && isPending -> if (item.isModified) { else -> ModuleState.Normal
items.remove(item)
} else {
itemsPending.remove(item)
}
isActive && item.isModified -> {
items.remove(item)
itemsPending.add(item)
}
isPending && !item.isModified -> {
itemsPending.remove(item)
items.add(item)
} }
} }
.map {
val diff = when (it.key) {
ModuleState.Modified -> itemsPending
ModuleState.Normal -> items
}.calculateDiff(it.value)
ResultEnclosure(it.key, it.value, diff)
}
.ensureAllStates()
private fun List<ResultEnclosure>.ensureAllStates(): List<ResultEnclosure> {
val me = this as? MutableList<ResultEnclosure> ?: this.toMutableList()
ModuleState.values().forEach {
if (me.none { rit -> it == rit.state }) {
me.add(ResultEnclosure(it, listOf(), null))
}
}
return me
}
fun updateState() {
// I don't feel like bothering with moving every single item to its updated state,
// so this kind of wasteful operation helps with that
Single.fromCallable { items + itemsPending }
.map { it.order() }
.subscribeK { it.forEach { it.update() } }
}
private enum class ModuleState {
Modified, Normal
}
private data class ResultEnclosure(
val state: ModuleState,
val list: List<ModuleItem>,
val diff: DiffUtil.DiffResult?
)
private fun ResultEnclosure.update() = when (state) {
ModuleState.Modified -> itemsPending
ModuleState.Normal -> items
}.update(list, diff)
private fun <T> DiffObservableList<T>.update(list: List<T>, diff: DiffUtil.DiffResult?) {
diff ?: let {
update(list)
return
}
update(list, diff)
} }
} }

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/ic_restart" android:state_selected="true" />
<item android:drawable="@drawable/ic_delete_md2" />
</selector>

View File

@ -5,6 +5,8 @@
<data> <data>
<import type="com.topjohnwu.magisk.R" />
<import type="com.topjohnwu.magisk.Config" /> <import type="com.topjohnwu.magisk.Config" />
<variable <variable
@ -19,7 +21,7 @@
<com.google.android.material.card.MaterialCardView <com.google.android.material.card.MaterialCardView
style="?styleCardVariant" style="?styleCardVariant"
isEnabled="@{!Config.coreOnly}" isEnabled="@{!Config.coreOnly &amp;&amp; !item.isRemoved()}"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:alpha="@{item.isEnabled() &amp;&amp; !Config.coreOnly ? 1f : .5f}" android:alpha="@{item.isEnabled() &amp;&amp; !Config.coreOnly ? 1f : .5f}"
@ -33,28 +35,41 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingBottom="@dimen/l1"> android:paddingBottom="@dimen/l1">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/module_state_icon"
style="?styleImageSmall"
gone="@{!item.isRemoved &amp;&amp; !item.updated}"
srcCompat="@{item.isRemoved ? R.drawable.ic_delete_md2 : R.drawable.ic_update_md2}"
android:layout_marginStart="@dimen/l1"
android:background="@null"
app:layout_constraintBottom_toBottomOf="@+id/module_version_author"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/module_title"
tools:srcCompat="@drawable/ic_update_md2" />
<androidx.appcompat.widget.AppCompatTextView <androidx.appcompat.widget.AppCompatTextView
android:id="@+id/module_title" android:id="@+id/module_title"
android:layout_width="match_parent" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="@dimen/l1" android:layout_marginStart="@dimen/l1"
android:layout_marginTop="@dimen/l1" android:layout_marginTop="@dimen/l1"
android:layout_marginEnd="32dp"
android:text="@{item.item.name}" android:text="@{item.item.name}"
android:textAppearance="?appearanceTextBodyNormal" android:textAppearance="?appearanceTextBodyNormal"
android:textStyle="bold" android:textStyle="bold"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toStartOf="@+id/module_remove"
app:layout_constraintStart_toEndOf="@+id/module_state_icon"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
tools:text="@tools:sample/lorem" /> tools:text="@tools:sample/lorem" />
<androidx.appcompat.widget.AppCompatTextView <androidx.appcompat.widget.AppCompatTextView
android:id="@+id/module_version_author" android:id="@+id/module_version_author"
android:layout_width="match_parent" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="@dimen/l1" android:layout_marginStart="@dimen/l1"
android:layout_marginEnd="@dimen/l1"
android:text="@{@string/module_version_author(item.item.version, item.item.author)}" android:text="@{@string/module_version_author(item.item.version, item.item.author)}"
android:textAppearance="?appearanceTextCaptionVariant" android:textAppearance="?appearanceTextCaptionVariant"
app:layout_constraintEnd_toStartOf="@+id/module_remove"
app:layout_constraintStart_toEndOf="@+id/module_state_icon"
app:layout_constraintTop_toBottomOf="@+id/module_title" app:layout_constraintTop_toBottomOf="@+id/module_title"
tools:text="v1 by topjohnwu" /> tools:text="v1 by topjohnwu" />
@ -71,35 +86,18 @@
tools:lines="4" tools:lines="4"
tools:text="@tools:sample/lorem/random" /> tools:text="@tools:sample/lorem/random" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/module_remove"
style="?styleIconNormal"
isSelected="@{item.isRemoved}"
android:alpha=".5"
android:onClick="@{(v) -> item.delete(viewModel)}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_delete_restore" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
<FrameLayout
android:id="@+id/module_expand_container"
revealFix="@{item.isExpanded}"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?colorSurfaceVariant"
android:visibility="gone">
<com.google.android.material.button.MaterialButton
style="?styleButtonError"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="Remove"
android:textAllCaps="false"
app:icon="@drawable/ic_delete_md2" />
</FrameLayout>
<androidx.appcompat.widget.AppCompatImageView
style="?styleIconNormal"
isSelected="@{item.isExpanded}"
android:layout_gravity="top|end"
android:onClick="@{(v) -> item.toggle(v)}"
android:rotation="@{item.isExpanded ? 225 : 180}"
app:srcCompat="@drawable/ic_more_collapse" />
</com.google.android.material.card.MaterialCardView> </com.google.android.material.card.MaterialCardView>