mirror of
https://github.com/topjohnwu/Magisk.git
synced 2024-11-27 20:15:29 +00:00
Updated module sections so it looks more consistent
This commit is contained in:
parent
82120cf47f
commit
495e734428
@ -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
|
||||||
|
@ -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) =
|
||||||
|
@ -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 && !item.updated}"
|
gone="@{!item.removed && !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"
|
||||||
|
@ -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"
|
||||||
|
@ -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>
|
||||||
|
Loading…
Reference in New Issue
Block a user