Replace old design with redesign (p2)

This commit is contained in:
topjohnwu
2020-01-12 16:07:30 +08:00
parent df0a5b59f8
commit 9094cf7ce3
51 changed files with 22 additions and 4354 deletions
@@ -1,111 +0,0 @@
package com.topjohnwu.magisk.ui.module
import android.content.res.Resources
import com.topjohnwu.magisk.BR
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.base.viewmodel.BaseViewModel
import com.topjohnwu.magisk.data.database.RepoDao
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
import com.topjohnwu.magisk.model.entity.recycler.SectionRvItem
import com.topjohnwu.magisk.model.events.InstallModuleEvent
import com.topjohnwu.magisk.model.events.OpenChangelogEvent
import com.topjohnwu.magisk.model.events.OpenFilePickerEvent
import com.topjohnwu.magisk.tasks.RepoUpdater
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
class ModuleViewModel(
private val resources: Resources,
private val repoUpdater: RepoUpdater,
private val repoDB: RepoDao
) : BaseViewModel() {
val query = KObservableField("")
private val allItems = mutableListOf<ComparableRvItem<*>>()
val itemsInstalled = DiffObservableList(ComparableRvItem.callback)
val itemsRemote = DiffObservableList(ComparableRvItem.callback)
val itemBinding = OnItemBind<ComparableRvItem<*>> { itemBinding, _, item ->
item.bind(itemBinding)
itemBinding.bindExtra(BR.viewModel, this@ModuleViewModel)
}
private var queryDisposable: Disposable? = null
init {
query.addOnPropertyChangedCallback {
queryDisposable?.dispose()
queryDisposable = query()
}
refresh(false)
}
fun fabPressed() = OpenFilePickerEvent().publish()
fun repoPressed(item: RepoRvItem) = OpenChangelogEvent(item.item).publish()
fun downloadPressed(item: RepoRvItem) = InstallModuleEvent(item.item).publish()
fun refresh(force: Boolean) {
Single.fromCallable { Module.loadModules() }
.flattenAsFlowable { it }
.map { ModuleRvItem(it) }
.toList()
.map { it to itemsInstalled.calculateDiff(it) }
.doOnSuccessUi { itemsInstalled.update(it.first, it.second) }
.flatMap { repoUpdater(force) }
.flattenAsFlowable { repoDB.repos }
.map { RepoRvItem(it) }
.toList()
.doOnSuccess { allItems.update(it) }
.flatMap { queryRaw() }
.applyViewModel(this)
.subscribeK { itemsRemote.update(it.first, it.second) }
}
private fun query() = queryRaw()
.subscribeK { itemsRemote.update(it.first, it.second) }
private fun queryRaw(query: String = this.query.value) = allItems.toSingle()
.map { it.filterIsInstance<RepoRvItem>() }
.flattenAsFlowable { it }
.filter {
it.item.name.contains(query, ignoreCase = true) ||
it.item.author.contains(query, ignoreCase = true) ||
it.item.description.contains(query, ignoreCase = true)
}
.toList()
.map { if (query.isEmpty()) it.divide() else it }
.map { it to itemsRemote.calculateDiff(it) }
private fun List<RepoRvItem>.divide(): List<ComparableRvItem<*>> {
val installed = itemsInstalled.filterIsInstance<ModuleRvItem>()
fun <T : ComparableRvItem<*>> List<T>.withTitle(text: Int) =
if (isEmpty()) this else listOf(SectionRvItem(resources.getString(text))) + this
val groupedItems = groupBy { repo ->
installed.firstOrNull { it.item.id == repo.item.id }?.let {
if (it.item.versionCode < repo.item.versionCode) MODULE_UPDATABLE
else MODULE_INSTALLED
} ?: MODULE_REMOTE
}
return groupedItems.getOrElse(MODULE_UPDATABLE) { listOf() }.withTitle(R.string.update_available) +
groupedItems.getOrElse(MODULE_INSTALLED) { listOf() }.withTitle(R.string.installed) +
groupedItems.getOrElse(MODULE_REMOTE) { listOf() }.withTitle(R.string.not_installed)
}
companion object {
protected const val MODULE_INSTALLED = 0
protected const val MODULE_REMOTE = 1
protected const val MODULE_UPDATABLE = 2
}
}
@@ -1,99 +0,0 @@
package com.topjohnwu.magisk.ui.module
import android.app.Activity
import android.content.Intent
import android.os.Bundle
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import androidx.recyclerview.widget.RecyclerView
import com.topjohnwu.magisk.Const
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.base.BaseFragment
import com.topjohnwu.magisk.databinding.FragmentModulesBinding
import com.topjohnwu.magisk.extensions.reboot
import com.topjohnwu.magisk.intent
import com.topjohnwu.magisk.model.events.OpenFilePickerEvent
import com.topjohnwu.magisk.model.events.ViewEvent
import com.topjohnwu.magisk.ui.flash.FlashActivity
import com.topjohnwu.superuser.Shell
import org.koin.androidx.viewmodel.ext.android.sharedViewModel
class ModulesFragment : BaseFragment<ModuleViewModel, FragmentModulesBinding>() {
override val layoutRes: Int = R.layout.fragment_modules
override val viewModel: ModuleViewModel by sharedViewModel()
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (requestCode == Const.ID.FETCH_ZIP && resultCode == Activity.RESULT_OK && data != null) {
// Get the URI of the selected file
val intent = activity.intent<FlashActivity>()
intent.setData(data.data).putExtra(Const.Key.FLASH_ACTION, Const.Value.FLASH_ZIP)
startActivity(intent)
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.modulesContent.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
binding.modulesRefreshLayout.isEnabled = recyclerView.getChildAt(0).top >= 0
}
})
}
override fun onEventDispatched(event: ViewEvent) {
super.onEventDispatched(event)
when (event) {
is OpenFilePickerEvent -> selectFile()
}
}
override fun onStart() {
super.onStart()
setHasOptionsMenu(true)
requireActivity().setTitle(R.string.modules)
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.menu_reboot, menu)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.reboot -> {
reboot()
return true
}
R.id.reboot_recovery -> {
Shell.su("/system/bin/reboot recovery").submit()
return true
}
R.id.reboot_bootloader -> {
reboot("bootloader")
return true
}
R.id.reboot_download -> {
reboot("download")
return true
}
R.id.reboot_edl -> {
reboot("edl")
return true
}
else -> return false
}
}
private fun selectFile() {
activity.withExternalRW {
onSuccess {
val intent = Intent(Intent.ACTION_GET_CONTENT)
intent.type = "application/zip"
startActivityForResult(intent, Const.ID.FETCH_ZIP)
}
}
}
}
@@ -1,116 +0,0 @@
package com.topjohnwu.magisk.ui.module
import android.annotation.SuppressLint
import android.app.AlertDialog
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.widget.SearchView
import com.topjohnwu.magisk.Config
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.base.BaseFragment
import com.topjohnwu.magisk.databinding.FragmentReposBinding
import com.topjohnwu.magisk.model.download.DownloadService
import com.topjohnwu.magisk.model.entity.internal.Configuration
import com.topjohnwu.magisk.model.entity.internal.DownloadSubject
import com.topjohnwu.magisk.model.entity.module.Repo
import com.topjohnwu.magisk.model.events.InstallModuleEvent
import com.topjohnwu.magisk.model.events.OpenChangelogEvent
import com.topjohnwu.magisk.model.events.ViewEvent
import com.topjohnwu.magisk.view.MarkDownWindow
import com.topjohnwu.magisk.view.dialogs.CustomAlertDialog
import org.koin.androidx.viewmodel.ext.android.sharedViewModel
class ReposFragment : BaseFragment<ModuleViewModel, FragmentReposBinding>(),
SearchView.OnQueryTextListener {
override val layoutRes: Int = R.layout.fragment_repos
override val viewModel: ModuleViewModel by sharedViewModel()
override fun onStart() {
super.onStart()
setHasOptionsMenu(true)
requireActivity().setTitle(R.string.downloads)
}
override fun onEventDispatched(event: ViewEvent) {
super.onEventDispatched(event)
when (event) {
is OpenChangelogEvent -> openChangelog(event.item)
is InstallModuleEvent -> installModule(event.item)
}
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.menu_repo, menu)
val query = viewModel.query.value
val searchItem = menu.findItem(R.id.repo_search)
val searchView = searchItem.actionView as? SearchView
searchView?.run {
setOnQueryTextListener(this@ReposFragment)
setQuery(query, false)
}
if (query.isNotBlank()) {
searchItem.expandActionView()
searchView?.isIconified = false
} else {
searchItem.collapseActionView()
searchView?.isIconified = true
}
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == R.id.repo_sort) {
AlertDialog.Builder(activity)
.setTitle(R.string.sorting_order)
.setSingleChoiceItems(
R.array.sorting_orders,
Config.repoOrder
) { d, which ->
Config.repoOrder = which
viewModel.refresh(false)
d.dismiss()
}.show()
}
return true
}
override fun onQueryTextSubmit(p0: String?): Boolean {
viewModel.query.value = p0.orEmpty()
return false
}
override fun onQueryTextChange(p0: String?): Boolean {
viewModel.query.value = p0.orEmpty()
return false
}
private fun openChangelog(item: Repo) {
MarkDownWindow.show(requireActivity(), null, item.readme)
}
@SuppressLint("MissingPermission")
private fun installModule(item: Repo) {
val context = activity
fun download(install: Boolean) = context.withExternalRW {
onSuccess {
DownloadService(context) {
val config = if (install) Configuration.Flash.Primary else Configuration.Download
subject = DownloadSubject.Module(item, config)
}
}
}
CustomAlertDialog(context)
.setTitle(context.getString(R.string.repo_install_title, item.name))
.setMessage(context.getString(R.string.repo_install_msg, item.downloadFilename))
.setCancelable(true)
.setPositiveButton(R.string.install) { _, _ -> download(true) }
.setNeutralButton(R.string.download) { _, _ -> download(false) }
.show()
}
}