mirror of
https://github.com/topjohnwu/Magisk.git
synced 2025-10-21 05:51:13 +00:00
Updated modules and repos screen
Screens are merged via common viewModel, all data are immediately accessible to both of them
This commit is contained in:
@@ -6,4 +6,5 @@ import org.koin.dsl.module
|
||||
|
||||
val databaseModule = module {
|
||||
single { get<App>().DB }
|
||||
single { get<App>().repoDB }
|
||||
}
|
||||
|
@@ -3,6 +3,7 @@ package com.topjohnwu.magisk.di
|
||||
import com.topjohnwu.magisk.ui.MainViewModel
|
||||
import com.topjohnwu.magisk.ui.hide.HideViewModel
|
||||
import com.topjohnwu.magisk.ui.home.HomeViewModel
|
||||
import com.topjohnwu.magisk.ui.module.ModuleViewModel
|
||||
import com.topjohnwu.magisk.ui.superuser.SuperuserViewModel
|
||||
import org.koin.androidx.viewmodel.dsl.viewModel
|
||||
import org.koin.dsl.module
|
||||
@@ -13,4 +14,5 @@ val viewModelModules = module {
|
||||
viewModel { HomeViewModel(get(), get()) }
|
||||
viewModel { SuperuserViewModel(get(), get(), get(), get()) }
|
||||
viewModel { HideViewModel(get(), get()) }
|
||||
viewModel { ModuleViewModel(get()) }
|
||||
}
|
||||
|
@@ -0,0 +1,66 @@
|
||||
package com.topjohnwu.magisk.model.entity.recycler
|
||||
|
||||
import android.content.res.Resources
|
||||
import androidx.annotation.StringRes
|
||||
import com.skoumal.teanity.databinding.ComparableRvItem
|
||||
import com.skoumal.teanity.extensions.addOnPropertyChangedCallback
|
||||
import com.skoumal.teanity.util.KObservableField
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.model.entity.Module
|
||||
import com.topjohnwu.magisk.model.entity.Repo
|
||||
import com.topjohnwu.magisk.utils.get
|
||||
import com.topjohnwu.magisk.utils.toggle
|
||||
|
||||
class ModuleRvItem(val item: Module) : ComparableRvItem<ModuleRvItem>() {
|
||||
|
||||
override val layoutRes: Int = R.layout.item_module
|
||||
|
||||
val lastActionNotice = KObservableField("")
|
||||
val isChecked = KObservableField(item.isEnabled)
|
||||
val isDeletable = KObservableField(item.willBeRemoved())
|
||||
|
||||
init {
|
||||
isChecked.addOnPropertyChangedCallback {
|
||||
when (it) {
|
||||
true -> item.removeDisableFile().notice(R.string.disable_file_removed)
|
||||
false -> item.createDisableFile().notice(R.string.disable_file_created)
|
||||
}
|
||||
}
|
||||
isDeletable.addOnPropertyChangedCallback {
|
||||
when (it) {
|
||||
true -> item.createRemoveFile().notice(R.string.remove_file_created)
|
||||
false -> item.deleteRemoveFile().notice(R.string.remove_file_deleted)
|
||||
}
|
||||
}
|
||||
when {
|
||||
item.isUpdated -> notice(R.string.update_file_created)
|
||||
item.willBeRemoved() -> notice(R.string.remove_file_created)
|
||||
}
|
||||
}
|
||||
|
||||
fun toggle() = isChecked.toggle()
|
||||
fun toggleDelete() = isDeletable.toggle()
|
||||
|
||||
@Suppress("unused")
|
||||
private fun Any.notice(@StringRes info: Int) {
|
||||
lastActionNotice.value = get<Resources>().getString(info)
|
||||
}
|
||||
|
||||
override fun contentSameAs(other: ModuleRvItem): Boolean = item.version == other.item.version
|
||||
&& item.versionCode == other.item.versionCode
|
||||
&& item.description == other.item.description
|
||||
|
||||
override fun itemSameAs(other: ModuleRvItem): Boolean = item.name == other.item.name
|
||||
}
|
||||
|
||||
class RepoRvItem(val item: Repo) : ComparableRvItem<RepoRvItem>() {
|
||||
|
||||
override val layoutRes: Int = R.layout.item_repo
|
||||
|
||||
override fun contentSameAs(other: RepoRvItem): Boolean = item.version == other.item.version
|
||||
&& item.lastUpdate == other.item.lastUpdate
|
||||
&& item.versionCode == other.item.versionCode
|
||||
&& item.description == other.item.description
|
||||
|
||||
override fun itemSameAs(other: RepoRvItem): Boolean = item.detailUrl == other.item.detailUrl
|
||||
}
|
@@ -2,6 +2,7 @@ package com.topjohnwu.magisk.model.events
|
||||
|
||||
import com.skoumal.teanity.rxbus.RxBus
|
||||
import com.topjohnwu.magisk.model.entity.recycler.HideProcessRvItem
|
||||
import com.topjohnwu.magisk.model.entity.recycler.ModuleRvItem
|
||||
import com.topjohnwu.magisk.model.entity.recycler.PolicyRvItem
|
||||
|
||||
class HideProcessEvent(val item: HideProcessRvItem) : RxBus.Event
|
||||
@@ -11,3 +12,5 @@ sealed class PolicyUpdateEvent(val item: PolicyRvItem) : RxBus.Event {
|
||||
class Notification(item: PolicyRvItem) : PolicyUpdateEvent(item)
|
||||
class Log(item: PolicyRvItem) : PolicyUpdateEvent(item)
|
||||
}
|
||||
|
||||
class ModuleUpdatedEvent(val item: ModuleRvItem) : RxBus.Event
|
||||
|
@@ -2,6 +2,7 @@ package com.topjohnwu.magisk.model.events
|
||||
|
||||
import android.app.Activity
|
||||
import com.skoumal.teanity.viewevents.ViewEvent
|
||||
import com.topjohnwu.magisk.model.entity.Repo
|
||||
|
||||
|
||||
data class OpenLinkEvent(val url: String) : ViewEvent()
|
||||
@@ -17,4 +18,9 @@ class EnvFixEvent : ViewEvent()
|
||||
|
||||
class UpdateSafetyNetEvent : ViewEvent()
|
||||
|
||||
class ViewActionEvent(val action: Activity.() -> Unit) : ViewEvent()
|
||||
class ViewActionEvent(val action: Activity.() -> Unit) : ViewEvent()
|
||||
|
||||
class OpenFilePickerEvent : ViewEvent()
|
||||
|
||||
class OpenChangelogEvent(val item: Repo) : ViewEvent()
|
||||
class InstallModuleEvent(val item: Repo) : ViewEvent()
|
@@ -17,6 +17,9 @@ abstract class MagiskLeanbackActivity<ViewModel : MagiskViewModel, Binding : Vie
|
||||
|
||||
private val resultListeners = SparseArrayCompat<BaseActivity.ActivityResultListener>()
|
||||
|
||||
@Deprecated("Permissions will be checked in a different streamlined way")
|
||||
fun runWithExternalRW(callback: () -> Unit) = runWithExternalRW(Runnable { callback() })
|
||||
|
||||
@Deprecated("Permissions will be checked in a different streamlined way")
|
||||
override fun runWithExternalRW(callback: Runnable) {
|
||||
runWithPermissions(Manifest.permission.WRITE_EXTERNAL_STORAGE, callback = callback)
|
||||
|
@@ -0,0 +1,81 @@
|
||||
package com.topjohnwu.magisk.ui.module
|
||||
|
||||
import android.database.Cursor
|
||||
import com.skoumal.teanity.databinding.ComparableRvItem
|
||||
import com.skoumal.teanity.extensions.subscribeK
|
||||
import com.skoumal.teanity.util.DiffObservableList
|
||||
import com.skoumal.teanity.util.KObservableField
|
||||
import com.topjohnwu.magisk.BR
|
||||
import com.topjohnwu.magisk.data.database.RepoDatabaseHelper
|
||||
import com.topjohnwu.magisk.model.entity.Module
|
||||
import com.topjohnwu.magisk.model.entity.Repo
|
||||
import com.topjohnwu.magisk.model.entity.recycler.ModuleRvItem
|
||||
import com.topjohnwu.magisk.model.entity.recycler.RepoRvItem
|
||||
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.UpdateRepos
|
||||
import com.topjohnwu.magisk.ui.base.MagiskViewModel
|
||||
import com.topjohnwu.magisk.utils.Event
|
||||
import com.topjohnwu.magisk.utils.Utils
|
||||
import io.reactivex.Single
|
||||
import me.tatarka.bindingcollectionadapter2.OnItemBind
|
||||
|
||||
class ModuleViewModel(
|
||||
private val repoDatabase: RepoDatabaseHelper
|
||||
) : MagiskViewModel() {
|
||||
|
||||
val query = KObservableField("")
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
init {
|
||||
Event.register(this)
|
||||
refresh()
|
||||
}
|
||||
|
||||
override fun getListeningEvents(): IntArray {
|
||||
return intArrayOf(Event.MODULE_LOAD_DONE, Event.REPO_LOAD_DONE)
|
||||
}
|
||||
|
||||
override fun onEvent(event: Int) = when (event) {
|
||||
Event.MODULE_LOAD_DONE -> updateModules(Event.getResult(event))
|
||||
Event.REPO_LOAD_DONE -> updateRepos()
|
||||
else -> Unit
|
||||
}
|
||||
|
||||
fun fabPressed() = OpenFilePickerEvent().publish()
|
||||
fun repoPressed(item: RepoRvItem) = OpenChangelogEvent(item.item).publish()
|
||||
fun downloadPressed(item: RepoRvItem) = InstallModuleEvent(item.item).publish()
|
||||
|
||||
fun refresh() {
|
||||
state = State.LOADING
|
||||
Utils.loadModules(true)
|
||||
UpdateRepos().exec(true)
|
||||
}
|
||||
|
||||
private fun updateModules(result: Map<String, Module>) = result.values
|
||||
.map { ModuleRvItem(it) }
|
||||
.let { itemsInstalled.update(it) }
|
||||
|
||||
internal fun updateRepos() {
|
||||
Single.fromCallable { repoDatabase.repoCursor.toList { Repo(it) } }
|
||||
.flattenAsFlowable { it }
|
||||
.map { RepoRvItem(it) }
|
||||
.toList()
|
||||
.applyViewModel(this)
|
||||
.subscribeK { itemsRemote.update(it) }
|
||||
}
|
||||
|
||||
private fun <Result> Cursor.toList(transformer: (Cursor) -> Result): List<Result> {
|
||||
val out = mutableListOf<Result>()
|
||||
while (moveToNext()) out.add(transformer(this))
|
||||
return out
|
||||
}
|
||||
|
||||
}
|
@@ -1,141 +0,0 @@
|
||||
package com.topjohnwu.magisk.ui.module;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.topjohnwu.magisk.ClassMap;
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.model.adapters.ModulesAdapter;
|
||||
import com.topjohnwu.magisk.model.entity.Module;
|
||||
import com.topjohnwu.magisk.ui.base.BaseFragment;
|
||||
import com.topjohnwu.magisk.ui.flash.FlashActivity;
|
||||
import com.topjohnwu.magisk.utils.Event;
|
||||
import com.topjohnwu.magisk.utils.RootUtils;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||
import butterknife.BindView;
|
||||
import butterknife.OnClick;
|
||||
|
||||
public class ModulesFragment extends BaseFragment {
|
||||
|
||||
@BindView(R.id.swipeRefreshLayout) SwipeRefreshLayout mSwipeRefreshLayout;
|
||||
@BindView(R.id.recyclerView) RecyclerView recyclerView;
|
||||
@BindView(R.id.empty_rv) TextView emptyRv;
|
||||
|
||||
@OnClick(R.id.fab)
|
||||
void selectFile() {
|
||||
runWithExternalRW(() -> {
|
||||
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
|
||||
intent.setType("application/zip");
|
||||
startActivityForResult(intent, Const.ID.FETCH_ZIP);
|
||||
});
|
||||
}
|
||||
|
||||
private List<Module> listModules = new ArrayList<>();
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.fragment_modules, container, false);
|
||||
unbinder = new ModulesFragment_ViewBinding(this, view);
|
||||
setHasOptionsMenu(true);
|
||||
|
||||
mSwipeRefreshLayout.setOnRefreshListener(() -> {
|
||||
recyclerView.setVisibility(View.GONE);
|
||||
Utils.loadModules();
|
||||
});
|
||||
|
||||
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
|
||||
@Override
|
||||
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
|
||||
mSwipeRefreshLayout.setEnabled(recyclerView.getChildAt(0).getTop() >= 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
|
||||
super.onScrollStateChanged(recyclerView, newState);
|
||||
}
|
||||
});
|
||||
|
||||
requireActivity().setTitle(R.string.modules);
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] getListeningEvents() {
|
||||
return new int[] {Event.MODULE_LOAD_DONE};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEvent(int event) {
|
||||
updateUI(Event.getResult(event));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
if (requestCode == Const.ID.FETCH_ZIP && resultCode == Activity.RESULT_OK && data != null) {
|
||||
// Get the URI of the selected file
|
||||
Intent intent = new Intent(getActivity(), ClassMap.get(FlashActivity.class));
|
||||
intent.setData(data.getData()).putExtra(Const.Key.FLASH_ACTION, Const.Value.FLASH_ZIP);
|
||||
startActivity(intent);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
inflater.inflate(R.menu.menu_reboot, menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.reboot:
|
||||
RootUtils.reboot();
|
||||
return true;
|
||||
case R.id.reboot_recovery:
|
||||
Shell.su("/system/bin/reboot recovery").submit();
|
||||
return true;
|
||||
case R.id.reboot_bootloader:
|
||||
Shell.su("/system/bin/reboot bootloader").submit();
|
||||
return true;
|
||||
case R.id.reboot_download:
|
||||
Shell.su("/system/bin/reboot download").submit();
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void updateUI(Map<String, Module> moduleMap) {
|
||||
listModules.clear();
|
||||
listModules.addAll(moduleMap.values());
|
||||
if (listModules.size() == 0) {
|
||||
emptyRv.setVisibility(View.VISIBLE);
|
||||
recyclerView.setVisibility(View.GONE);
|
||||
} else {
|
||||
emptyRv.setVisibility(View.GONE);
|
||||
recyclerView.setVisibility(View.VISIBLE);
|
||||
recyclerView.setAdapter(new ModulesAdapter(listModules));
|
||||
}
|
||||
mSwipeRefreshLayout.setRefreshing(false);
|
||||
}
|
||||
}
|
@@ -0,0 +1,115 @@
|
||||
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.skoumal.teanity.viewevents.ViewEvent
|
||||
import com.topjohnwu.magisk.ClassMap
|
||||
import com.topjohnwu.magisk.Const
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.databinding.FragmentModulesBinding
|
||||
import com.topjohnwu.magisk.model.events.OpenFilePickerEvent
|
||||
import com.topjohnwu.magisk.ui.base.MagiskFragment
|
||||
import com.topjohnwu.magisk.ui.flash.FlashActivity
|
||||
import com.topjohnwu.magisk.utils.RootUtils
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import org.koin.androidx.viewmodel.ext.android.sharedViewModel
|
||||
|
||||
class ModulesFragment : MagiskFragment<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 = Intent(activity, ClassMap.get<Any>(FlashActivity::class.java))
|
||||
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 -> {
|
||||
RootUtils.reboot()
|
||||
return true
|
||||
}
|
||||
R.id.reboot_recovery -> {
|
||||
Shell.su("/system/bin/reboot recovery").submit()
|
||||
return true
|
||||
}
|
||||
R.id.reboot_bootloader -> {
|
||||
Shell.su("/system/bin/reboot bootloader").submit()
|
||||
return true
|
||||
}
|
||||
R.id.reboot_download -> {
|
||||
Shell.su("/system/bin/reboot download").submit()
|
||||
return true
|
||||
}
|
||||
else -> return false
|
||||
}
|
||||
}
|
||||
|
||||
private fun selectFile() {
|
||||
magiskActivity.runWithExternalRW {
|
||||
val intent = Intent(Intent.ACTION_GET_CONTENT)
|
||||
intent.type = "application/zip"
|
||||
startActivityForResult(intent, Const.ID.FETCH_ZIP)
|
||||
}
|
||||
}
|
||||
|
||||
/*override fun getListeningEvents(): IntArray {
|
||||
return intArrayOf(Event.MODULE_LOAD_DONE)
|
||||
}
|
||||
|
||||
override fun onEvent(event: Int) {
|
||||
updateUI(Event.getResult(event))
|
||||
}*/
|
||||
|
||||
/*private fun updateUI(moduleMap: Map<String, Module>) {
|
||||
listModules.clear()
|
||||
listModules.addAll(moduleMap.values)
|
||||
if (listModules.size == 0) {
|
||||
emptyRv!!.visibility = View.VISIBLE
|
||||
recyclerView!!.visibility = View.GONE
|
||||
} else {
|
||||
emptyRv!!.visibility = View.GONE
|
||||
recyclerView!!.visibility = View.VISIBLE
|
||||
recyclerView!!.adapter = ModulesAdapter(listModules)
|
||||
}
|
||||
mSwipeRefreshLayout!!.isRefreshing = false
|
||||
}*/
|
||||
}
|
@@ -1,101 +0,0 @@
|
||||
package com.topjohnwu.magisk.ui.module;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.SearchView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.topjohnwu.magisk.Config;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.model.adapters.ReposAdapter;
|
||||
import com.topjohnwu.magisk.tasks.UpdateRepos;
|
||||
import com.topjohnwu.magisk.ui.base.BaseFragment;
|
||||
import com.topjohnwu.magisk.utils.Event;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||
import butterknife.BindView;
|
||||
|
||||
public class ReposFragment extends BaseFragment {
|
||||
|
||||
@BindView(R.id.recyclerView) RecyclerView recyclerView;
|
||||
@BindView(R.id.empty_rv) TextView emptyRv;
|
||||
@BindView(R.id.swipeRefreshLayout) SwipeRefreshLayout mSwipeRefreshLayout;
|
||||
|
||||
private ReposAdapter adapter;
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setHasOptionsMenu(true);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.fragment_repos, container, false);
|
||||
unbinder = new ReposFragment_ViewBinding(this, view);
|
||||
|
||||
mSwipeRefreshLayout.setRefreshing(true);
|
||||
mSwipeRefreshLayout.setOnRefreshListener(() -> new UpdateRepos().exec(true));
|
||||
|
||||
adapter = new ReposAdapter();
|
||||
recyclerView.setAdapter(adapter);
|
||||
recyclerView.setVisibility(View.GONE);
|
||||
|
||||
requireActivity().setTitle(R.string.downloads);
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
Event.unregister(adapter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] getListeningEvents() {
|
||||
return new int[] {Event.REPO_LOAD_DONE};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEvent(int event) {
|
||||
adapter.notifyDBChanged(false);
|
||||
Event.register(adapter);
|
||||
mSwipeRefreshLayout.setRefreshing(false);
|
||||
boolean empty = adapter.getItemCount() == 0;
|
||||
recyclerView.setVisibility(empty ? View.GONE : View.VISIBLE);
|
||||
emptyRv.setVisibility(empty ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
inflater.inflate(R.menu.menu_repo, menu);
|
||||
SearchView search = (SearchView) menu.findItem(R.id.repo_search).getActionView();
|
||||
adapter.setSearchView(search);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
if (item.getItemId() == R.id.repo_sort) {
|
||||
new AlertDialog.Builder(getActivity())
|
||||
.setTitle(R.string.sorting_order)
|
||||
.setSingleChoiceItems(R.array.sorting_orders,
|
||||
Config.get(Config.Key.REPO_ORDER), (d, which) -> {
|
||||
Config.set(Config.Key.REPO_ORDER, which);
|
||||
adapter.notifyDBChanged(true);
|
||||
d.dismiss();
|
||||
}).show();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
@@ -0,0 +1,104 @@
|
||||
package com.topjohnwu.magisk.ui.module
|
||||
|
||||
import android.app.AlertDialog
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import android.widget.SearchView
|
||||
import com.skoumal.teanity.viewevents.ViewEvent
|
||||
import com.topjohnwu.magisk.ClassMap
|
||||
import com.topjohnwu.magisk.Config
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.databinding.FragmentReposBinding
|
||||
import com.topjohnwu.magisk.model.download.DownloadModuleService
|
||||
import com.topjohnwu.magisk.model.entity.Repo
|
||||
import com.topjohnwu.magisk.model.events.InstallModuleEvent
|
||||
import com.topjohnwu.magisk.model.events.OpenChangelogEvent
|
||||
import com.topjohnwu.magisk.ui.base.MagiskFragment
|
||||
import com.topjohnwu.magisk.view.MarkDownWindow
|
||||
import com.topjohnwu.magisk.view.dialogs.CustomAlertDialog
|
||||
import org.koin.androidx.viewmodel.ext.android.sharedViewModel
|
||||
|
||||
class ReposFragment : MagiskFragment<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)
|
||||
(menu.findItem(R.id.repo_search).actionView as? SearchView)
|
||||
?.setOnQueryTextListener(this)
|
||||
}
|
||||
|
||||
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.get<Int>(Config.Key.REPO_ORDER)!!
|
||||
) { d, which ->
|
||||
Config.set(Config.Key.REPO_ORDER, which)
|
||||
viewModel.updateRepos()
|
||||
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(context, null, item.detailUrl)
|
||||
}
|
||||
|
||||
private fun installModule(item: Repo) {
|
||||
val context = magiskActivity
|
||||
|
||||
fun download(install: Boolean) {
|
||||
context.runWithExternalRW {
|
||||
val intent = Intent(activity, ClassMap.get<Any>(DownloadModuleService::class.java))
|
||||
.putExtra("repo", item).putExtra("install", install)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
context.startForegroundService(intent) //hmm, service starts itself in foreground, this seems unnecessary
|
||||
} else {
|
||||
context.startService(intent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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) }
|
||||
.setNegativeButton(R.string.no_thanks, null)
|
||||
.show()
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user