diff --git a/app/build.gradle.kts b/app/build.gradle.kts index a8185d57..13e09553 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -197,12 +197,15 @@ dependencies { val glideVersion = "4.12.0" val markwonVersion = "4.6.2" val okhttpVersion = "4.9.1" + val navVersion = "2.3.5" annotationProcessor("com.github.bumptech.glide:compiler:$glideVersion") implementation("androidx.activity:activity:1.2.3") implementation("androidx.browser:browser:1.3.0") implementation("androidx.constraintlayout:constraintlayout:2.0.4") implementation("androidx.core:core:1.5.0") implementation("androidx.fragment:fragment:1.3.4") + implementation("androidx.navigation:navigation-fragment:$navVersion") + implementation("androidx.navigation:navigation-ui:$navVersion") implementation("androidx.recyclerview:recyclerview:1.2.0") implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0") implementation("com.caverock:androidsvg-aar:1.4") diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1c30ab60..f917a880 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -26,7 +26,8 @@ - @@ -45,45 +46,24 @@ android:theme="@style/AppTheme" tools:ignore="AllowBackup,GoogleAppIndexingWarning" tools:targetApi="q"> - - - - - + - - - - - - - - + android:name=".ui.activity.CrashReportActivity" + android:process=":error_activity" /> implements Filterable, Handler.Callback { - private final AppListActivity activity; + private final Activity activity; + private final AppListFragment fragment; private final PackageManager pm; private final SharedPreferences preferences; private final Handler loadAppListHandler; @@ -121,9 +123,9 @@ public class ScopeAdapter extends RecyclerView.Adapter @Override public void run() { synchronized (this) { - activity.binding.progress.setIndeterminate(false); - activity.binding.swipeRefreshLayout.setRefreshing(false); - String queryStr = activity.searchView != null ? activity.searchView.getQuery().toString() : ""; + fragment.binding.progress.setIndeterminate(false); + fragment.binding.swipeRefreshLayout.setRefreshing(false); + String queryStr = fragment.searchView != null ? fragment.searchView.getQuery().toString() : ""; getFilter().filter(queryStr); this.notify(); } @@ -134,8 +136,9 @@ public class ScopeAdapter extends RecyclerView.Adapter private boolean refreshing = false; private boolean enabled = true; - public ScopeAdapter(AppListActivity activity, ModuleUtil.InstalledModule module) { - this.activity = activity; + public ScopeAdapter(AppListFragment fragment, ModuleUtil.InstalledModule module) { + this.fragment = fragment; + this.activity = fragment.requireActivity(); this.module = module; moduleUtil = ModuleUtil.getInstance(); HandlerThread handlerThread = new HandlerThread("appList"); @@ -250,12 +253,12 @@ public class ScopeAdapter extends RecyclerView.Adapter if (launchIntent != null) { ConfigManager.startActivityAsUserWithFeature(launchIntent, module.userId); } else { - activity.makeSnackBar(R.string.module_no_ui, Snackbar.LENGTH_LONG); + fragment.makeSnackBar(R.string.module_no_ui, Snackbar.LENGTH_LONG); } return true; } else if (itemId == R.id.backup) { Calendar now = Calendar.getInstance(); - activity.backupLauncher.launch(String.format(Locale.US, + fragment.backupLauncher.launch(String.format(Locale.US, "%s_%04d%02d%02d_%02d%02d%02d.lsp", module.getAppName(), now.get(Calendar.YEAR), now.get(Calendar.MONTH) + 1, @@ -263,7 +266,7 @@ public class ScopeAdapter extends RecyclerView.Adapter now.get(Calendar.MINUTE), now.get(Calendar.SECOND))); return true; } else if (itemId == R.id.restore) { - activity.restoreLauncher.launch(new String[]{"*/*"}); + fragment.restoreLauncher.launch(new String[]{"*/*"}); return true; } else if (!AppHelper.onOptionsItemSelected(item, preferences)) { return false; @@ -284,7 +287,7 @@ public class ScopeAdapter extends RecyclerView.Adapter ConfigManager.startActivityAsUserWithFeature(launchIntent, module.userId); } } else if (itemId == R.id.menu_compile_speed) { - CompileDialogFragment.speed(activity.getSupportFragmentManager(), info); + CompileDialogFragment.speed(fragment.getChildFragmentManager(), info); } else if (itemId == R.id.menu_other_app) { var intent = new Intent(Intent.ACTION_SHOW_APP_INFO); intent.putExtra(Intent.EXTRA_PACKAGE_NAME, module.packageName); @@ -309,8 +312,7 @@ public class ScopeAdapter extends RecyclerView.Adapter return true; } - public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) { - inflater.inflate(R.menu.menu_app_list, menu); + public void onPrepareOptionsMenu(@NonNull Menu menu) { Intent intent = AppHelper.getSettingsIntent(module.packageName, module.userId); if (intent == null) { menu.removeItem(R.id.menu_launch); @@ -448,14 +450,14 @@ public class ScopeAdapter extends RecyclerView.Adapter } loadAppListHandler.removeMessages(0); if (!force) { - activity.binding.progress.setVisibility(View.INVISIBLE); - activity.binding.progress.setIndeterminate(true); - activity.binding.progress.setVisibility(View.VISIBLE); + fragment.binding.progress.setVisibility(View.INVISIBLE); + fragment.binding.progress.setIndeterminate(true); + fragment.binding.progress.setVisibility(View.VISIBLE); } enabled = moduleUtil.isModuleEnabled(module.packageName); - activity.binding.masterSwitch.setOnCheckedChangeListener(null); - activity.binding.masterSwitch.setChecked(enabled); - activity.binding.masterSwitch.setOnCheckedChangeListener(switchBarOnCheckedChangeListener); + fragment.binding.masterSwitch.setOnCheckedChangeListener(null); + fragment.binding.masterSwitch.setChecked(enabled); + fragment.binding.masterSwitch.setOnCheckedChangeListener(switchBarOnCheckedChangeListener); loadAppListHandler.sendMessage(Message.obtain(loadAppListHandler, 0, force)); } @@ -466,7 +468,7 @@ public class ScopeAdapter extends RecyclerView.Adapter checkedList.remove(appInfo.application); } if (!ConfigManager.setModuleScope(module.packageName, checkedList)) { - activity.makeSnackBar(R.string.failed_to_save_scope_list, Snackbar.LENGTH_SHORT); + fragment.makeSnackBar(R.string.failed_to_save_scope_list, Snackbar.LENGTH_SHORT); if (!isChecked) { checkedList.add(appInfo.application); } else { @@ -474,7 +476,7 @@ public class ScopeAdapter extends RecyclerView.Adapter } buttonView.setChecked(!isChecked); } else if (appInfo.packageName.equals("android")) { - Snackbar.make(activity.binding.snackbar, R.string.reboot_required, Snackbar.LENGTH_SHORT) + Snackbar.make(fragment.binding.snackbar, R.string.reboot_required, Snackbar.LENGTH_SHORT) .setAction(R.string.reboot, v -> ConfigManager.reboot(false, null, false)) .show(); } @@ -619,7 +621,7 @@ public class ScopeAdapter extends RecyclerView.Adapter } public boolean onBackPressed() { - if (!refreshing && activity.binding.masterSwitch.isChecked() && checkedList.isEmpty()) { + if (!refreshing && fragment.binding.masterSwitch.isChecked() && checkedList.isEmpty()) { AlertDialog.Builder builder = new AlertDialog.Builder(activity); builder.setMessage(!recommendedList.isEmpty() ? R.string.no_scope_selected_has_recommended : R.string.no_scope_selected); if (!recommendedList.isEmpty()) { diff --git a/app/src/main/java/org/lsposed/manager/ui/activity/MainActivity.java b/app/src/main/java/org/lsposed/manager/ui/activity/MainActivity.java index cd091607..51b89081 100644 --- a/app/src/main/java/org/lsposed/manager/ui/activity/MainActivity.java +++ b/app/src/main/java/org/lsposed/manager/ui/activity/MainActivity.java @@ -1,157 +1,126 @@ -/* - * This file is part of LSPosed. - * - * LSPosed is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * LSPosed is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with LSPosed. If not, see . - * - * Copyright (C) 2020 EdXposed Contributors - * Copyright (C) 2021 LSPosed Contributors - */ - package org.lsposed.manager.ui.activity; +import android.annotation.SuppressLint; +import android.content.Context; import android.content.Intent; -import android.os.Build; import android.os.Bundle; -import android.text.method.LinkMovementMethod; -import android.view.LayoutInflater; -import android.view.View; +import android.text.TextUtils; +import android.view.KeyEvent; +import android.view.MotionEvent; -import androidx.core.text.HtmlCompat; +import androidx.annotation.NonNull; +import androidx.navigation.NavController; +import androidx.navigation.Navigation; +import androidx.navigation.fragment.NavHostFragment; -import com.bumptech.glide.Glide; -import com.google.android.material.snackbar.Snackbar; - -import org.lsposed.manager.BuildConfig; -import org.lsposed.manager.ConfigManager; import org.lsposed.manager.R; import org.lsposed.manager.databinding.ActivityMainBinding; -import org.lsposed.manager.databinding.DialogAboutBinding; import org.lsposed.manager.ui.activity.base.BaseActivity; -import org.lsposed.manager.ui.dialog.BlurBehindDialogBuilder; -import org.lsposed.manager.ui.dialog.InfoDialogBuilder; -import org.lsposed.manager.util.GlideHelper; -import org.lsposed.manager.util.ModuleUtil; -import org.lsposed.manager.util.NavUtil; -import org.lsposed.manager.util.chrome.LinkTransformationMethod; - -import java.util.Locale; - -import rikka.core.res.ResourcesKt; public class MainActivity extends BaseActivity { - ActivityMainBinding binding; + private static final String KEY_PREFIX = MainActivity.class.getName() + '.'; + private static final String EXTRA_SAVED_INSTANCE_STATE = KEY_PREFIX + "SAVED_INSTANCE_STATE"; + private boolean restarting; + private ActivityMainBinding binding; + + @NonNull + public static Intent newIntent(@NonNull Context context) { + return new Intent(context, MainActivity.class); + } + + @NonNull + private static Intent newIntent(@NonNull Bundle savedInstanceState, @NonNull Context context) { + return newIntent(context) + .putExtra(EXTRA_SAVED_INSTANCE_STATE, savedInstanceState); + } @Override public void onCreate(Bundle savedInstanceState) { + if (savedInstanceState == null) { + savedInstanceState = getIntent().getBundleExtra(EXTRA_SAVED_INSTANCE_STATE); + } super.onCreate(savedInstanceState); + binding = ActivityMainBinding.inflate(getLayoutInflater()); setContentView(binding.getRoot()); - binding.status.setOnClickListener(v -> { - if (ConfigManager.getXposedApiVersion() != -1) { - new InfoDialogBuilder(this) - .setTitle(R.string.info) - .show(); - } else { - NavUtil.startURL(this, getString(R.string.about_source)); - } - }); - binding.modules.setOnClickListener(new StartActivityListener(ModulesActivity.class, true)); - binding.download.setOnClickListener(new StartActivityListener(RepoActivity.class, false)); - binding.logs.setOnClickListener(new StartActivityListener(LogsActivity.class, true)); - binding.settings.setOnClickListener(new StartActivityListener(SettingsActivity.class, false)); - binding.about.setOnClickListener(v -> { - DialogAboutBinding binding = DialogAboutBinding.inflate(LayoutInflater.from(this), null, false); - binding.sourceCode.setMovementMethod(LinkMovementMethod.getInstance()); - binding.sourceCode.setTransformationMethod(new LinkTransformationMethod(this)); - binding.sourceCode.setText(HtmlCompat.fromHtml(getString( - R.string.about_view_source_code, - "GitHub", - "Telegram"), HtmlCompat.FROM_HTML_MODE_LEGACY)); - binding.translators.setMovementMethod(LinkMovementMethod.getInstance()); - binding.translators.setTransformationMethod(new LinkTransformationMethod(this)); - binding.translators.setText(HtmlCompat.fromHtml(getString(R.string.about_translators, getString(R.string.translators)), HtmlCompat.FROM_HTML_MODE_LEGACY)); - binding.version.setText(String.format(Locale.US, "%s (%s)", BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE)); - new BlurBehindDialogBuilder(this) - .setView(binding.getRoot()) - .show(); - }); - Glide.with(binding.appIcon) - .load(GlideHelper.wrapApplicationInfoForIconLoader(getApplicationInfo())) - .into(binding.appIcon); - String installXposedVersion = ConfigManager.getXposedVersionName(); - int cardBackgroundColor; - if (installXposedVersion != null) { - if (!ConfigManager.isSepolicyLoaded()) { - binding.statusTitle.setText(R.string.partial_activated); - cardBackgroundColor = ResourcesKt.resolveColor(getTheme(), R.attr.colorWarning); - binding.statusIcon.setImageResource(R.drawable.ic_warning); - binding.statusSummary.setText(R.string.selinux_policy_not_loaded_summary); - } else if (!ConfigManager.systemServerRequested()) { - binding.statusTitle.setText(R.string.partial_activated); - cardBackgroundColor = ResourcesKt.resolveColor(getTheme(), R.attr.colorWarning); - binding.statusIcon.setImageResource(R.drawable.ic_warning); - binding.statusSummary.setText(R.string.system_inject_fail_summary); - } else { - binding.statusTitle.setText(R.string.activated); - cardBackgroundColor = ResourcesKt.resolveColor(getTheme(), R.attr.colorNormal); - binding.statusIcon.setImageResource(R.drawable.ic_check_circle); - binding.statusSummary.setText(String.format(Locale.US, "%s (%d)", installXposedVersion, ConfigManager.getXposedVersionCode())); - } - } else { - cardBackgroundColor = ResourcesKt.resolveColor(getTheme(), R.attr.colorInstall); - boolean isMagiskInstalled = ConfigManager.isMagiskInstalled(); - binding.statusTitle.setText(isMagiskInstalled ? R.string.Install : R.string.NotInstall); - binding.statusSummary.setText(isMagiskInstalled ? R.string.InstallDetail : R.string.NotInstallDetail); - if (!isMagiskInstalled) { - binding.status.setOnClickListener(null); - binding.download.setVisibility(View.GONE); - } - binding.statusIcon.setImageResource(R.drawable.ic_error); - Snackbar.make(binding.snackbar, R.string.lsposed_not_active, Snackbar.LENGTH_INDEFINITE).show(); - } - binding.status.setCardBackgroundColor(cardBackgroundColor); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { - binding.status.setOutlineSpotShadowColor(cardBackgroundColor); - binding.status.setOutlineAmbientShadowColor(cardBackgroundColor); + + if (savedInstanceState == null) { + handleIntent(getIntent()); } } - private class StartActivityListener implements View.OnClickListener { - boolean requireInstalled; - Class clazz; + @Override + protected void onNewIntent(Intent intent) { + super.onNewIntent(intent); + handleIntent(intent); + } - StartActivityListener(Class clazz, boolean requireInstalled) { - this.clazz = clazz; - this.requireInstalled = requireInstalled; + private void handleIntent(Intent intent) { + if (intent == null) { + return; } - - @Override - public void onClick(View v) { - if (requireInstalled && ConfigManager.getXposedVersionName() == null) { - Snackbar.make(binding.snackbar, R.string.lsposed_not_active, Snackbar.LENGTH_LONG).show(); - } else { - Intent intent = new Intent(); - intent.setClass(MainActivity.this, clazz); - startActivity(intent); + NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager().findFragmentById(R.id.nav_host_fragment); + NavController navController = navHostFragment.getNavController(); + if (intent.getAction().equals("android.intent.action.APPLICATION_PREFERENCES")) { + navController.navigate(R.id.settings_fragment); + } else if (intent.hasExtra("modulePackageName")) { + Bundle bundle = new Bundle(); + bundle.putString("modulePackageName", intent.getStringExtra("modulePackageName")); + bundle.putInt("moduleUserId", intent.getIntExtra("moduleUserId", -1)); + navController.navigate(R.id.app_list_fragment, bundle); + } else if (!TextUtils.isEmpty(intent.getDataString())) { + switch (intent.getDataString()) { + case "modules": + navController.navigate(R.id.modules_fragment); + break; + case "logs": + navController.navigate(R.id.logs_fragment); + break; + case "repo": + navController.navigate(R.id.repo_fragment); + break; } } } @Override - protected void onResume() { - super.onResume(); - int moduleCount = ModuleUtil.getInstance().getEnabledModulesCount(); - binding.modulesSummary.setText(getResources().getQuantityString(R.plurals.modules_enabled_count, moduleCount, moduleCount)); + public boolean onSupportNavigateUp() { + NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment); + return navController.navigateUp() || super.onSupportNavigateUp(); + } + + public void restart() { + Bundle savedInstanceState = new Bundle(); + onSaveInstanceState(savedInstanceState); + finish(); + startActivity(newIntent(savedInstanceState, this)); + overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out); + restarting = true; + } + + @Override + public boolean dispatchKeyEvent(@NonNull KeyEvent event) { + return restarting || super.dispatchKeyEvent(event); + } + + @SuppressLint("RestrictedApi") + @Override + public boolean dispatchKeyShortcutEvent(@NonNull KeyEvent event) { + return restarting || super.dispatchKeyShortcutEvent(event); + } + + @Override + public boolean dispatchTouchEvent(@NonNull MotionEvent event) { + return restarting || super.dispatchTouchEvent(event); + } + + @Override + public boolean dispatchTrackballEvent(@NonNull MotionEvent event) { + return restarting || super.dispatchTrackballEvent(event); + } + + @Override + public boolean dispatchGenericMotionEvent(@NonNull MotionEvent event) { + return restarting || super.dispatchGenericMotionEvent(event); } } diff --git a/app/src/main/java/org/lsposed/manager/ui/activity/SettingsActivity.java b/app/src/main/java/org/lsposed/manager/ui/activity/SettingsActivity.java index 297d2379..a42fd347 100644 --- a/app/src/main/java/org/lsposed/manager/ui/activity/SettingsActivity.java +++ b/app/src/main/java/org/lsposed/manager/ui/activity/SettingsActivity.java @@ -51,7 +51,7 @@ import com.takisoft.preferencex.PreferenceFragmentCompat; import org.lsposed.manager.BuildConfig; import org.lsposed.manager.ConfigManager; import org.lsposed.manager.R; -import org.lsposed.manager.databinding.ActivitySettingsBinding; +import org.lsposed.manager.databinding.FragmentSettingsBinding; import org.lsposed.manager.ui.activity.base.BaseActivity; import org.lsposed.manager.util.BackupUtils; import org.lsposed.manager.util.theme.ThemeUtil; @@ -67,7 +67,7 @@ import rikka.widget.borderview.BorderRecyclerView; public class SettingsActivity extends BaseActivity { private static final String KEY_PREFIX = SettingsActivity.class.getName() + '.'; private static final String EXTRA_SAVED_INSTANCE_STATE = KEY_PREFIX + "SAVED_INSTANCE_STATE"; - ActivitySettingsBinding binding; + FragmentSettingsBinding binding; private boolean restarting; @NonNull @@ -87,7 +87,7 @@ public class SettingsActivity extends BaseActivity { savedInstanceState = getIntent().getBundleExtra(EXTRA_SAVED_INSTANCE_STATE); } super.onCreate(savedInstanceState); - binding = ActivitySettingsBinding.inflate(getLayoutInflater()); + binding = FragmentSettingsBinding.inflate(getLayoutInflater()); setContentView(binding.getRoot()); setAppBar(binding.appBar, binding.toolbar); binding.getRoot().bringChildToFront(binding.appBar); diff --git a/app/src/main/java/org/lsposed/manager/ui/activity/base/ListActivity.java b/app/src/main/java/org/lsposed/manager/ui/activity/base/ListActivity.java deleted file mode 100644 index abf13657..00000000 --- a/app/src/main/java/org/lsposed/manager/ui/activity/base/ListActivity.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * This file is part of LSPosed. - * - * LSPosed is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * LSPosed is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with LSPosed. If not, see . - * - * Copyright (C) 2020 EdXposed Contributors - * Copyright (C) 2021 LSPosed Contributors - */ - -package org.lsposed.manager.ui.activity.base; - -import android.os.Bundle; -import android.view.Menu; -import android.view.View; -import android.widget.Filterable; - -import androidx.appcompat.app.ActionBar; -import androidx.appcompat.widget.SearchView; -import androidx.recyclerview.widget.RecyclerView; - -import org.lsposed.manager.R; -import org.lsposed.manager.databinding.ActivityListBinding; -import org.lsposed.manager.util.LinearLayoutManagerFix; - -import rikka.recyclerview.RecyclerViewKt; - -public abstract class ListActivity extends BaseActivity { - - protected ActivityListBinding binding; - protected SearchView searchView; - private SearchView.OnQueryTextListener mSearchListener; - private BaseAdapter adapter = null; - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - binding = ActivityListBinding.inflate(getLayoutInflater()); - setContentView(binding.getRoot()); - setAppBar(binding.appBar, binding.toolbar); - binding.getRoot().bringChildToFront(binding.appBar); - binding.toolbar.setNavigationOnClickListener(view -> onBackPressed()); - binding.recyclerView.getBorderViewDelegate().setBorderVisibilityChangedListener((top, oldTop, bottom, oldBottom) -> binding.appBar.setRaised(!top)); - ActionBar bar = getSupportActionBar(); - if (bar != null) { - bar.setDisplayHomeAsUpEnabled(true); - } - adapter = createAdapter(); - adapter.setHasStableIds(true); - binding.recyclerView.setAdapter(adapter); - binding.recyclerView.setHasFixedSize(true); - binding.recyclerView.setLayoutManager(new LinearLayoutManagerFix(this)); - RecyclerViewKt.addFastScroller(binding.recyclerView, binding.recyclerView); - RecyclerViewKt.fixEdgeEffect(binding.recyclerView, false, true); - binding.progress.setVisibilityAfterHide(View.GONE); - mSearchListener = new SearchView.OnQueryTextListener() { - @Override - public boolean onQueryTextSubmit(String query) { - adapter.getFilter().filter(query); - return false; - } - - @Override - public boolean onQueryTextChange(String newText) { - adapter.getFilter().filter(newText); - return false; - } - }; - } - - @Override - public boolean onPrepareOptionsMenu(Menu menu) { - searchView = (SearchView) menu.findItem(R.id.menu_search).getActionView(); - searchView.setOnQueryTextListener(mSearchListener); - return super.onPrepareOptionsMenu(menu); - } - - @Override - public void onBackPressed() { - if (searchView.isIconified()) { - super.onBackPressed(); - } else { - searchView.setIconified(true); - } - } - - protected abstract static class BaseAdapter extends RecyclerView.Adapter implements Filterable { - - } - - protected abstract BaseAdapter createAdapter(); -} diff --git a/app/src/main/java/org/lsposed/manager/ui/activity/AppListActivity.java b/app/src/main/java/org/lsposed/manager/ui/fragment/AppListFragment.java similarity index 65% rename from app/src/main/java/org/lsposed/manager/ui/activity/AppListActivity.java rename to app/src/main/java/org/lsposed/manager/ui/fragment/AppListFragment.java index 3aa4c6df..618e243b 100644 --- a/app/src/main/java/org/lsposed/manager/ui/activity/AppListActivity.java +++ b/app/src/main/java/org/lsposed/manager/ui/fragment/AppListFragment.java @@ -1,37 +1,19 @@ -/* - * This file is part of LSPosed. - * - * LSPosed is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * LSPosed is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with LSPosed. If not, see . - * - * Copyright (C) 2020 EdXposed Contributors - * Copyright (C) 2021 LSPosed Contributors - */ - -package org.lsposed.manager.ui.activity; +package org.lsposed.manager.ui.fragment; import android.content.Intent; import android.os.AsyncTask; import android.os.Bundle; +import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.contract.ActivityResultContracts; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.StringRes; -import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.widget.SearchView; @@ -40,8 +22,7 @@ import com.google.android.material.snackbar.Snackbar; import org.lsposed.manager.BuildConfig; import org.lsposed.manager.R; import org.lsposed.manager.adapters.ScopeAdapter; -import org.lsposed.manager.databinding.ActivityAppListBinding; -import org.lsposed.manager.ui.activity.base.BaseActivity; +import org.lsposed.manager.databinding.FragmentAppListBinding; import org.lsposed.manager.util.BackupUtils; import org.lsposed.manager.util.LinearLayoutManagerFix; import org.lsposed.manager.util.ModuleUtil; @@ -50,68 +31,74 @@ import java.util.Locale; import rikka.recyclerview.RecyclerViewKt; -public class AppListActivity extends BaseActivity { +public class AppListFragment extends BaseFragment { + public SearchView searchView; private ScopeAdapter scopeAdapter; + private ModuleUtil.InstalledModule module; private SearchView.OnQueryTextListener searchListener; - public ActivityAppListBinding binding; + public FragmentAppListBinding binding; public ActivityResultLauncher backupLauncher; public ActivityResultLauncher restoreLauncher; + @Nullable @Override - public void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - String modulePackageName = getIntent().getStringExtra("modulePackageName"); - int moduleUserId = getIntent().getIntExtra("moduleUserId", -1); - binding = ActivityAppListBinding.inflate(getLayoutInflater()); - setContentView(binding.getRoot()); - setAppBar(binding.appBar, binding.toolbar); + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + binding = FragmentAppListBinding.inflate(getLayoutInflater(), container, false); binding.appBar.setRaised(true); - binding.toolbar.setNavigationOnClickListener(view -> onBackPressed()); - ModuleUtil.InstalledModule module = ModuleUtil.getInstance().getModule(modulePackageName, moduleUserId); - if (module == null) { - finish(); - return; - } - ActionBar bar = getSupportActionBar(); - if (bar != null) { - bar.setDisplayHomeAsUpEnabled(true); - if (module.userId != 0) { - bar.setTitle(String.format(Locale.US, "%s (%d)", module.getAppName(), module.userId)); - } else { - bar.setTitle(module.getAppName()); - } - bar.setSubtitle(module.packageName); + String title; + if (module.userId != 0) { + title = String.format(Locale.US, "%s (%d)", module.getAppName(), module.userId); + } else { + title = module.getAppName(); } + binding.toolbar.setSubtitle(module.packageName); + scopeAdapter = new ScopeAdapter(this, module); scopeAdapter.setHasStableIds(true); binding.recyclerView.setAdapter(scopeAdapter); binding.recyclerView.setHasFixedSize(true); - binding.recyclerView.setLayoutManager(new LinearLayoutManagerFix(this)); + binding.recyclerView.setLayoutManager(new LinearLayoutManagerFix(requireActivity())); RecyclerViewKt.addFastScroller(binding.recyclerView, binding.recyclerView); RecyclerViewKt.fixEdgeEffect(binding.recyclerView, false, true); binding.swipeRefreshLayout.setOnRefreshListener(() -> scopeAdapter.refresh(true)); searchListener = scopeAdapter.getSearchListener(); + setupToolbar(binding.toolbar, title, R.menu.menu_app_list); + return binding.getRoot(); + } + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + String modulePackageName = getArguments().getString("modulePackageName"); + int moduleUserId = getArguments().getInt("moduleUserId", -1); + + module = ModuleUtil.getInstance().getModule(modulePackageName, moduleUserId); + if (module == null) { + getNavController().navigateUp(); + return; + } + backupLauncher = registerForActivityResult(new ActivityResultContracts.CreateDocument(), uri -> { if (uri != null) { try { // grantUriPermission might throw RemoteException on MIUI - grantUriPermission(BuildConfig.APPLICATION_ID, uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION); + requireActivity().grantUriPermission(BuildConfig.APPLICATION_ID, uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION); } catch (Exception e) { e.printStackTrace(); } - AlertDialog alertDialog = new AlertDialog.Builder(this) + AlertDialog alertDialog = new AlertDialog.Builder(requireActivity()) .setCancelable(false) .setMessage(R.string.settings_backuping) .show(); AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> { - boolean success = BackupUtils.backup(this, uri, modulePackageName); + boolean success = BackupUtils.backup(requireActivity(), uri, modulePackageName); try { - runOnUiThread(() -> { + requireActivity().runOnUiThread(() -> { alertDialog.dismiss(); makeSnackBar(success ? R.string.settings_backup_success : R.string.settings_backup_failed, Snackbar.LENGTH_SHORT); }); @@ -126,18 +113,18 @@ public class AppListActivity extends BaseActivity { if (uri != null) { try { // grantUriPermission might throw RemoteException on MIUI - grantUriPermission(BuildConfig.APPLICATION_ID, uri, Intent.FLAG_GRANT_READ_URI_PERMISSION); + requireActivity().grantUriPermission(BuildConfig.APPLICATION_ID, uri, Intent.FLAG_GRANT_READ_URI_PERMISSION); } catch (Exception e) { e.printStackTrace(); } - AlertDialog alertDialog = new AlertDialog.Builder(this) + AlertDialog alertDialog = new AlertDialog.Builder(requireActivity()) .setCancelable(false) .setMessage(R.string.settings_restoring) .show(); AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> { - boolean success = BackupUtils.restore(this, uri, modulePackageName); + boolean success = BackupUtils.restore(requireActivity(), uri, modulePackageName); try { - runOnUiThread(() -> { + requireActivity().runOnUiThread(() -> { alertDialog.dismiss(); makeSnackBar(success ? R.string.settings_restore_success : R.string.settings_restore_failed, Snackbar.LENGTH_SHORT); scopeAdapter.refresh(false); @@ -151,7 +138,7 @@ public class AppListActivity extends BaseActivity { } @Override - protected void onResume() { + public void onResume() { super.onResume(); scopeAdapter.refresh(false); } @@ -165,11 +152,11 @@ public class AppListActivity extends BaseActivity { } @Override - public boolean onCreateOptionsMenu(@NonNull Menu menu) { - scopeAdapter.onCreateOptionsMenu(menu, getMenuInflater()); + public void onPrepareOptionsMenu(@NonNull Menu menu) { + super.onPrepareOptionsMenu(menu); searchView = (SearchView) menu.findItem(R.id.menu_search).getActionView(); searchView.setOnQueryTextListener(searchListener); - return super.onCreateOptionsMenu(menu); + scopeAdapter.onPrepareOptionsMenu(menu); } @Override @@ -180,17 +167,6 @@ public class AppListActivity extends BaseActivity { return super.onContextItemSelected(item); } - @Override - public void onBackPressed() { - if (searchView.isIconified()) { - if (scopeAdapter.onBackPressed()) { - super.onBackPressed(); - } - } else { - searchView.setIconified(true); - } - } - public void makeSnackBar(String text, @Snackbar.Duration int duration) { Snackbar.make(binding.snackbar, text, duration).show(); } diff --git a/app/src/main/java/org/lsposed/manager/ui/fragment/BaseFragment.java b/app/src/main/java/org/lsposed/manager/ui/fragment/BaseFragment.java new file mode 100644 index 00000000..46798e50 --- /dev/null +++ b/app/src/main/java/org/lsposed/manager/ui/fragment/BaseFragment.java @@ -0,0 +1,58 @@ +package org.lsposed.manager.ui.fragment; + +import android.view.View; + +import androidx.appcompat.widget.Toolbar; +import androidx.fragment.app.Fragment; +import androidx.navigation.NavController; +import androidx.navigation.NavOptions; +import androidx.navigation.Navigation; + +import org.lsposed.manager.R; + +public class BaseFragment extends Fragment { + public void navigateUp() { + getNavController().navigateUp(); + } + + public NavController getNavController() { + View view = getView(); + if (view == null) { + return null; + } + View tabletFragmentContainer = view.findViewById(R.id.tablet_nav_container); + if (tabletFragmentContainer != null) { + return Navigation.findNavController(tabletFragmentContainer); + } else { + return Navigation.findNavController(view); + } + } + + public void setupToolbar(Toolbar toolbar, int title) { + setupToolbar(toolbar, getString(title), -1); + } + + public void setupToolbar(Toolbar toolbar, int title, int menu) { + setupToolbar(toolbar, getString(title), menu); + } + + public void setupToolbar(Toolbar toolbar, String title, int menu) { + toolbar.setNavigationOnClickListener(v -> navigateUp()); + toolbar.setNavigationIcon(R.drawable.ic_baseline_arrow_back_24); + toolbar.setTitle(title); + if (menu != -1) { + toolbar.inflateMenu(menu); + toolbar.setOnMenuItemClickListener(this::onOptionsItemSelected); + onPrepareOptionsMenu(toolbar.getMenu()); + } + } + + public NavOptions getNavOptions() { + return new NavOptions.Builder() + .setEnterAnim(R.anim.nav_default_enter_anim) + .setExitAnim(R.anim.nav_default_exit_anim) + .setPopEnterAnim(R.anim.nav_default_pop_enter_anim) + .setPopExitAnim(R.anim.nav_default_pop_exit_anim) + .build(); + } +} diff --git a/app/src/main/java/org/lsposed/manager/ui/fragment/CompileDialogFragment.java b/app/src/main/java/org/lsposed/manager/ui/fragment/CompileDialogFragment.java index 9c6b820f..219ffe8e 100644 --- a/app/src/main/java/org/lsposed/manager/ui/fragment/CompileDialogFragment.java +++ b/app/src/main/java/org/lsposed/manager/ui/fragment/CompileDialogFragment.java @@ -41,7 +41,6 @@ import com.google.android.material.snackbar.Snackbar; import org.lsposed.manager.App; import org.lsposed.manager.R; import org.lsposed.manager.databinding.FragmentCompileDialogBinding; -import org.lsposed.manager.ui.activity.AppListActivity; import java.io.BufferedReader; import java.io.InputStreamReader; @@ -156,9 +155,9 @@ public class CompileDialogFragment extends AppCompatDialogFragment { CompileDialogFragment fragment = outerRef.get(); if (fragment != null) { fragment.dismissAllowingStateLoss(); - AppListActivity activity = (AppListActivity) fragment.getActivity(); - if (activity != null && activity.getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.RESUMED)) { - activity.makeSnackBar(text, Snackbar.LENGTH_LONG); + AppListFragment appListFragment = (AppListFragment) fragment.getParentFragment(); + if (appListFragment != null && appListFragment.getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.RESUMED)) { + appListFragment.makeSnackBar(text, Snackbar.LENGTH_LONG); return; } } diff --git a/app/src/main/java/org/lsposed/manager/ui/fragment/HomeFragment.java b/app/src/main/java/org/lsposed/manager/ui/fragment/HomeFragment.java new file mode 100644 index 00000000..cc499de0 --- /dev/null +++ b/app/src/main/java/org/lsposed/manager/ui/fragment/HomeFragment.java @@ -0,0 +1,154 @@ +package org.lsposed.manager.ui.fragment; + +import android.os.Build; +import android.os.Bundle; +import android.text.method.LinkMovementMethod; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.core.text.HtmlCompat; +import androidx.navigation.NavOptions; + +import com.bumptech.glide.Glide; +import com.google.android.material.snackbar.Snackbar; + +import org.lsposed.manager.BuildConfig; +import org.lsposed.manager.ConfigManager; +import org.lsposed.manager.R; +import org.lsposed.manager.databinding.DialogAboutBinding; +import org.lsposed.manager.databinding.FragmentHomeBinding; +import org.lsposed.manager.databinding.FragmentMainBinding; +import org.lsposed.manager.ui.activity.base.BaseActivity; +import org.lsposed.manager.ui.dialog.BlurBehindDialogBuilder; +import org.lsposed.manager.ui.dialog.InfoDialogBuilder; +import org.lsposed.manager.util.GlideHelper; +import org.lsposed.manager.util.ModuleUtil; +import org.lsposed.manager.util.NavUtil; +import org.lsposed.manager.util.chrome.LinkTransformationMethod; + +import java.util.Locale; + +import rikka.core.res.ResourcesKt; + +public class HomeFragment extends BaseFragment { + + private FragmentHomeBinding binding; + private View snackbar; + + @Override + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + FragmentMainBinding mainBinding = FragmentMainBinding.inflate(inflater, container, false); + snackbar = mainBinding.snackbar; + binding = FragmentHomeBinding.bind(mainBinding.snackbar); + return mainBinding.getRoot(); + } + + @Override + public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + BaseActivity activity = (BaseActivity) requireActivity(); + binding.status.setOnClickListener(v -> { + if (ConfigManager.getXposedApiVersion() != -1) { + new InfoDialogBuilder(activity) + .setTitle(R.string.info) + .show(); + } else { + NavUtil.startURL(activity, getString(R.string.about_source)); + } + }); + binding.modules.setOnClickListener(new StartFragmentListener(R.id.action_main_fragment_to_modules_fragment, true)); + binding.download.setOnClickListener(new StartFragmentListener(R.id.action_main_fragment_to_repo_fragment, false)); + binding.logs.setOnClickListener(new StartFragmentListener(R.id.action_main_fragment_to_logs_fragment, true)); + binding.settings.setOnClickListener(new StartFragmentListener(R.id.action_main_fragment_to_settings_fragment, false)); + binding.about.setOnClickListener(v -> { + DialogAboutBinding binding = DialogAboutBinding.inflate(LayoutInflater.from(requireActivity()), null, false); + binding.sourceCode.setMovementMethod(LinkMovementMethod.getInstance()); + binding.sourceCode.setTransformationMethod(new LinkTransformationMethod(activity)); + binding.sourceCode.setText(HtmlCompat.fromHtml(getString( + R.string.about_view_source_code, + "GitHub", + "Telegram"), HtmlCompat.FROM_HTML_MODE_LEGACY)); + binding.translators.setMovementMethod(LinkMovementMethod.getInstance()); + binding.translators.setTransformationMethod(new LinkTransformationMethod(activity)); + binding.translators.setText(HtmlCompat.fromHtml(getString(R.string.about_translators, getString(R.string.translators)), HtmlCompat.FROM_HTML_MODE_LEGACY)); + binding.version.setText(String.format(Locale.US, "%s (%s)", BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE)); + new BlurBehindDialogBuilder(activity) + .setView(binding.getRoot()) + .show(); + }); + Glide.with(binding.appIcon) + .load(GlideHelper.wrapApplicationInfoForIconLoader(activity.getApplicationInfo())) + .into(binding.appIcon); + String installXposedVersion = ConfigManager.getXposedVersionName(); + int cardBackgroundColor; + if (installXposedVersion != null) { + if (!ConfigManager.isSepolicyLoaded()) { + binding.statusTitle.setText(R.string.partial_activated); + cardBackgroundColor = ResourcesKt.resolveColor(activity.getTheme(), R.attr.colorWarning); + binding.statusIcon.setImageResource(R.drawable.ic_warning); + binding.statusSummary.setText(R.string.selinux_policy_not_loaded_summary); + } else if (!ConfigManager.systemServerRequested()) { + binding.statusTitle.setText(R.string.partial_activated); + cardBackgroundColor = ResourcesKt.resolveColor(activity.getTheme(), R.attr.colorWarning); + binding.statusIcon.setImageResource(R.drawable.ic_warning); + binding.statusSummary.setText(R.string.system_inject_fail_summary); + } else { + binding.statusTitle.setText(R.string.activated); + cardBackgroundColor = ResourcesKt.resolveColor(activity.getTheme(), R.attr.colorNormal); + binding.statusIcon.setImageResource(R.drawable.ic_check_circle); + binding.statusSummary.setText(String.format(Locale.US, "%s (%d)", installXposedVersion, ConfigManager.getXposedVersionCode())); + } + } else { + cardBackgroundColor = ResourcesKt.resolveColor(activity.getTheme(), R.attr.colorInstall); + boolean isMagiskInstalled = ConfigManager.isMagiskInstalled(); + binding.statusTitle.setText(isMagiskInstalled ? R.string.Install : R.string.NotInstall); + binding.statusSummary.setText(isMagiskInstalled ? R.string.InstallDetail : R.string.NotInstallDetail); + if (!isMagiskInstalled) { + binding.status.setOnClickListener(null); + binding.download.setVisibility(View.GONE); + } + binding.statusIcon.setImageResource(R.drawable.ic_error); + Snackbar.make(snackbar, R.string.lsposed_not_active, Snackbar.LENGTH_INDEFINITE).show(); + } + binding.status.setCardBackgroundColor(cardBackgroundColor); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + binding.status.setOutlineSpotShadowColor(cardBackgroundColor); + binding.status.setOutlineAmbientShadowColor(cardBackgroundColor); + } + } + + private class StartFragmentListener implements View.OnClickListener { + boolean requireInstalled; + int fragment; + + StartFragmentListener(int fragment, boolean requireInstalled) { + this.fragment = fragment; + this.requireInstalled = requireInstalled; + } + + @Override + public void onClick(View v) { + if (requireInstalled && ConfigManager.getXposedVersionName() == null) { + Snackbar.make(snackbar, R.string.lsposed_not_active, Snackbar.LENGTH_LONG).show(); + } else { + getNavController().navigate(fragment, null, getNavOptions()); + } + } + } + + @Override + public void onResume() { + super.onResume(); + int moduleCount = ModuleUtil.getInstance().getEnabledModulesCount(); + binding.modulesSummary.setText(getResources().getQuantityString(R.plurals.modules_enabled_count, moduleCount, moduleCount)); + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + binding = null; + } +} diff --git a/app/src/main/java/org/lsposed/manager/ui/activity/LogsActivity.java b/app/src/main/java/org/lsposed/manager/ui/fragment/LogsFragment.java similarity index 83% rename from app/src/main/java/org/lsposed/manager/ui/activity/LogsActivity.java rename to app/src/main/java/org/lsposed/manager/ui/fragment/LogsFragment.java index 9bac020e..3f52630a 100644 --- a/app/src/main/java/org/lsposed/manager/ui/activity/LogsActivity.java +++ b/app/src/main/java/org/lsposed/manager/ui/fragment/LogsFragment.java @@ -18,7 +18,7 @@ * Copyright (C) 2021 LSPosed Contributors */ -package org.lsposed.manager.ui.activity; +package org.lsposed.manager.ui.fragment; import android.annotation.SuppressLint; import android.content.Intent; @@ -29,7 +29,6 @@ import android.os.Handler; import android.os.Looper; import android.os.ParcelFileDescriptor; import android.view.LayoutInflater; -import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; @@ -39,7 +38,6 @@ import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.contract.ActivityResultContracts; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.AlertDialog; import androidx.core.content.FileProvider; import androidx.recyclerview.widget.RecyclerView; @@ -47,13 +45,13 @@ import androidx.recyclerview.widget.RecyclerView; import com.google.android.material.snackbar.Snackbar; import com.google.android.material.tabs.TabLayout; +import org.lsposed.manager.App; import org.lsposed.manager.BuildConfig; import org.lsposed.manager.ConfigManager; import org.lsposed.manager.R; -import org.lsposed.manager.databinding.ActivityLogsBinding; import org.lsposed.manager.databinding.DialogInstallWarningBinding; +import org.lsposed.manager.databinding.FragmentLogsBinding; import org.lsposed.manager.databinding.ItemLogBinding; -import org.lsposed.manager.ui.activity.base.BaseActivity; import org.lsposed.manager.util.LinearLayoutManagerFix; import java.io.BufferedReader; @@ -76,23 +74,23 @@ import rikka.insets.WindowInsetsHelperKt; import rikka.recyclerview.RecyclerViewKt; @SuppressLint("NotifyDataSetChanged") -public class LogsActivity extends BaseActivity { +public class LogsFragment extends BaseFragment { private boolean verbose = false; private LogsAdapter adapter; private final Handler handler = new Handler(Looper.getMainLooper()); - private ActivityLogsBinding binding; + private FragmentLogsBinding binding; private LinearLayoutManagerFix layoutManager; ActivityResultLauncher saveLogsLauncher = registerForActivityResult(new ActivityResultContracts.CreateDocument(), uri -> { if (uri != null) { try { // grantUriPermission might throw RemoteException on MIUI - grantUriPermission(BuildConfig.APPLICATION_ID, uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION); + requireContext().grantUriPermission(BuildConfig.APPLICATION_ID, uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION); } catch (Exception e) { e.printStackTrace(); } AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> { - try (var os = getContentResolver().openOutputStream(uri)) { + try (var os = requireContext().getContentResolver().openOutputStream(uri)) { ParcelFileDescriptor parcelFileDescriptor = ConfigManager.getLogs(verbose); if (parcelFileDescriptor == null) { return; @@ -107,59 +105,65 @@ public class LogsActivity extends BaseActivity { } }); + @Nullable @Override - public void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - binding = ActivityLogsBinding.inflate(getLayoutInflater()); - setContentView(binding.getRoot()); - setAppBar(binding.appBar, binding.toolbar); + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + binding = FragmentLogsBinding.inflate(inflater, container, false); binding.getRoot().bringChildToFront(binding.appBar); - binding.toolbar.setNavigationOnClickListener(view -> onBackPressed()); + setupToolbar(binding.toolbar, R.string.Logs, R.menu.menu_logs); binding.recyclerView.getBorderViewDelegate().setBorderVisibilityChangedListener((top, oldTop, bottom, oldBottom) -> binding.appBar.setRaised(!top)); - ActionBar bar = getSupportActionBar(); - if (bar != null) { - bar.setDisplayHomeAsUpEnabled(true); + + + if (!ConfigManager.isVerboseLogEnabled()) { + WindowInsetsHelperKt.setInitialPadding(binding.recyclerView, 0, ResourcesKt.resolveDimensionPixelOffset(requireActivity().getTheme(), R.attr.actionBarSize, 0), 0, 0); + binding.slidingTabs.setVisibility(View.GONE); + } else { + binding.slidingTabs.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() { + @Override + public void onTabSelected(TabLayout.Tab tab) { + verbose = tab.getPosition() == 1; + reloadErrorLog(); + } + + @Override + public void onTabUnselected(TabLayout.Tab tab) { + + } + + @Override + public void onTabReselected(TabLayout.Tab tab) { + + } + }); } - if (!preferences.getBoolean("hide_logcat_warning", false)) { + + adapter = new LogsAdapter(); + RecyclerViewKt.fixEdgeEffect(binding.recyclerView, false, true); + binding.recyclerView.setAdapter(adapter); + layoutManager = new LinearLayoutManagerFix(requireActivity()); + binding.recyclerView.setLayoutManager(layoutManager); + return binding.getRoot(); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + if (!App.getPreferences().getBoolean("hide_logcat_warning", false)) { DialogInstallWarningBinding binding = DialogInstallWarningBinding.inflate(getLayoutInflater()); binding.getRoot().setOnClickListener(v -> binding.checkbox.toggle()); - new AlertDialog.Builder(this) + new AlertDialog.Builder(requireActivity()) .setMessage(R.string.not_logcat_2) .setView(binding.getRoot()) .setPositiveButton(android.R.string.ok, (dialog, which) -> { if (binding.checkbox.isChecked()) { - preferences.edit().putBoolean("hide_logcat_warning", true).apply(); + App.getPreferences().edit().putBoolean("hide_logcat_warning", true).apply(); } }) .setCancelable(false) .show(); } - if (!ConfigManager.isVerboseLogEnabled()) { - WindowInsetsHelperKt.setInitialPadding(binding.recyclerView, 0, ResourcesKt.resolveDimensionPixelOffset(getTheme(), R.attr.actionBarSize, 0), 0, 0); - binding.slidingTabs.setVisibility(View.GONE); - } - adapter = new LogsAdapter(); - RecyclerViewKt.fixEdgeEffect(binding.recyclerView, false, true); - binding.recyclerView.setAdapter(adapter); - layoutManager = new LinearLayoutManagerFix(this); - binding.recyclerView.setLayoutManager(layoutManager); - binding.slidingTabs.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() { - @Override - public void onTabSelected(TabLayout.Tab tab) { - verbose = tab.getPosition() == 1; - reloadErrorLog(); - } - @Override - public void onTabUnselected(TabLayout.Tab tab) { - - } - - @Override - public void onTabReselected(TabLayout.Tab tab) { - - } - }); } @Override @@ -168,12 +172,6 @@ public class LogsActivity extends BaseActivity { reloadErrorLog(); } - @Override - public boolean onCreateOptionsMenu(@NonNull Menu menu) { - getMenuInflater().inflate(R.menu.menu_logs, menu); - return super.onCreateOptionsMenu(menu); - } - @Override public boolean onOptionsItemSelected(@NonNull MenuItem item) { int itemId = item.getItemId(); @@ -216,7 +214,7 @@ public class LogsActivity extends BaseActivity { new LogsReader().execute(parcelFileDescriptor.getFileDescriptor()); } else { binding.slidingTabs.selectTab(binding.slidingTabs.getTabAt(0)); - new AlertDialog.Builder(this) + new AlertDialog.Builder(requireActivity()) .setMessage(R.string.verbose_log_not_avaliable) .setPositiveButton(android.R.string.ok, null) .show(); @@ -244,7 +242,7 @@ public class LogsActivity extends BaseActivity { now.get(Calendar.YEAR), now.get(Calendar.MONTH) + 1, now.get(Calendar.DAY_OF_MONTH), now.get(Calendar.HOUR_OF_DAY), now.get(Calendar.MINUTE), now.get(Calendar.SECOND)); - File cacheFile = new File(getCacheDir(), filename); + File cacheFile = new File(requireActivity().getCacheDir(), filename); try (var os = new FileOutputStream(cacheFile); var is = new FileInputStream(parcelFileDescriptor.getFileDescriptor())) { FileUtils.copy(is, os); } catch (IOException e) { @@ -252,7 +250,7 @@ public class LogsActivity extends BaseActivity { return; } - Uri uri = FileProvider.getUriForFile(this, BuildConfig.APPLICATION_ID + ".fileprovider", cacheFile); + Uri uri = FileProvider.getUriForFile(requireActivity(), BuildConfig.APPLICATION_ID + ".fileprovider", cacheFile); Intent sendIntent = new Intent(); sendIntent.setAction(Intent.ACTION_SEND); sendIntent.putExtra(Intent.EXTRA_STREAM, uri); @@ -279,7 +277,7 @@ public class LogsActivity extends BaseActivity { private final Runnable mRunnable = new Runnable() { @Override public void run() { - if (!isFinishing()) { + if (!requireActivity().isFinishing()) { mProgressDialog.show(); } } @@ -287,7 +285,7 @@ public class LogsActivity extends BaseActivity { @Override protected void onPreExecute() { - mProgressDialog = new AlertDialog.Builder(LogsActivity.this).create(); + mProgressDialog = new AlertDialog.Builder(requireActivity()).create(); mProgressDialog.setMessage(getString(R.string.loading)); mProgressDialog.setCancelable(false); handler.postDelayed(mRunnable, 300); @@ -305,7 +303,7 @@ public class LogsActivity extends BaseActivity { logs.add(line); } } catch (IOException e) { - logs.add(LogsActivity.this.getResources().getString(R.string.logs_cannot_read)); + logs.add(requireActivity().getResources().getString(R.string.logs_cannot_read)); if (e.getMessage() != null) { logs.addAll(Arrays.asList(e.getMessage().split("\n"))); } @@ -368,6 +366,4 @@ public class LogsActivity extends BaseActivity { } } } - - } diff --git a/app/src/main/java/org/lsposed/manager/ui/activity/ModulesActivity.java b/app/src/main/java/org/lsposed/manager/ui/fragment/ModulesFragment.java similarity index 84% rename from app/src/main/java/org/lsposed/manager/ui/activity/ModulesActivity.java rename to app/src/main/java/org/lsposed/manager/ui/fragment/ModulesFragment.java index 8a7e7e24..746509d7 100644 --- a/app/src/main/java/org/lsposed/manager/ui/activity/ModulesActivity.java +++ b/app/src/main/java/org/lsposed/manager/ui/fragment/ModulesFragment.java @@ -1,24 +1,4 @@ -/* - * This file is part of LSPosed. - * - * LSPosed is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * LSPosed is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with LSPosed. If not, see . - * - * Copyright (C) 2020 EdXposed Contributors - * Copyright (C) 2021 LSPosed Contributors - */ - -package org.lsposed.manager.ui.activity; +package org.lsposed.manager.ui.fragment; import static android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS; @@ -39,6 +19,7 @@ import android.text.TextUtils; import android.text.style.ForegroundColorSpan; import android.text.style.StyleSpan; import android.text.style.TypefaceSpan; +import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.View; @@ -51,7 +32,6 @@ import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.widget.SearchView; import androidx.constraintlayout.widget.ConstraintLayout; @@ -70,12 +50,11 @@ import org.lsposed.lspd.models.UserInfo; import org.lsposed.manager.ConfigManager; import org.lsposed.manager.R; import org.lsposed.manager.adapters.AppHelper; -import org.lsposed.manager.databinding.ActivityModuleDetailBinding; import org.lsposed.manager.databinding.DialogRecyclerviewBinding; +import org.lsposed.manager.databinding.FragmentPagerBinding; import org.lsposed.manager.databinding.ItemModuleBinding; import org.lsposed.manager.databinding.ItemRepoRecyclerviewBinding; import org.lsposed.manager.repo.RepoLoader; -import org.lsposed.manager.ui.activity.base.BaseActivity; import org.lsposed.manager.ui.widget.EmptyStateRecyclerView; import org.lsposed.manager.util.GlideApp; import org.lsposed.manager.util.LinearLayoutManagerFix; @@ -93,13 +72,14 @@ import rikka.insets.WindowInsetsHelperKt; import rikka.recyclerview.RecyclerViewKt; import rikka.widget.borderview.BorderRecyclerView; -public class ModulesActivity extends BaseActivity implements ModuleUtil.ModuleListener { +public class ModulesFragment extends BaseFragment implements ModuleUtil.ModuleListener { - protected ActivityModuleDetailBinding binding; + protected FragmentPagerBinding binding; protected SearchView searchView; private SearchView.OnQueryTextListener mSearchListener; private final PagerAdapter pagerAdapter = new PagerAdapter(); private final ArrayList adapters = new ArrayList<>(); + private final ArrayList titles = new ArrayList<>(); private Handler workHandler; private PackageManager pm; @@ -107,23 +87,36 @@ public class ModulesActivity extends BaseActivity implements ModuleUtil.ModuleLi private ModuleUtil.InstalledModule selectedModule; @Override - public void onCreate(Bundle savedInstanceState) { + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); HandlerThread workThread = new HandlerThread("ModulesActivity WorkHandler"); workThread.start(); workHandler = new Handler(workThread.getLooper()); moduleUtil = ModuleUtil.getInstance(); - pm = getPackageManager(); + pm = requireContext().getPackageManager(); moduleUtil.addListener(this); - super.onCreate(savedInstanceState); - binding = ActivityModuleDetailBinding.inflate(getLayoutInflater()); - setContentView(binding.getRoot()); - setAppBar(binding.appBar, binding.toolbar); + mSearchListener = new SearchView.OnQueryTextListener() { + @Override + public boolean onQueryTextSubmit(String query) { + adapters.forEach(adapter -> adapter.getFilter().filter(query)); + return false; + } + + @Override + public boolean onQueryTextChange(String newText) { + adapters.forEach(adapter -> adapter.getFilter().filter(newText)); + return false; + } + }; + } + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + binding = FragmentPagerBinding.inflate(inflater, container, false); + binding.getRoot().bringChildToFront(binding.appBar); - binding.toolbar.setNavigationOnClickListener(view -> onBackPressed()); - ActionBar bar = getSupportActionBar(); - if (bar != null) { - bar.setDisplayHomeAsUpEnabled(true); - } + setupToolbar(binding.toolbar, R.string.Modules, R.menu.menu_modules); binding.viewPager.setAdapter(new PagerAdapter()); binding.viewPager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() { @Override @@ -138,19 +131,19 @@ public class ModulesActivity extends BaseActivity implements ModuleUtil.ModuleLi } }); - binding.fab.setOnClickListener(view -> { + binding.fab.setOnClickListener(v -> { var pickAdaptor = new ModuleAdapter(null, true); var position = binding.viewPager.getCurrentItem(); var snapshot = adapters.get(position).snapshot().stream().map(m -> m.packageName).collect(Collectors.toSet()); var user = adapters.get(position).getUser(); pickAdaptor.setFilter(m -> !snapshot.contains(m.packageName)); pickAdaptor.refresh(); - var v = DialogRecyclerviewBinding.inflate(getLayoutInflater()).getRoot(); - v.setAdapter(pickAdaptor); - v.setLayoutManager(new LinearLayoutManagerFix(ModulesActivity.this)); - var dialog = new AlertDialog.Builder(ModulesActivity.this) + var rv = DialogRecyclerviewBinding.inflate(getLayoutInflater()).getRoot(); + rv.setAdapter(pickAdaptor); + rv.setLayoutManager(new LinearLayoutManagerFix(requireActivity())); + var dialog = new AlertDialog.Builder(requireActivity()) .setTitle(getString(R.string.install_to_user, user.name)) - .setView(v) + .setView(rv) .setNegativeButton(android.R.string.cancel, null) .show(); pickAdaptor.setOnPickListener(picked -> { @@ -160,43 +153,26 @@ public class ModulesActivity extends BaseActivity implements ModuleUtil.ModuleLi }); }); - mSearchListener = new SearchView.OnQueryTextListener() { - @Override - public boolean onQueryTextSubmit(String query) { - adapters.forEach(adapter -> adapter.getFilter().filter(query)); - return false; - } + return binding.getRoot(); + } - @Override - public boolean onQueryTextChange(String newText) { - adapters.forEach(adapter -> adapter.getFilter().filter(newText)); - return false; - } - }; + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); if (ConfigManager.getXposedVersionName() == null) { - Toast.makeText(this, R.string.lsposed_not_active, Toast.LENGTH_LONG).show(); - finish(); + Toast.makeText(requireContext(), R.string.lsposed_not_active, Toast.LENGTH_LONG).show(); + getNavController().navigateUp(); } } @Override - public boolean onPrepareOptionsMenu(Menu menu) { + public void onPrepareOptionsMenu(Menu menu) { searchView = (SearchView) menu.findItem(R.id.menu_search).getActionView(); searchView.setOnQueryTextListener(mSearchListener); - return super.onPrepareOptionsMenu(menu); } @Override - public void onBackPressed() { - if (searchView.isIconified()) { - super.onBackPressed(); - } else { - searchView.setIconified(true); - } - } - - @Override - protected void onResume() { + public void onResume() { super.onResume(); var users = ConfigManager.getUsers(); if (users != null) { @@ -204,14 +180,12 @@ public class ModulesActivity extends BaseActivity implements ModuleUtil.ModuleLi adapters.clear(); if (users.size() != 1) { binding.viewPager.setUserInputEnabled(true); - ArrayList titles = new ArrayList<>(); for (var user : users) { var adapter = new ModuleAdapter(user); adapter.setHasStableIds(true); adapters.add(adapter); titles.add(user.name); } - new TabLayoutMediator(binding.tabLayout, binding.viewPager, (tab, position) -> tab.setText(titles.get(position))).attach(); binding.tabLayout.setVisibility(View.VISIBLE); } else { binding.viewPager.setUserInputEnabled(false); @@ -222,16 +196,13 @@ public class ModulesActivity extends BaseActivity implements ModuleUtil.ModuleLi } pagerAdapter.notifyDataSetChanged(); } + if (users.size() != 1) { + new TabLayoutMediator(binding.tabLayout, binding.viewPager, (tab, position) -> tab.setText(titles.get(position))).attach(); + } } adapters.forEach(ModuleAdapter::refresh); } - @Override - public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(R.menu.menu_modules, menu); - return super.onCreateOptionsMenu(menu); - } - @Override public void onDestroy() { super.onDestroy(); @@ -253,18 +224,18 @@ public class ModulesActivity extends BaseActivity implements ModuleUtil.ModuleLi } private void installModuleToUser(ModuleUtil.InstalledModule module, UserInfo user) { - new AlertDialog.Builder(this) + new AlertDialog.Builder(requireActivity()) .setTitle(getString(R.string.install_to_user, user.name)) .setMessage(getString(R.string.install_to_user_message, module.getAppName(), user.name)) .setPositiveButton(android.R.string.ok, (dialog, which) -> workHandler.post(() -> { var success = ConfigManager.installExistingPackageAsUser(module.packageName, user.id); - runOnUiThread(() -> { + requireActivity().runOnUiThread(() -> { String text = success ? getString(R.string.module_installed, module.getAppName(), user.name) : getString(R.string.module_install_failed); if (getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.RESUMED)) { Snackbar.make(binding.snackbar, text, Snackbar.LENGTH_SHORT).show(); } else { - Toast.makeText(ModulesActivity.this, text, Toast.LENGTH_SHORT).show(); + Toast.makeText(requireActivity(), text, Toast.LENGTH_SHORT).show(); } }); if (success) @@ -302,18 +273,18 @@ public class ModulesActivity extends BaseActivity implements ModuleUtil.ModuleLi ConfigManager.startActivityAsUserWithFeature(new Intent(ACTION_APPLICATION_DETAILS_SETTINGS, Uri.fromParts("package", selectedModule.packageName, null)), selectedModule.userId); return true; } else if (itemId == R.id.menu_uninstall) { - new AlertDialog.Builder(this) + new AlertDialog.Builder(requireActivity()) .setTitle(selectedModule.getAppName()) .setMessage(R.string.module_uninstall_message) .setPositiveButton(android.R.string.ok, (dialog, which) -> workHandler.post(() -> { boolean success = ConfigManager.uninstallPackage(selectedModule.packageName, selectedModule.userId); - runOnUiThread(() -> { + requireActivity().runOnUiThread(() -> { String text = success ? getString(R.string.module_uninstalled, selectedModule.getAppName()) : getString(R.string.module_uninstall_failed); if (getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.RESUMED)) { Snackbar.make(binding.snackbar, text, Snackbar.LENGTH_SHORT).show(); } else { - Toast.makeText(ModulesActivity.this, text, Toast.LENGTH_SHORT).show(); + Toast.makeText(requireActivity(), text, Toast.LENGTH_SHORT).show(); } }); if (success) @@ -323,11 +294,10 @@ public class ModulesActivity extends BaseActivity implements ModuleUtil.ModuleLi .show(); return true; } else if (itemId == R.id.menu_repo) { - Intent intent = new Intent(); - intent.setClass(this, RepoItemActivity.class); - intent.putExtra("modulePackageName", selectedModule.packageName); - intent.putExtra("moduleName", selectedModule.getAppName()); - startActivity(intent); + Bundle bundle = new Bundle(); + bundle.putString("modulePackageName", selectedModule.packageName); + bundle.putString("moduleName", selectedModule.getAppName()); + getNavController().navigate(R.id.action_modules_fragment_to_repo_item_fragment, bundle, getNavOptions()); return true; } return super.onContextItemSelected(item); @@ -344,11 +314,11 @@ public class ModulesActivity extends BaseActivity implements ModuleUtil.ModuleLi @Override public void onBindViewHolder(@NonNull PagerAdapter.ViewHolder holder, int position) { if (getItemCount() == 1) { - WindowInsetsHelperKt.setInitialPadding(holder.recyclerView, 0, ResourcesKt.resolveDimensionPixelOffset(getTheme(), R.attr.actionBarSize, 0), 0, 0); + WindowInsetsHelperKt.setInitialPadding(holder.recyclerView, 0, ResourcesKt.resolveDimensionPixelOffset(requireActivity().getTheme(), R.attr.actionBarSize, 0), 0, 0); } holder.recyclerView.setTag(position); holder.recyclerView.setAdapter(adapters.get(position)); - holder.recyclerView.setLayoutManager(new LinearLayoutManagerFix(ModulesActivity.this)); + holder.recyclerView.setLayoutManager(new LinearLayoutManagerFix(requireActivity())); holder.recyclerView.getBorderViewDelegate().setBorderVisibilityChangedListener((top, oldTop, bottom, oldBottom) -> binding.appBar.setRaised(!top)); holder.recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { @Override @@ -403,12 +373,12 @@ public class ModulesActivity extends BaseActivity implements ModuleUtil.ModuleLi @NonNull @Override - public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { - return new ViewHolder(ItemModuleBinding.inflate(getLayoutInflater(), parent, false)); + public ModuleAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + return new ModuleAdapter.ViewHolder(ItemModuleBinding.inflate(getLayoutInflater(), parent, false)); } @Override - public void onBindViewHolder(@NonNull ViewHolder holder, int position) { + public void onBindViewHolder(@NonNull ModuleAdapter.ViewHolder holder, int position) { ModuleUtil.InstalledModule item = showList.get(position); String appName; if (item.userId != 0) { @@ -451,7 +421,7 @@ public class ModulesActivity extends BaseActivity implements ModuleUtil.ModuleLi if (warningText != null) { sb.append("\n"); sb.append(warningText); - final ForegroundColorSpan foregroundColorSpan = new ForegroundColorSpan(ContextCompat.getColor(ModulesActivity.this, R.color.material_red_500)); + final ForegroundColorSpan foregroundColorSpan = new ForegroundColorSpan(ContextCompat.getColor(requireActivity(), R.color.material_red_500)); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { final TypefaceSpan typefaceSpan = new TypefaceSpan(Typeface.create("sans-serif-medium", Typeface.NORMAL)); sb.setSpan(typefaceSpan, sb.length() - warningText.length(), sb.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE); @@ -466,17 +436,17 @@ public class ModulesActivity extends BaseActivity implements ModuleUtil.ModuleLi if (!isPick) { holder.root.setAlpha(moduleUtil.isModuleEnabled(item.packageName) ? 1.0f : .5f); holder.itemView.setOnClickListener(v -> { - Intent intent = new Intent(ModulesActivity.this, AppListActivity.class); - intent.putExtra("modulePackageName", item.packageName); - intent.putExtra("moduleUserId", item.userId); - startActivity(intent); + Bundle bundle = new Bundle(); + bundle.putString("modulePackageName", item.packageName); + bundle.putInt("moduleUserId", item.userId); + getNavController().navigate(R.id.action_modules_fragment_to_app_list_fragment, bundle, getNavOptions()); }); holder.itemView.setOnLongClickListener(v -> { selectedModule = item; return false; }); holder.itemView.setOnCreateContextMenuListener((menu, v, menuInfo) -> { - getMenuInflater().inflate(R.menu.context_menu_modules, menu); + requireActivity().getMenuInflater().inflate(R.menu.context_menu_modules, menu); menu.setHeaderTitle(item.getAppName()); Intent intent = AppHelper.getSettingsIntent(item.packageName, item.userId); if (intent == null) { @@ -523,7 +493,7 @@ public class ModulesActivity extends BaseActivity implements ModuleUtil.ModuleLi @Override public Filter getFilter() { - return new ApplicationFilter(); + return new ModuleAdapter.ApplicationFilter(); } public void setFilter(@NonNull Predicate filter) { @@ -544,7 +514,7 @@ public class ModulesActivity extends BaseActivity implements ModuleUtil.ModuleLi public void refresh(boolean force) { if (force) moduleUtil.reloadInstalledModules(); - runOnUiThread(reloadModules); + requireActivity().runOnUiThread(reloadModules); } private final Runnable reloadModules = new Runnable() { @@ -565,7 +535,7 @@ public class ModulesActivity extends BaseActivity implements ModuleUtil.ModuleLi searchList.clear(); searchList.addAll(tmpList); String queryStr = searchView != null ? searchView.getQuery().toString() : ""; - runOnUiThread(() -> getFilter().filter(queryStr)); + requireActivity().runOnUiThread(() -> getFilter().filter(queryStr)); } }; diff --git a/app/src/main/java/org/lsposed/manager/ui/activity/RepoActivity.java b/app/src/main/java/org/lsposed/manager/ui/fragment/RepoFragment.java similarity index 63% rename from app/src/main/java/org/lsposed/manager/ui/activity/RepoActivity.java rename to app/src/main/java/org/lsposed/manager/ui/fragment/RepoFragment.java index c15b97aa..4ba17c32 100644 --- a/app/src/main/java/org/lsposed/manager/ui/activity/RepoActivity.java +++ b/app/src/main/java/org/lsposed/manager/ui/fragment/RepoFragment.java @@ -1,50 +1,35 @@ -/* - * This file is part of LSPosed. - * - * LSPosed is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * LSPosed is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with LSPosed. If not, see . - * - * Copyright (C) 2020 EdXposed Contributors - * Copyright (C) 2021 LSPosed Contributors - */ +package org.lsposed.manager.ui.fragment; -package org.lsposed.manager.ui.activity; - -import android.content.Intent; import android.os.Bundle; import android.text.SpannableStringBuilder; import android.text.TextUtils; +import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; +import android.view.View; import android.view.ViewGroup; import android.widget.Filter; +import android.widget.Filterable; import android.widget.TextView; import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.appcompat.widget.SearchView; import androidx.constraintlayout.widget.ConstraintLayout; import androidx.lifecycle.Lifecycle; import androidx.recyclerview.widget.RecyclerView; import com.google.android.material.snackbar.Snackbar; +import org.lsposed.manager.App; import org.lsposed.manager.ConfigManager; import org.lsposed.manager.R; +import org.lsposed.manager.databinding.FragmentRepoBinding; import org.lsposed.manager.databinding.ItemOnlinemoduleBinding; import org.lsposed.manager.repo.RepoLoader; import org.lsposed.manager.repo.model.OnlineModule; -import org.lsposed.manager.ui.activity.base.ListActivity; +import org.lsposed.manager.util.LinearLayoutManagerFix; import java.time.Instant; import java.util.ArrayList; @@ -55,41 +40,89 @@ import java.util.List; import java.util.stream.Collectors; import rikka.core.util.LabelComparator; +import rikka.recyclerview.RecyclerViewKt; + +public class RepoFragment extends BaseFragment implements RepoLoader.Listener { + protected FragmentRepoBinding binding; + protected SearchView searchView; + private SearchView.OnQueryTextListener mSearchListener; -public class RepoActivity extends ListActivity implements RepoLoader.Listener { private final RepoLoader repoLoader = RepoLoader.getInstance(); private RepoAdapter adapter; @Override public void onCreate(@Nullable Bundle savedInstanceState) { repoLoader.addListener(this); + mSearchListener = new SearchView.OnQueryTextListener() { + @Override + public boolean onQueryTextSubmit(String query) { + adapter.getFilter().filter(query); + return false; + } + + @Override + public boolean onQueryTextChange(String newText) { + adapter.getFilter().filter(newText); + return false; + } + }; super.onCreate(savedInstanceState); + } + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + binding = FragmentRepoBinding.inflate(getLayoutInflater(), container, false); + binding.getRoot().bringChildToFront(binding.appBar); + setupToolbar(binding.toolbar, R.string.module_repo, R.menu.menu_repo); + binding.recyclerView.getBorderViewDelegate().setBorderVisibilityChangedListener((top, oldTop, bottom, oldBottom) -> binding.appBar.setRaised(!top)); + adapter = new RepoAdapter(); + adapter.setHasStableIds(true); + binding.recyclerView.setAdapter(adapter); + binding.recyclerView.setHasFixedSize(true); + binding.recyclerView.setLayoutManager(new LinearLayoutManagerFix(requireActivity())); + RecyclerViewKt.addFastScroller(binding.recyclerView, binding.recyclerView); + RecyclerViewKt.fixEdgeEffect(binding.recyclerView, false, true); + binding.progress.setVisibilityAfterHide(View.GONE); + return binding.getRoot(); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); if (ConfigManager.getXposedVersionName() == null && !ConfigManager.isMagiskInstalled()) { - Toast.makeText(this, R.string.lsposed_not_active, Toast.LENGTH_LONG).show(); - finish(); + Toast.makeText(requireActivity(), R.string.lsposed_not_active, Toast.LENGTH_LONG).show(); + getNavController().navigateUp(); } } @Override - protected BaseAdapter createAdapter() { - return adapter = new RepoAdapter(); + public void onPrepareOptionsMenu(Menu menu) { + searchView = (SearchView) menu.findItem(R.id.menu_search).getActionView(); + searchView.setOnQueryTextListener(mSearchListener); + int sort = App.getPreferences().getInt("repo_sort", 0); + if (sort == 0) { + menu.findItem(R.id.item_sort_by_name).setChecked(true); + } else if (sort == 1) { + menu.findItem(R.id.item_sort_by_update_time).setChecked(true); + } } @Override - protected void onDestroy() { + public void onDestroy() { super.onDestroy(); repoLoader.removeListener(this); } @Override - protected void onResume() { + public void onResume() { super.onResume(); adapter.initData(); } @Override public void repoLoaded() { - runOnUiThread(() -> { + requireActivity().runOnUiThread(() -> { binding.progress.hide(); adapter.setData(repoLoader.getOnlineModules()); }); @@ -115,29 +148,17 @@ public class RepoActivity extends ListActivity implements RepoLoader.Listener { repoLoader.loadRemoteData(); } else if (itemId == R.id.item_sort_by_name) { item.setChecked(true); - preferences.edit().putInt("repo_sort", 0).apply(); + App.getPreferences().edit().putInt("repo_sort", 0).apply(); adapter.setData(repoLoader.getOnlineModules()); } else if (itemId == R.id.item_sort_by_update_time) { item.setChecked(true); - preferences.edit().putInt("repo_sort", 1).apply(); + App.getPreferences().edit().putInt("repo_sort", 1).apply(); adapter.setData(repoLoader.getOnlineModules()); } return super.onOptionsItemSelected(item); } - @Override - public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(R.menu.menu_repo, menu); - int sort = preferences.getInt("repo_sort", 0); - if (sort == 0) { - menu.findItem(R.id.item_sort_by_name).setChecked(true); - } else if (sort == 1) { - menu.findItem(R.id.item_sort_by_update_time).setChecked(true); - } - return super.onCreateOptionsMenu(menu); - } - - private class RepoAdapter extends BaseAdapter { + private class RepoAdapter extends RecyclerView.Adapter implements Filterable { private List fullList, showList; private final LabelComparator labelComparator = new LabelComparator(); @@ -147,12 +168,12 @@ public class RepoActivity extends ListActivity implements RepoLoader.Listener { @NonNull @Override - public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { - return new ViewHolder(ItemOnlinemoduleBinding.inflate(getLayoutInflater(), parent, false)); + public RepoAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + return new RepoAdapter.ViewHolder(ItemOnlinemoduleBinding.inflate(getLayoutInflater(), parent, false)); } @Override - public void onBindViewHolder(@NonNull ViewHolder holder, int position) { + public void onBindViewHolder(@NonNull RepoAdapter.ViewHolder holder, int position) { OnlineModule module = showList.get(position); holder.appName.setText(module.getDescription()); SpannableStringBuilder sb = new SpannableStringBuilder(module.getName()); @@ -163,11 +184,10 @@ public class RepoActivity extends ListActivity implements RepoLoader.Listener { } holder.appDescription.setText(sb); holder.itemView.setOnClickListener(v -> { - Intent intent = new Intent(); - intent.setClass(RepoActivity.this, RepoItemActivity.class); - intent.putExtra("modulePackageName", module.getName()); - intent.putExtra("moduleName", module.getDescription()); - startActivity(intent); + Bundle bundle = new Bundle(); + bundle.putString("modulePackageName", module.getName()); + bundle.putString("moduleName", module.getDescription()); + getNavController().navigate(R.id.action_repo_fragment_to_repo_item_fragment, bundle, getNavOptions()); }); } @@ -179,14 +199,14 @@ public class RepoActivity extends ListActivity implements RepoLoader.Listener { public void setData(Collection modules) { fullList = new ArrayList<>(modules); fullList = fullList.stream().filter((onlineModule -> !onlineModule.isHide())).collect(Collectors.toList()); - int sort = preferences.getInt("repo_sort", 0); + int sort = App.getPreferences().getInt("repo_sort", 0); if (sort == 0) { fullList.sort((o1, o2) -> labelComparator.compare(o1.getDescription(), o2.getDescription())); } else if (sort == 1) { fullList.sort(Collections.reverseOrder(Comparator.comparing(o -> Instant.parse(o.getReleases().get(0).getUpdatedAt())))); } String queryStr = searchView != null ? searchView.getQuery().toString() : ""; - runOnUiThread(() -> getFilter().filter(queryStr)); + requireActivity().runOnUiThread(() -> getFilter().filter(queryStr)); } public void initData() { @@ -206,7 +226,7 @@ public class RepoActivity extends ListActivity implements RepoLoader.Listener { @Override public Filter getFilter() { - return new ModuleFilter(); + return new RepoAdapter.ModuleFilter(); } class ViewHolder extends RecyclerView.ViewHolder { diff --git a/app/src/main/java/org/lsposed/manager/ui/activity/RepoItemActivity.java b/app/src/main/java/org/lsposed/manager/ui/fragment/RepoItemFragment.java similarity index 79% rename from app/src/main/java/org/lsposed/manager/ui/activity/RepoItemActivity.java rename to app/src/main/java/org/lsposed/manager/ui/fragment/RepoItemFragment.java index 4c4ed760..5161a973 100644 --- a/app/src/main/java/org/lsposed/manager/ui/activity/RepoItemActivity.java +++ b/app/src/main/java/org/lsposed/manager/ui/fragment/RepoItemFragment.java @@ -1,24 +1,4 @@ -/* - * This file is part of LSPosed. - * - * LSPosed is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * LSPosed is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with LSPosed. If not, see . - * - * Copyright (C) 2020 EdXposed Contributors - * Copyright (C) 2021 LSPosed Contributors - */ - -package org.lsposed.manager.ui.activity; +package org.lsposed.manager.ui.fragment; import android.os.Bundle; import android.text.Spannable; @@ -26,7 +6,7 @@ import android.text.SpannableStringBuilder; import android.text.TextUtils; import android.text.style.ClickableSpan; import android.text.util.Linkify; -import android.view.Menu; +import android.view.LayoutInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; @@ -34,7 +14,6 @@ import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.AlertDialog; import androidx.lifecycle.Lifecycle; import androidx.recyclerview.widget.RecyclerView; @@ -46,7 +25,7 @@ import com.google.android.material.snackbar.Snackbar; import com.google.android.material.tabs.TabLayoutMediator; import org.lsposed.manager.R; -import org.lsposed.manager.databinding.ActivityModuleDetailBinding; +import org.lsposed.manager.databinding.FragmentPagerBinding; import org.lsposed.manager.databinding.ItemRepoLoadmoreBinding; import org.lsposed.manager.databinding.ItemRepoReadmeBinding; import org.lsposed.manager.databinding.ItemRepoRecyclerviewBinding; @@ -57,7 +36,6 @@ import org.lsposed.manager.repo.model.Collaborator; import org.lsposed.manager.repo.model.OnlineModule; import org.lsposed.manager.repo.model.Release; import org.lsposed.manager.repo.model.ReleaseAsset; -import org.lsposed.manager.ui.activity.base.BaseActivity; import org.lsposed.manager.ui.widget.LinkifyTextView; import org.lsposed.manager.util.GlideApp; import org.lsposed.manager.util.LinearLayoutManagerFix; @@ -83,38 +61,21 @@ import rikka.widget.borderview.BorderNestedScrollView; import rikka.widget.borderview.BorderRecyclerView; import rikka.widget.borderview.BorderView; -public class RepoItemActivity extends BaseActivity implements RepoLoader.Listener { - ActivityModuleDetailBinding binding; +public class RepoItemFragment extends BaseFragment implements RepoLoader.Listener { + FragmentPagerBinding binding; private Markwon markwon; private OnlineModule module; private ReleaseAdapter releaseAdapter; + @Nullable @Override - public void onCreate(@Nullable Bundle savedInstanceState) { - RepoLoader.getInstance().addListener(this); - super.onCreate(savedInstanceState); - binding = ActivityModuleDetailBinding.inflate(getLayoutInflater()); - String modulePackageName = getIntent().getStringExtra("modulePackageName"); - String moduleName = getIntent().getStringExtra("moduleName"); - setContentView(binding.getRoot()); - setAppBar(binding.appBar, binding.toolbar); + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + binding = FragmentPagerBinding.inflate(getLayoutInflater(), container, false); + String modulePackageName = getArguments().getString("modulePackageName"); + String moduleName = getArguments().getString("moduleName"); binding.getRoot().bringChildToFront(binding.appBar); - binding.toolbar.setNavigationOnClickListener(view -> onBackPressed()); - ActionBar bar = getSupportActionBar(); - assert bar != null; - bar.setTitle(moduleName); - bar.setSubtitle(modulePackageName); - bar.setDisplayHomeAsUpEnabled(true); - markwon = Markwon.builder(this) - .usePlugin(StrikethroughPlugin.create()) - .usePlugin(TablePlugin.create(this)) - .usePlugin(TaskListPlugin.create(this)) - .usePlugin(HtmlPlugin.create()) - .usePlugin(GlideImagesPlugin.create(GlideApp.with(this))) - .usePlugin(LinkifyPlugin.create(Linkify.WEB_URLS, true)) - .usePlugin(SoftBreakAddsNewLinePlugin.create()) - .build(); - module = RepoLoader.getInstance().getOnlineModule(modulePackageName); + setupToolbar(binding.toolbar, moduleName, R.menu.menu_repo_item); + binding.toolbar.setSubtitle(modulePackageName); binding.viewPager.setAdapter(new PagerAdapter()); binding.viewPager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() { @Override @@ -128,19 +89,38 @@ public class RepoItemActivity extends BaseActivity implements RepoLoader.Listene }); int[] titles = new int[]{R.string.module_readme, R.string.module_releases, R.string.module_information}; new TabLayoutMediator(binding.tabLayout, binding.viewPager, (tab, position) -> tab.setText(titles[position])).attach(); + return binding.getRoot(); } @Override - public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(R.menu.menu_repo_item, menu); - return super.onCreateOptionsMenu(menu); + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + } + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + RepoLoader.getInstance().addListener(this); + super.onCreate(savedInstanceState); + + markwon = Markwon.builder(requireActivity()) + .usePlugin(StrikethroughPlugin.create()) + .usePlugin(TablePlugin.create(requireActivity())) + .usePlugin(TaskListPlugin.create(requireActivity())) + .usePlugin(HtmlPlugin.create()) + .usePlugin(GlideImagesPlugin.create(GlideApp.with(this))) + .usePlugin(LinkifyPlugin.create(Linkify.WEB_URLS, true)) + .usePlugin(SoftBreakAddsNewLinePlugin.create()) + .build(); + String modulePackageName = getArguments().getString("modulePackageName"); + module = RepoLoader.getInstance().getOnlineModule(modulePackageName); + } @Override public boolean onOptionsItemSelected(@NonNull MenuItem item) { int id = item.getItemId(); if (id == R.id.menu_open_in_browser) { - NavUtil.startURL(this, "https://modules.lsposed.org/module/" + module.getName()); + NavUtil.startURL(requireActivity(), "https://modules.lsposed.org/module/" + module.getName()); } return super.onOptionsItemSelected(item); } @@ -154,7 +134,7 @@ public class RepoItemActivity extends BaseActivity implements RepoLoader.Listene public void moduleReleasesLoaded(OnlineModule module) { this.module = module; if (releaseAdapter != null) { - runOnUiThread(() -> releaseAdapter.loadItems()); + requireActivity().runOnUiThread(() -> releaseAdapter.loadItems()); if (module.getReleases().size() == 1) { Snackbar.make(binding.snackbar, R.string.module_release_no_more, Snackbar.LENGTH_SHORT).show(); } @@ -164,7 +144,7 @@ public class RepoItemActivity extends BaseActivity implements RepoLoader.Listene @Override public void onThrowable(Throwable t) { if (releaseAdapter != null) { - runOnUiThread(() -> releaseAdapter.loadItems()); + requireActivity().runOnUiThread(() -> releaseAdapter.loadItems()); } if (getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.RESUMED)) { Snackbar.make(binding.snackbar, getString(R.string.repo_load_failed, t.getLocalizedMessage()), Snackbar.LENGTH_SHORT).show(); @@ -172,7 +152,7 @@ public class RepoItemActivity extends BaseActivity implements RepoLoader.Listene } @Override - protected void onDestroy() { + public void onDestroy() { super.onDestroy(); RepoLoader.getInstance().removeListener(this); } @@ -218,7 +198,7 @@ public class RepoItemActivity extends BaseActivity implements RepoLoader.Listene Collaborator collaborator = iterator.next(); String name = collaborator.getName() == null ? collaborator.getLogin() : collaborator.getName(); sb.append(name); - CustomTabsURLSpan span = new CustomTabsURLSpan(RepoItemActivity.this, String.format("https://github.com/%s", collaborator.getLogin())); + CustomTabsURLSpan span = new CustomTabsURLSpan(requireActivity(), String.format("https://github.com/%s", collaborator.getLogin())); sb.setSpan(span, sb.length() - name.length(), sb.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); if (iterator.hasNext()) { sb.append(", "); @@ -231,7 +211,7 @@ public class RepoItemActivity extends BaseActivity implements RepoLoader.Listene } holder.itemView.setOnClickListener(v -> { if (position == homepageRow) { - NavUtil.startURL(RepoItemActivity.this, module.getHomepageUrl()); + NavUtil.startURL(requireActivity(), module.getHomepageUrl()); } else if (position == collaboratorsRow) { ClickableSpan span = holder.description.getCurrentSpan(); holder.description.clearCurrentSpan(); @@ -240,7 +220,7 @@ public class RepoItemActivity extends BaseActivity implements RepoLoader.Listene span.onClick(v); } } else if (position == sourceUrlRow) { - NavUtil.startURL(RepoItemActivity.this, module.getSourceUrl()); + NavUtil.startURL(requireActivity(), module.getSourceUrl()); } }); @@ -277,16 +257,16 @@ public class RepoItemActivity extends BaseActivity implements RepoLoader.Listene @NonNull @Override - public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + public ReleaseAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { if (viewType == 0) { - return new ReleaseViewHolder(ItemRepoReleaseBinding.inflate(getLayoutInflater(), parent, false)); + return new ReleaseAdapter.ReleaseViewHolder(ItemRepoReleaseBinding.inflate(getLayoutInflater(), parent, false)); } else { - return new LoadmoreViewHolder(ItemRepoLoadmoreBinding.inflate(getLayoutInflater(), parent, false)); + return new ReleaseAdapter.LoadmoreViewHolder(ItemRepoLoadmoreBinding.inflate(getLayoutInflater(), parent, false)); } } @Override - public void onBindViewHolder(@NonNull ViewHolder holder, int position) { + public void onBindViewHolder(@NonNull ReleaseAdapter.ViewHolder holder, int position) { if (position == items.size()) { holder.progress.setVisibility(View.GONE); holder.title.setVisibility(View.VISIBLE); @@ -300,18 +280,18 @@ public class RepoItemActivity extends BaseActivity implements RepoLoader.Listene } else { Release release = items.get(position); holder.title.setText(release.getName()); - holder.description.setTransformationMethod(new LinkTransformationMethod(RepoItemActivity.this)); + holder.description.setTransformationMethod(new LinkTransformationMethod(requireActivity())); holder.description.setSpannableFactory(NoCopySpannableFactory.getInstance()); markwon.setMarkdown(holder.description, release.getDescription()); holder.description.setMovementMethod(null); - holder.openInBrowser.setOnClickListener(v -> NavUtil.startURL(RepoItemActivity.this, release.getUrl())); + holder.openInBrowser.setOnClickListener(v -> NavUtil.startURL(requireActivity(), release.getUrl())); List assets = release.getReleaseAssets(); if (assets != null && !assets.isEmpty()) { holder.viewAssets.setOnClickListener(v -> { ArrayList names = new ArrayList<>(); assets.forEach(releaseAsset -> names.add(releaseAsset.getName())); - new AlertDialog.Builder(RepoItemActivity.this) - .setItems(names.toArray(new String[0]), (dialog, which) -> NavUtil.startURL(RepoItemActivity.this, assets.get(which).getDownloadUrl())) + new AlertDialog.Builder(requireActivity()) + .setItems(names.toArray(new String[0]), (dialog, which) -> NavUtil.startURL(requireActivity(), assets.get(which).getDownloadUrl())) .show(); }); } else { @@ -350,7 +330,7 @@ public class RepoItemActivity extends BaseActivity implements RepoLoader.Listene } } - class ReleaseViewHolder extends ViewHolder { + class ReleaseViewHolder extends ReleaseAdapter.ViewHolder { public ReleaseViewHolder(ItemRepoReleaseBinding binding) { super(binding.getRoot()); title = binding.title; @@ -360,7 +340,7 @@ public class RepoItemActivity extends BaseActivity implements RepoLoader.Listene } } - class LoadmoreViewHolder extends ViewHolder { + class LoadmoreViewHolder extends ReleaseAdapter.ViewHolder { public LoadmoreViewHolder(ItemRepoLoadmoreBinding binding) { super(binding.getRoot()); title = binding.title; @@ -375,17 +355,17 @@ public class RepoItemActivity extends BaseActivity implements RepoLoader.Listene @Override public PagerAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { if (viewType == 0) { - return new ReadmeViewHolder(ItemRepoReadmeBinding.inflate(getLayoutInflater(), parent, false)); + return new PagerAdapter.ReadmeViewHolder(ItemRepoReadmeBinding.inflate(getLayoutInflater(), parent, false)); } else { - return new RecyclerviewBinding(ItemRepoRecyclerviewBinding.inflate(getLayoutInflater(), parent, false)); + return new PagerAdapter.RecyclerviewBinding(ItemRepoRecyclerviewBinding.inflate(getLayoutInflater(), parent, false)); } } @Override - public void onBindViewHolder(@NonNull ViewHolder holder, int position) { + public void onBindViewHolder(@NonNull PagerAdapter.ViewHolder holder, int position) { switch (position) { case 0: - holder.textView.setTransformationMethod(new LinkTransformationMethod(RepoItemActivity.this)); + holder.textView.setTransformationMethod(new LinkTransformationMethod(requireActivity())); holder.scrollView.getBorderViewDelegate().setBorderVisibilityChangedListener((top, oldTop, bottom, oldBottom) -> binding.appBar.setRaised(!top)); holder.scrollView.setTag(position); markwon.setMarkdown(holder.textView, module.getReadme()); @@ -398,7 +378,7 @@ public class RepoItemActivity extends BaseActivity implements RepoLoader.Listene holder.recyclerView.setAdapter(new InformationAdapter(module)); } holder.recyclerView.setTag(position); - holder.recyclerView.setLayoutManager(new LinearLayoutManagerFix(RepoItemActivity.this)); + holder.recyclerView.setLayoutManager(new LinearLayoutManagerFix(requireActivity())); holder.recyclerView.getBorderViewDelegate().setBorderVisibilityChangedListener((top, oldTop, bottom, oldBottom) -> binding.appBar.setRaised(!top)); RecyclerViewKt.fixEdgeEffect(holder.recyclerView, false, true); RecyclerViewKt.addFastScroller(holder.recyclerView, holder.itemView); @@ -426,7 +406,7 @@ public class RepoItemActivity extends BaseActivity implements RepoLoader.Listene } } - class ReadmeViewHolder extends ViewHolder { + class ReadmeViewHolder extends PagerAdapter.ViewHolder { public ReadmeViewHolder(ItemRepoReadmeBinding binding) { super(binding.getRoot()); textView = binding.readme; @@ -434,7 +414,7 @@ public class RepoItemActivity extends BaseActivity implements RepoLoader.Listene } } - class RecyclerviewBinding extends ViewHolder { + class RecyclerviewBinding extends PagerAdapter.ViewHolder { public RecyclerviewBinding(ItemRepoRecyclerviewBinding binding) { super(binding.getRoot()); recyclerView = binding.recyclerView; diff --git a/app/src/main/java/org/lsposed/manager/ui/fragment/SettingsFragment.java b/app/src/main/java/org/lsposed/manager/ui/fragment/SettingsFragment.java new file mode 100644 index 00000000..06a92412 --- /dev/null +++ b/app/src/main/java/org/lsposed/manager/ui/fragment/SettingsFragment.java @@ -0,0 +1,262 @@ +package org.lsposed.manager.ui.fragment; + +import android.Manifest; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.os.AsyncTask; +import android.os.Build; +import android.os.Bundle; +import android.provider.Settings; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContracts; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.StringRes; +import androidx.appcompat.app.AlertDialog; +import androidx.preference.Preference; +import androidx.preference.SwitchPreference; +import androidx.recyclerview.widget.RecyclerView; + +import com.google.android.material.snackbar.Snackbar; +import com.takisoft.preferencex.PreferenceCategory; +import com.takisoft.preferencex.PreferenceFragmentCompat; + +import org.lsposed.manager.App; +import org.lsposed.manager.BuildConfig; +import org.lsposed.manager.ConfigManager; +import org.lsposed.manager.R; +import org.lsposed.manager.databinding.FragmentSettingsBinding; +import org.lsposed.manager.ui.activity.MainActivity; +import org.lsposed.manager.util.BackupUtils; +import org.lsposed.manager.util.theme.ThemeUtil; + +import java.util.Calendar; +import java.util.Locale; + +import rikka.core.util.ResourceUtils; +import rikka.material.app.DayNightDelegate; +import rikka.recyclerview.RecyclerViewKt; +import rikka.widget.borderview.BorderRecyclerView; + +public class SettingsFragment extends BaseFragment { + FragmentSettingsBinding binding; + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + binding = FragmentSettingsBinding.inflate(inflater, container, false); + binding.getRoot().bringChildToFront(binding.appBar); + setupToolbar(binding.toolbar, R.string.Settings); + if (savedInstanceState == null) { + getChildFragmentManager().beginTransaction() + .add(R.id.container, new PreferenceFragment()).commit(); + } + return binding.getRoot(); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + if (ConfigManager.getXposedVersionName() == null) { + Snackbar.make(binding.snackbar, R.string.lsposed_not_active, Snackbar.LENGTH_LONG).show(); + } + } + + + public static class PreferenceFragment extends PreferenceFragmentCompat { + ActivityResultLauncher backupLauncher = registerForActivityResult(new ActivityResultContracts.CreateDocument(), + uri -> { + if (uri != null) { + try { + // grantUriPermission might throw RemoteException on MIUI + requireActivity().grantUriPermission(BuildConfig.APPLICATION_ID, uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION); + } catch (Exception e) { + e.printStackTrace(); + } + AlertDialog alertDialog = new AlertDialog.Builder(requireActivity()) + .setCancelable(false) + .setMessage(R.string.settings_backuping) + .show(); + AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> { + boolean success = BackupUtils.backup(requireContext(), uri); + try { + SettingsFragment fragment = (SettingsFragment) getParentFragment(); + requireActivity().runOnUiThread(() -> { + alertDialog.dismiss(); + fragment.makeSnackBar(success ? R.string.settings_backup_success : R.string.settings_backup_failed, Snackbar.LENGTH_SHORT); + }); + } catch (Exception e) { + e.printStackTrace(); + } + }); + } + }); + ActivityResultLauncher restoreLauncher = registerForActivityResult(new ActivityResultContracts.OpenDocument(), + uri -> { + if (uri != null) { + try { + // grantUriPermission might throw RemoteException on MIUI + requireActivity().grantUriPermission(BuildConfig.APPLICATION_ID, uri, Intent.FLAG_GRANT_READ_URI_PERMISSION); + } catch (Exception e) { + e.printStackTrace(); + } + AlertDialog alertDialog = new AlertDialog.Builder(requireActivity()) + .setCancelable(false) + .setMessage(R.string.settings_restoring) + .show(); + AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> { + boolean success = BackupUtils.restore(requireContext(), uri); + try { + SettingsFragment fragment = (SettingsFragment) getParentFragment(); + requireActivity().runOnUiThread(() -> { + alertDialog.dismiss(); + fragment.makeSnackBar(success ? R.string.settings_restore_success : R.string.settings_restore_failed, Snackbar.LENGTH_SHORT); + }); + } catch (Exception e) { + e.printStackTrace(); + } + }); + } + }); + + @Override + public void onCreatePreferencesFix(Bundle savedInstanceState, String rootKey) { + addPreferencesFromResource(R.xml.prefs); + + boolean installed = ConfigManager.getXposedVersionName() != null; + SwitchPreference prefVerboseLogs = findPreference("disable_verbose_log"); + if (prefVerboseLogs != null) { + if (requireActivity().getApplicationInfo().uid / 100000 != 0) { + prefVerboseLogs.setVisible(false); + } else { + prefVerboseLogs.setEnabled(installed); + prefVerboseLogs.setChecked(!ConfigManager.isVerboseLogEnabled()); + prefVerboseLogs.setOnPreferenceChangeListener((preference, newValue) -> { + boolean result = ConfigManager.setVerboseLogEnabled(!(boolean) newValue); + SettingsFragment fragment = (SettingsFragment) getParentFragment(); + if (result && fragment != null) { + Snackbar.make(fragment.binding.snackbar, R.string.reboot_required, Snackbar.LENGTH_SHORT) + .setAction(R.string.reboot, v -> ConfigManager.reboot(false, null, false)) + .show(); + } + return result; + }); + } + } + + SwitchPreference prefEnableResources = findPreference("enable_resources"); + if (prefEnableResources != null) { + prefEnableResources.setEnabled(installed); + prefEnableResources.setChecked(ConfigManager.isResourceHookEnabled()); + prefEnableResources.setOnPreferenceChangeListener((preference, newValue) -> ConfigManager.setResourceHookEnabled((boolean) newValue)); + } + + Preference backup = findPreference("backup"); + if (backup != null) { + backup.setEnabled(installed); + backup.setOnPreferenceClickListener(preference -> { + Calendar now = Calendar.getInstance(); + backupLauncher.launch(String.format(Locale.US, + "LSPosed_%04d%02d%02d_%02d%02d%02d.lsp", + now.get(Calendar.YEAR), now.get(Calendar.MONTH) + 1, + now.get(Calendar.DAY_OF_MONTH), now.get(Calendar.HOUR_OF_DAY), + now.get(Calendar.MINUTE), now.get(Calendar.SECOND))); + return true; + }); + } + + Preference restore = findPreference("restore"); + if (restore != null) { + restore.setEnabled(installed); + restore.setOnPreferenceClickListener(preference -> { + restoreLauncher.launch(new String[]{"*/*"}); + return true; + }); + } + + Preference theme = findPreference("dark_theme"); + if (theme != null) { + theme.setOnPreferenceChangeListener((preference, newValue) -> { + if (!App.getPreferences().getString("dark_theme", ThemeUtil.MODE_NIGHT_FOLLOW_SYSTEM).equals(newValue)) { + DayNightDelegate.setDefaultNightMode(ThemeUtil.getDarkTheme((String) newValue)); + MainActivity activity = (MainActivity) getActivity(); + if (activity != null) { + activity.restart(); + } + } + return true; + }); + } + + Preference black_dark_theme = findPreference("black_dark_theme"); + if (black_dark_theme != null) { + black_dark_theme.setOnPreferenceChangeListener((preference, newValue) -> { + MainActivity activity = (MainActivity) getActivity(); + if (activity != null && ResourceUtils.isNightMode(getResources().getConfiguration())) { + activity.restart(); + } + return true; + }); + } + + Preference primary_color = findPreference("theme_color"); + if (primary_color != null) { + primary_color.setOnPreferenceChangeListener((preference, newValue) -> { + MainActivity activity = (MainActivity) getActivity(); + if (activity != null) { + activity.restart(); + } + return true; + }); + } + + PreferenceCategory prefGroupSystem = findPreference("settings_group_system"); + SwitchPreference prefShowHiddenIcons = findPreference("show_hidden_icon_apps_enabled"); + if (prefGroupSystem != null && prefShowHiddenIcons != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q + && requireActivity().checkSelfPermission(Manifest.permission.WRITE_SECURE_SETTINGS) == PackageManager.PERMISSION_GRANTED) { + prefGroupSystem.setVisible(true); + prefShowHiddenIcons.setVisible(true); + prefShowHiddenIcons.setChecked(Settings.Global.getInt( + requireActivity().getContentResolver(), "show_hidden_icon_apps_enabled", 1) != 0); + prefShowHiddenIcons.setOnPreferenceChangeListener((preference, newValue) -> Settings.Global.putInt(requireActivity().getContentResolver(), + "show_hidden_icon_apps_enabled", (boolean) newValue ? 1 : 0)); + } + + SwitchPreference prefFollowSystemAccent = findPreference("follow_system_accent"); + if (prefFollowSystemAccent != null && (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S || Build.VERSION.SDK_INT == Build.VERSION_CODES.R && Build.VERSION.PREVIEW_SDK_INT != 0)) { + if (primary_color != null) { + primary_color.setVisible(!prefFollowSystemAccent.isChecked()); + } + prefFollowSystemAccent.setVisible(true); + prefFollowSystemAccent.setOnPreferenceChangeListener((preference, newValue) -> { + MainActivity activity = (MainActivity) getActivity(); + if (activity != null) { + activity.restart(); + } + return true; + }); + } + } + + @Override + public RecyclerView onCreateRecyclerView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) { + BorderRecyclerView recyclerView = (BorderRecyclerView) super.onCreateRecyclerView(inflater, parent, savedInstanceState); + RecyclerViewKt.fixEdgeEffect(recyclerView, false, true); + recyclerView.getBorderViewDelegate().setBorderVisibilityChangedListener((top, oldTop, bottom, oldBottom) -> { + SettingsFragment fragment = (SettingsFragment) getParentFragment(); + if (fragment != null) { + fragment.binding.appBar.setRaised(!top); + } + }); + return recyclerView; + } + } + + public void makeSnackBar(@StringRes int text, @Snackbar.Duration int duration) { + Snackbar.make(binding.snackbar, text, duration).show(); + } +} diff --git a/app/src/main/java/org/lsposed/manager/util/NavUtil.java b/app/src/main/java/org/lsposed/manager/util/NavUtil.java index 88047171..b465e480 100644 --- a/app/src/main/java/org/lsposed/manager/util/NavUtil.java +++ b/app/src/main/java/org/lsposed/manager/util/NavUtil.java @@ -20,6 +20,7 @@ package org.lsposed.manager.util; +import android.app.Activity; import android.net.Uri; import androidx.browser.customtabs.CustomTabColorSchemeParams; @@ -32,7 +33,7 @@ import rikka.core.util.ResourceUtils; public final class NavUtil { - public static void startURL(BaseActivity activity, Uri uri) { + public static void startURL(Activity activity, Uri uri) { CustomTabsIntent.Builder customTabsIntent = new CustomTabsIntent.Builder(); customTabsIntent.setShowTitle(true); CustomTabColorSchemeParams params = new CustomTabColorSchemeParams.Builder() @@ -46,7 +47,7 @@ public final class NavUtil { customTabsIntent.build().launchUrl(activity, uri); } - public static void startURL(BaseActivity activity, String url) { + public static void startURL(Activity activity, String url) { startURL(activity, Uri.parse(url)); } } diff --git a/app/src/main/java/org/lsposed/manager/util/NotificationUtil.java b/app/src/main/java/org/lsposed/manager/util/NotificationUtil.java index 3850fffe..f0b1cb1c 100644 --- a/app/src/main/java/org/lsposed/manager/util/NotificationUtil.java +++ b/app/src/main/java/org/lsposed/manager/util/NotificationUtil.java @@ -29,7 +29,7 @@ import android.content.Intent; import androidx.core.app.NotificationCompat; import org.lsposed.manager.R; -import org.lsposed.manager.ui.activity.AppListActivity; +import org.lsposed.manager.ui.activity.MainActivity; public final class NotificationUtil { @@ -51,7 +51,7 @@ public final class NotificationUtil { String title = context.getString(enabled ? R.string.xposed_module_updated_notification_title : R.string.module_is_not_activated_yet); String content = context.getString(enabled ? R.string.xposed_module_updated_notification_content : R.string.module_is_not_activated_yet_detailed, moduleName); - Intent intent = new Intent(context, AppListActivity.class) + Intent intent = new Intent(context, MainActivity.class) .putExtra("modulePackageName", modulePackageName) .putExtra("moduleUserId", moduleUserId) .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); diff --git a/app/src/main/java/org/lsposed/manager/util/chrome/CustomTabsURLSpan.java b/app/src/main/java/org/lsposed/manager/util/chrome/CustomTabsURLSpan.java index 905d0854..ccc57e9b 100644 --- a/app/src/main/java/org/lsposed/manager/util/chrome/CustomTabsURLSpan.java +++ b/app/src/main/java/org/lsposed/manager/util/chrome/CustomTabsURLSpan.java @@ -20,6 +20,7 @@ package org.lsposed.manager.util.chrome; +import android.app.Activity; import android.text.style.URLSpan; import android.view.View; @@ -28,9 +29,9 @@ import org.lsposed.manager.util.NavUtil; public class CustomTabsURLSpan extends URLSpan { - private final BaseActivity activity; + private final Activity activity; - public CustomTabsURLSpan(BaseActivity activity, String url) { + public CustomTabsURLSpan(Activity activity, String url) { super(url); this.activity = activity; } diff --git a/app/src/main/java/org/lsposed/manager/util/chrome/LinkTransformationMethod.java b/app/src/main/java/org/lsposed/manager/util/chrome/LinkTransformationMethod.java index d4888e27..196f33c1 100644 --- a/app/src/main/java/org/lsposed/manager/util/chrome/LinkTransformationMethod.java +++ b/app/src/main/java/org/lsposed/manager/util/chrome/LinkTransformationMethod.java @@ -20,6 +20,7 @@ package org.lsposed.manager.util.chrome; +import android.app.Activity; import android.graphics.Rect; import android.text.Spannable; import android.text.Spanned; @@ -32,9 +33,9 @@ import org.lsposed.manager.ui.activity.base.BaseActivity; public class LinkTransformationMethod implements TransformationMethod { - private final BaseActivity activity; + private final Activity activity; - public LinkTransformationMethod(BaseActivity activity) { + public LinkTransformationMethod(Activity activity) { this.activity = activity; } diff --git a/app/src/main/res/drawable/ic_baseline_arrow_back_24.xml b/app/src/main/res/drawable/ic_baseline_arrow_back_24.xml new file mode 100644 index 00000000..e9a2ed45 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_arrow_back_24.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/layout-sw600dp/fragment_main.xml b/app/src/main/res/layout-sw600dp/fragment_main.xml new file mode 100644 index 00000000..d3b6af42 --- /dev/null +++ b/app/src/main/res/layout-sw600dp/fragment_main.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 01523607..3080eb78 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -1,321 +1,21 @@ - - - + + android:layout_height="match_parent"> - + app:defaultNavHost="true" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintLeft_toLeftOf="parent" + app:layout_constraintRight_toRightOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:navGraph="@navigation/nav_graph" /> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + diff --git a/app/src/main/res/layout/activity_app_list.xml b/app/src/main/res/layout/fragment_app_list.xml similarity index 100% rename from app/src/main/res/layout/activity_app_list.xml rename to app/src/main/res/layout/fragment_app_list.xml diff --git a/app/src/main/res/layout/fragment_home.xml b/app/src/main/res/layout/fragment_home.xml new file mode 100644 index 00000000..9f479d7d --- /dev/null +++ b/app/src/main/res/layout/fragment_home.xml @@ -0,0 +1,325 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_logs.xml b/app/src/main/res/layout/fragment_logs.xml similarity index 100% rename from app/src/main/res/layout/activity_logs.xml rename to app/src/main/res/layout/fragment_logs.xml diff --git a/app/src/main/res/layout/fragment_main.xml b/app/src/main/res/layout/fragment_main.xml new file mode 100644 index 00000000..f57b156d --- /dev/null +++ b/app/src/main/res/layout/fragment_main.xml @@ -0,0 +1,17 @@ + + + + + + + + diff --git a/app/src/main/res/layout/activity_module_detail.xml b/app/src/main/res/layout/fragment_pager.xml similarity index 100% rename from app/src/main/res/layout/activity_module_detail.xml rename to app/src/main/res/layout/fragment_pager.xml diff --git a/app/src/main/res/layout/activity_list.xml b/app/src/main/res/layout/fragment_repo.xml similarity index 100% rename from app/src/main/res/layout/activity_list.xml rename to app/src/main/res/layout/fragment_repo.xml diff --git a/app/src/main/res/layout/activity_settings.xml b/app/src/main/res/layout/fragment_settings.xml similarity index 100% rename from app/src/main/res/layout/activity_settings.xml rename to app/src/main/res/layout/fragment_settings.xml diff --git a/app/src/main/res/navigation/nav_graph.xml b/app/src/main/res/navigation/nav_graph.xml new file mode 100644 index 00000000..01457f96 --- /dev/null +++ b/app/src/main/res/navigation/nav_graph.xml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/navigation/sub_nav_graph.xml b/app/src/main/res/navigation/sub_nav_graph.xml new file mode 100644 index 00000000..f5c3cfe3 --- /dev/null +++ b/app/src/main/res/navigation/sub_nav_graph.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index 9aa7f8fa..cd4de515 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -22,4 +22,6 @@ 48dp 48dp 32dp - \ No newline at end of file + 400dp + 8dp + diff --git a/app/src/main/res/xml/shortcuts.xml b/app/src/main/res/xml/shortcuts.xml index e2017971..5d974e29 100644 --- a/app/src/main/res/xml/shortcuts.xml +++ b/app/src/main/res/xml/shortcuts.xml @@ -28,29 +28,32 @@ android:shortcutShortLabel="@string/Modules">