From f5c982355a2e3380b2b64af4b0caa8f4f7cf9157 Mon Sep 17 00:00:00 2001 From: topjohnwu Date: Tue, 31 Aug 2021 02:46:29 -0700 Subject: [PATCH] Remove online section in modules fragment --- app/build.gradle.kts | 2 +- .../com/topjohnwu/magisk/di/ServiceLocator.kt | 2 - .../magisk/ui/module/ModuleFragment.kt | 36 +-- .../magisk/ui/module/ModuleRvItem.kt | 26 +- .../magisk/ui/module/ModuleViewModel.kt | 227 +++--------------- app/src/main/res/layout/item_module_md2.xml | 4 +- app/src/main/res/layout/item_repo_md2.xml | 10 +- 7 files changed, 53 insertions(+), 254 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 2471fc8c0..cedd3d2a6 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -232,7 +232,7 @@ dependencies { implementation("androidx.navigation:navigation-ui-ktx:${vNav}") implementation("androidx.biometric:biometric:1.1.0") - implementation("androidx.constraintlayout:constraintlayout:2.0.4") + implementation("androidx.constraintlayout:constraintlayout:2.1.0") implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0") implementation("androidx.browser:browser:1.3.0") implementation("androidx.preference:preference:1.1.1") diff --git a/app/src/main/java/com/topjohnwu/magisk/di/ServiceLocator.kt b/app/src/main/java/com/topjohnwu/magisk/di/ServiceLocator.kt index cb79d1eaa..d2869c95c 100644 --- a/app/src/main/java/com/topjohnwu/magisk/di/ServiceLocator.kt +++ b/app/src/main/java/com/topjohnwu/magisk/di/ServiceLocator.kt @@ -19,7 +19,6 @@ import com.topjohnwu.magisk.ktx.deviceProtectedContext import com.topjohnwu.magisk.ui.home.HomeViewModel import com.topjohnwu.magisk.ui.install.InstallViewModel import com.topjohnwu.magisk.ui.log.LogViewModel -import com.topjohnwu.magisk.ui.module.ModuleViewModel import com.topjohnwu.magisk.ui.settings.SettingsViewModel import com.topjohnwu.magisk.ui.superuser.SuperuserViewModel import com.topjohnwu.magisk.ui.surequest.SuRequestViewModel @@ -61,7 +60,6 @@ object ServiceLocator { return when (clz) { HomeViewModel::class.java -> HomeViewModel(networkService) LogViewModel::class.java -> LogViewModel(logRepo) - ModuleViewModel::class.java -> ModuleViewModel(repoDB, repoUpdater) SettingsViewModel::class.java -> SettingsViewModel(repoDB) SuperuserViewModel::class.java -> SuperuserViewModel(policyDB) InstallViewModel::class.java -> InstallViewModel(networkService) diff --git a/app/src/main/java/com/topjohnwu/magisk/ui/module/ModuleFragment.kt b/app/src/main/java/com/topjohnwu/magisk/ui/module/ModuleFragment.kt index 973aa8e48..fea37bc94 100644 --- a/app/src/main/java/com/topjohnwu/magisk/ui/module/ModuleFragment.kt +++ b/app/src/main/java/com/topjohnwu/magisk/ui/module/ModuleFragment.kt @@ -10,8 +10,6 @@ import androidx.recyclerview.widget.RecyclerView import com.topjohnwu.magisk.R import com.topjohnwu.magisk.arch.BaseUIFragment import com.topjohnwu.magisk.arch.ReselectionTarget -import com.topjohnwu.magisk.arch.ViewEvent -import com.topjohnwu.magisk.core.download.BaseDownloader import com.topjohnwu.magisk.databinding.FragmentModuleMd2Binding import com.topjohnwu.magisk.di.viewModel import com.topjohnwu.magisk.ktx.* @@ -42,13 +40,10 @@ class ModuleFragment : BaseUIFragment super.onStart() setHasOptionsMenu(true) activity.title = resources.getString(R.string.modules) - BaseDownloader.observeProgress(this, viewModel::onProgressUpdate) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - setEndlessScroller() - setEndlessSearch() binding.moduleFilterToggle.setOnClickListener { isFilterVisible = true @@ -116,13 +111,6 @@ class ModuleFragment : BaseUIFragment // --- - override fun onEventDispatched(event: ViewEvent) = when (event) { - is EndlessRecyclerScrollListener.ResetState -> listeners.forEach { it.resetState() } - else -> super.onEventDispatched(event) - } - - // --- - override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { inflater.inflate(R.menu.menu_module_md2, menu) } @@ -137,32 +125,12 @@ class ModuleFragment : BaseUIFragment // --- override fun onReselected() { - binding.moduleList - .also { it.scrollToPosition(10) } - .let { binding.moduleList } - .also { it.post { it.smoothScrollToPosition(0) } } + binding.moduleList.scrollToPosition(10) + binding.moduleList.also { it.post { it.smoothScrollToPosition(0) } } } // --- override fun onPreBind(binding: FragmentModuleMd2Binding) = Unit - private fun setEndlessScroller() { - val lama = binding.moduleList.layoutManager ?: return - lama.isAutoMeasureEnabled = false - - val listener = EndlessRecyclerScrollListener(lama, viewModel::loadRemote) - binding.moduleList.addOnScrollListener(listener) - listeners.add(listener) - } - - private fun setEndlessSearch() { - val lama = binding.moduleFilterInclude.moduleFilterList.layoutManager ?: return - lama.isAutoMeasureEnabled = false - - val listener = EndlessRecyclerScrollListener(lama, viewModel::loadMoreQuery) - binding.moduleFilterInclude.moduleFilterList.addOnScrollListener(listener) - listeners.add(listener) - } - } diff --git a/app/src/main/java/com/topjohnwu/magisk/ui/module/ModuleRvItem.kt b/app/src/main/java/com/topjohnwu/magisk/ui/module/ModuleRvItem.kt index 466f9de78..38178b9ec 100644 --- a/app/src/main/java/com/topjohnwu/magisk/ui/module/ModuleRvItem.kt +++ b/app/src/main/java/com/topjohnwu/magisk/ui/module/ModuleRvItem.kt @@ -39,34 +39,27 @@ class SectionTitle( override fun contentSameAs(other: SectionTitle): Boolean = this === other } -sealed class RepoItem(val item: OnlineModule) : ObservableItem() { +class OnlineModuleRvItem(val item: OnlineModule) : ObservableItem() { override val layoutRes: Int = R.layout.item_repo_md2 @get:Bindable var progress = 0 set(value) = set(value, field, { field = it }, BR.progress) - abstract val isUpdate: Boolean + var hasUpdate = false - override fun contentSameAs(other: RepoItem): Boolean = item == other.item - override fun itemSameAs(other: RepoItem): Boolean = item.id == other.item.id + override fun contentSameAs(other: OnlineModuleRvItem): Boolean = item == other.item + override fun itemSameAs(other: OnlineModuleRvItem): Boolean = item.id == other.item.id - class Update(item: OnlineModule) : RepoItem(item) { - override val isUpdate get() = true - } - - class Remote(item: OnlineModule) : RepoItem(item) { - override val isUpdate get() = false - } } -class ModuleItem(val item: LocalModule) : ObservableItem() { +class LocalModuleRvItem(val item: LocalModule) : ObservableItem() { override val layoutRes = R.layout.item_module_md2 @get:Bindable - var repo: OnlineModule? = null - set(value) = set(value, field, { field = it }, BR.repo) + var online: OnlineModule? = null + set(value) = set(value, field, { field = it }, BR.online) @get:Bindable var isEnabled = item.enable @@ -88,11 +81,10 @@ class ModuleItem(val item: LocalModule) : ObservableItem() { viewModel.updateActiveState() } - override fun contentSameAs(other: ModuleItem): Boolean = item.version == other.item.version + override fun contentSameAs(other: LocalModuleRvItem): Boolean = item.version == other.item.version && item.versionCode == other.item.versionCode && item.description == other.item.description && item.name == other.item.name - override fun itemSameAs(other: ModuleItem): Boolean = item.id == other.item.id + override fun itemSameAs(other: LocalModuleRvItem): Boolean = item.id == other.item.id } - diff --git a/app/src/main/java/com/topjohnwu/magisk/ui/module/ModuleViewModel.kt b/app/src/main/java/com/topjohnwu/magisk/ui/module/ModuleViewModel.kt index a4d38cf87..9c8560234 100644 --- a/app/src/main/java/com/topjohnwu/magisk/ui/module/ModuleViewModel.kt +++ b/app/src/main/java/com/topjohnwu/magisk/ui/module/ModuleViewModel.kt @@ -6,12 +6,10 @@ import androidx.lifecycle.viewModelScope import com.topjohnwu.magisk.BR import com.topjohnwu.magisk.R import com.topjohnwu.magisk.arch.* -import com.topjohnwu.magisk.core.Config import com.topjohnwu.magisk.core.Info -import com.topjohnwu.magisk.core.download.Subject import com.topjohnwu.magisk.core.model.module.LocalModule -import com.topjohnwu.magisk.core.tasks.RepoUpdater -import com.topjohnwu.magisk.data.database.RepoDao +import com.topjohnwu.magisk.core.model.module.OnlineModule +import com.topjohnwu.magisk.databinding.ComparableRvItem import com.topjohnwu.magisk.databinding.RvItem import com.topjohnwu.magisk.events.OpenReadmeEvent import com.topjohnwu.magisk.events.SelectModuleEvent @@ -19,37 +17,16 @@ import com.topjohnwu.magisk.events.SnackbarEvent import com.topjohnwu.magisk.events.dialog.ModuleInstallDialog import com.topjohnwu.magisk.ktx.addOnListChangedCallback import com.topjohnwu.magisk.ktx.reboot -import com.topjohnwu.magisk.utils.EndlessRecyclerScrollListener import com.topjohnwu.magisk.utils.set import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import me.tatarka.bindingcollectionadapter2.collections.MergeObservableList -import kotlin.math.roundToInt -/* -* The repo fetching behavior should follow these rules: -* -* For the first time the repo list is queried in the app, it should ALWAYS fetch for -* updates. However, this particular fetch should go through RepoUpdater.invoke(false), -* which internally will set ETAGs when doing GET requests to GitHub's API and will -* only update repo DB only if the GitHub API shows that something is changed remotely. -* -* When a user explicitly requests a full DB refresh, it should ALWAYS do a full force -* refresh, which in code can be done with RepoUpdater.invoke(true). This will update -* every single repo's information regardless whether GitHub's API shows if there is -* anything changed or not. -* */ - -class ModuleViewModel( - private val repoDB: RepoDao, - private val repoUpdater: RepoUpdater -) : BaseViewModel(), Queryable { +class ModuleViewModel : BaseViewModel(), Queryable { override val queryDelay = 1000L - private var queryJob: Job? = null - private var remoteJob: Job? = null @get:Bindable var isRemoteLoading = false @@ -67,57 +44,27 @@ class ModuleViewModel( var searchLoading = false set(value) = set(value, field, { field = it }, BR.searchLoading) - val itemsSearch = diffListOf() - val itemSearchBinding = itemBindingOf { + val itemsSearch = diffListOf>() + val itemSearchBinding = itemBindingOf> { it.bindExtra(BR.viewModel, this) } private val installSectionList = ObservableArrayList() - private val updatableSectionList = ObservableArrayList() - - private val itemsInstalled = diffListOf() - private val itemsUpdatable = diffListOf() - private val itemsOnline = diffListOf() - - private val sectionUpdate = SectionTitle( - R.string.module_section_pending, - R.string.module_section_pending_action, - R.drawable.ic_update_md2 - // enable with implementation of https://github.com/topjohnwu/Magisk/issues/2036 - ).also { it.hasButton = false } - + private val itemsInstalled = diffListOf() private val sectionInstalled = SectionTitle( R.string.module_installed, R.string.reboot, R.drawable.ic_restart ).also { it.hasButton = false } - private val sectionOnline = SectionTitle( - R.string.module_section_online, - R.string.sorting_order - ).apply { updateOrderIcon() } - val adapter = adapterOf() val items = MergeObservableList() - .also { if (Info.env.isActive) { - it.insertItem(InstallModule) - .insertList(updatableSectionList) - .insertList(itemsUpdatable) - .insertList(installSectionList) - .insertList(itemsInstalled) - } } - .insertItem(sectionOnline) - .insertList(itemsOnline) val itemBinding = itemBindingOf { it.bindExtra(BR.viewModel, this) } // --- - private var refetch = false - - // --- - init { itemsInstalled.addOnListChangedCallback( onItemRangeInserted = { _, _, _ -> @@ -129,131 +76,58 @@ class ModuleViewModel( installSectionList.clear() } ) - itemsUpdatable.addOnListChangedCallback( - onItemRangeInserted = { _, _, _ -> - if (updatableSectionList.isEmpty()) - updatableSectionList.add(sectionUpdate) - }, - onItemRangeRemoved = { list, _, _ -> - if (list.isEmpty()) - updatableSectionList.clear() - } - ) + + if (Info.env.isActive) { + items.insertItem(InstallModule) + .insertList(installSectionList) + .insertList(itemsInstalled) + } } // --- - fun onProgressUpdate(progress: Float, subject: Subject) { - if (subject !is Subject.Module) - return - - viewModelScope.launch { - val items = withContext(Dispatchers.Default) { - val predicate = { it: RepoItem -> it.item.id == subject.module.id } - itemsUpdatable.filter(predicate) + - itemsOnline.filter(predicate) + - itemsSearch.filter(predicate) - } - items.forEach { it.progress = progress.times(100).roundToInt() } - } - } - override fun refresh(): Job { return viewModelScope.launch { state = State.LOADING loadInstalled() - if (itemsOnline.isEmpty()) - loadRemote() state = State.LOADED } } - private fun SectionTitle.updateOrderIcon() { - hasButton = true - icon = when (Config.repoOrder) { - Config.Value.ORDER_NAME -> R.drawable.ic_order_name - Config.Value.ORDER_DATE -> R.drawable.ic_order_date - else -> return - } - } - private suspend fun loadInstalled() { - val installed = LocalModule.installed().map { ModuleItem(it) } + val installed = LocalModule.installed().map { LocalModuleRvItem(it) } val diff = withContext(Dispatchers.Default) { itemsInstalled.calculateDiff(installed) } itemsInstalled.update(installed, diff) } - private suspend fun loadUpdatable() { - val (updates, diff) = withContext(Dispatchers.IO) { - itemsInstalled.forEach { - launch { - it.repo = repoDB.getModule(it.item.id) - } - } - val updates = itemsInstalled - .mapNotNull { repoDB.getUpdatableModule(it.item.id, it.item.versionCode) } - .map { RepoItem.Update(it) } - val diff = itemsUpdatable.calculateDiff(updates) - return@withContext updates to diff - } - itemsUpdatable.update(updates, diff) - } - - fun loadRemote() { - // check for existing jobs - if (remoteJob?.isActive == true) - return - - if (itemsOnline.isEmpty()) - EndlessRecyclerScrollListener.ResetState().publish() - - remoteJob = viewModelScope.launch { - suspend fun loadRemoteDB(offset: Int) = withContext(Dispatchers.IO) { - repoDB.getModules(offset).map { RepoItem.Remote(it) } - } - - isRemoteLoading = true - val repos = if (itemsOnline.isEmpty()) { - repoUpdater.run(refetch) - loadUpdatable() - loadRemoteDB(0) - } else { - loadRemoteDB(itemsOnline.size) - } - isRemoteLoading = false - refetch = false - queryHandler.post { itemsOnline.addAll(repos) } - } - } - fun forceRefresh() { - itemsOnline.clear() - itemsUpdatable.clear() - itemsSearch.clear() - refetch = true + itemsInstalled.clear() refresh() submitQuery() } // --- - private suspend fun queryInternal(query: String, offset: Int): List { + private suspend fun queryInternal(query: String): List> { return if (query.isBlank()) { itemsSearch.clear() listOf() } else { - withContext(Dispatchers.IO) { - repoDB.searchModules(query, offset).map { RepoItem.Remote(it) } + withContext(Dispatchers.Default) { + itemsInstalled.filter { + it.item.id.contains(query, true) + || it.item.name.contains(query, true) + || it.item.description.contains(query, true) + } } } } override fun query() { - EndlessRecyclerScrollListener.ResetState().publish() - queryJob = viewModelScope.launch { - val searched = queryInternal(query, 0) + viewModelScope.launch { + val searched = queryInternal(query) val diff = withContext(Dispatchers.Default) { itemsSearch.calculateDiff(searched) } @@ -262,59 +136,28 @@ class ModuleViewModel( } } - fun loadMoreQuery() { - if (queryJob?.isActive == true) return - queryJob = viewModelScope.launch { - val searched = queryInternal(query, itemsSearch.size) - queryHandler.post { itemsSearch.addAll(searched) } - } - } - // --- - fun updateActiveState() = viewModelScope.launch { - sectionInstalled.hasButton = withContext(Dispatchers.Default) { - itemsInstalled.any { it.isModified } - } + fun updateActiveState() { + sectionInstalled.hasButton = itemsInstalled.any { it.isModified } } fun sectionPressed(item: SectionTitle) = when (item) { - sectionInstalled -> reboot() // TODO add reboot picker, regular reboot is not always preferred - sectionOnline -> { - Config.repoOrder = when (Config.repoOrder) { - Config.Value.ORDER_NAME -> Config.Value.ORDER_DATE - Config.Value.ORDER_DATE -> Config.Value.ORDER_NAME - else -> Config.Value.ORDER_NAME - } - sectionOnline.updateOrderIcon() - queryHandler.post { - itemsOnline.clear() - loadRemote() - } - Unit - } + sectionInstalled -> reboot() else -> Unit } - fun downloadPressed(item: RepoItem) = if (isConnected.get()) withExternalRW { - ModuleInstallDialog(item.item).publish() - } else { - SnackbarEvent(R.string.no_connection).publish() - } + // The following methods are not used, but kept for future integration - fun installPressed() = withExternalRW { - SelectModuleEvent().publish() - } + fun downloadPressed(item: OnlineModule) = + if (isConnected.get()) withExternalRW { ModuleInstallDialog(item).publish() } + else { SnackbarEvent(R.string.no_connection).publish() } - fun infoPressed(item: RepoItem) = - if (isConnected.get()) OpenReadmeEvent(item.item).publish() + fun installPressed() = withExternalRW { SelectModuleEvent().publish() } + + fun infoPressed(item: OnlineModule) = + if (isConnected.get()) OpenReadmeEvent(item).publish() else SnackbarEvent(R.string.no_connection).publish() - - fun infoPressed(item: ModuleItem) { - item.repo?.also { - if (isConnected.get()) OpenReadmeEvent(it).publish() - else SnackbarEvent(R.string.no_connection).publish() - } ?: return - } + fun infoPressed(item: LocalModuleRvItem) { infoPressed(item.online ?: return) } } diff --git a/app/src/main/res/layout/item_module_md2.xml b/app/src/main/res/layout/item_module_md2.xml index 7da6a25c9..661a54ac5 100644 --- a/app/src/main/res/layout/item_module_md2.xml +++ b/app/src/main/res/layout/item_module_md2.xml @@ -11,7 +11,7 @@ + type="com.topjohnwu.magisk.ui.module.LocalModuleRvItem" /> + type="com.topjohnwu.magisk.ui.module.OnlineModuleRvItem" /> @@ -102,7 +101,7 @@ android:layout_width="wrap_content" android:alpha=".5" android:nextFocusLeft="@id/module_card" - android:onClick="@{() -> viewModel.infoPressed(item)}" + android:onClick="@{() -> viewModel.infoPressed(item.item)}" android:paddingEnd="@dimen/l_50" app:layout_constraintBottom_toBottomOf="@+id/module_download" app:layout_constraintEnd_toStartOf="@+id/module_download" @@ -113,11 +112,11 @@ android:id="@+id/module_download" style="@style/WidgetFoundation.Icon.Primary" isEnabled="@{!(item.progress == -100 || (item.progress > 0 && item.progress < 100))}" - srcCompat="@{item.isUpdate ? R.drawable.ic_update_md2 : R.drawable.ic_download_md2}" + srcCompat="@{item.hasUpdate ? R.drawable.ic_update_md2 : R.drawable.ic_download_md2}" android:layout_width="wrap_content" android:contentDescription="@string/download" android:nextFocusLeft="@id/module_info" - android:onClick="@{() -> viewModel.downloadPressed(item)}" + android:onClick="@{() -> viewModel.downloadPressed(item.item)}" android:paddingStart="@dimen/l_50" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" @@ -154,4 +153,3 @@ -