[app] More UI fix (#1438)
This commit is contained in:
parent
48fd4c042c
commit
5d782a7680
|
|
@ -187,7 +187,7 @@ dependencies {
|
|||
implementation("androidx.preference:preference:1.1.1")
|
||||
implementation("androidx.recyclerview:recyclerview:1.2.1")
|
||||
implementation("androidx.slidingpanelayout:slidingpanelayout:1.2.0-beta01")
|
||||
implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0")
|
||||
implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.2.0-alpha01")
|
||||
implementation("com.github.bumptech.glide:glide:$glideVersion")
|
||||
implementation("com.google.android.material:material:1.5.0-beta01")
|
||||
implementation("com.google.code.gson:gson:2.8.9")
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
|
@ -195,7 +195,6 @@ public class App extends Application {
|
|||
}, new IntentFilter(Intent.ACTION_PACKAGE_CHANGED));
|
||||
|
||||
UpdateUtil.loadRemoteVersion();
|
||||
RepoLoader.getInstance().loadRemoteData();
|
||||
|
||||
executorService.submit(HTML_TEMPLATE);
|
||||
executorService.submit(HTML_TEMPLATE_DARK);
|
||||
|
|
|
|||
|
|
@ -36,7 +36,6 @@ import java.io.File;
|
|||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
|
|
|||
|
|
@ -26,11 +26,8 @@ import android.content.pm.ApplicationInfo;
|
|||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.os.Looper;
|
||||
import android.view.MenuItem;
|
||||
|
||||
import org.lsposed.lspd.models.Application;
|
||||
import org.lsposed.manager.App;
|
||||
import org.lsposed.manager.ConfigManager;
|
||||
import org.lsposed.manager.R;
|
||||
|
||||
|
|
@ -153,6 +150,6 @@ public class AppHelper {
|
|||
|
||||
public static CharSequence getAppLabel(PackageInfo info, PackageManager pm) {
|
||||
if (info == null || info.applicationInfo == null) return null;
|
||||
return appLabel.computeIfAbsent(info, i->i.applicationInfo.loadLabel(pm));
|
||||
return appLabel.computeIfAbsent(info, i -> i.applicationInfo.loadLabel(pm));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,8 +33,6 @@ import android.graphics.Typeface;
|
|||
import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.os.HandlerThread;
|
||||
import android.text.Spannable;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.TextUtils;
|
||||
|
|
@ -61,7 +59,6 @@ import androidx.recyclerview.widget.RecyclerView;
|
|||
import com.bumptech.glide.request.target.CustomTarget;
|
||||
import com.bumptech.glide.request.transition.Transition;
|
||||
import com.google.android.material.checkbox.MaterialCheckBox;
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
|
||||
import org.lsposed.lspd.models.Application;
|
||||
import org.lsposed.manager.App;
|
||||
|
|
@ -96,8 +93,6 @@ public class ScopeAdapter extends EmptyStateRecyclerView.EmptyStateAdapter<Scope
|
|||
private final AppListFragment fragment;
|
||||
private final PackageManager pm;
|
||||
private final SharedPreferences preferences;
|
||||
private final HandlerThread handlerThread = new HandlerThread("appList");
|
||||
private final Handler loadAppListHandler;
|
||||
private final ModuleUtil moduleUtil;
|
||||
|
||||
private final ModuleUtil.InstalledModule module;
|
||||
|
|
@ -121,7 +116,7 @@ public class ScopeAdapter extends EmptyStateRecyclerView.EmptyStateAdapter<Scope
|
|||
};
|
||||
|
||||
private ApplicationInfo selectedInfo;
|
||||
private boolean refreshing = false;
|
||||
private boolean isLoaded = false;
|
||||
private boolean enabled = true;
|
||||
|
||||
public ScopeAdapter(AppListFragment fragment, ModuleUtil.InstalledModule module) {
|
||||
|
|
@ -129,8 +124,6 @@ public class ScopeAdapter extends EmptyStateRecyclerView.EmptyStateAdapter<Scope
|
|||
this.activity = fragment.requireActivity();
|
||||
this.module = module;
|
||||
moduleUtil = ModuleUtil.getInstance();
|
||||
handlerThread.start();
|
||||
loadAppListHandler = new Handler(handlerThread.getLooper());
|
||||
preferences = App.getPreferences();
|
||||
pm = activity.getPackageManager();
|
||||
}
|
||||
|
|
@ -217,15 +210,21 @@ public class ScopeAdapter extends EmptyStateRecyclerView.EmptyStateAdapter<Scope
|
|||
});
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
private void setLoaded(boolean loaded) {
|
||||
fragment.runOnUiThread(() -> {
|
||||
isLoaded = loaded;
|
||||
notifyDataSetChanged();
|
||||
});
|
||||
}
|
||||
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
int itemId = item.getItemId();
|
||||
if (itemId == R.id.use_recommended) {
|
||||
if (!checkedList.isEmpty()) {
|
||||
new BlurBehindDialogBuilder(activity)
|
||||
.setMessage(R.string.use_recommended_message)
|
||||
.setPositiveButton(android.R.string.ok, (dialog, which) -> {
|
||||
checkRecommended();
|
||||
})
|
||||
.setPositiveButton(android.R.string.ok, (dialog, which) -> checkRecommended())
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.show();
|
||||
} else {
|
||||
|
|
@ -244,14 +243,6 @@ public class ScopeAdapter extends EmptyStateRecyclerView.EmptyStateAdapter<Scope
|
|||
} else if (itemId == R.id.item_filter_denylist) {
|
||||
item.setChecked(!item.isChecked());
|
||||
preferences.edit().putBoolean("filter_denylist", item.isChecked()).apply();
|
||||
} else if (itemId == R.id.menu_launch) {
|
||||
Intent launchIntent = AppHelper.getSettingsIntent(module.packageName, module.userId);
|
||||
if (launchIntent != null) {
|
||||
ConfigManager.startActivityAsUserWithFeature(launchIntent, module.userId);
|
||||
} else {
|
||||
Snackbar.make(fragment.binding.snackbar, R.string.module_no_ui, Snackbar.LENGTH_LONG).show();
|
||||
}
|
||||
return true;
|
||||
} else if (itemId == R.id.backup) {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
fragment.backupLauncher.launch(String.format(Locale.ROOT,
|
||||
|
|
@ -279,7 +270,7 @@ public class ScopeAdapter extends EmptyStateRecyclerView.EmptyStateAdapter<Scope
|
|||
ConfigManager.startActivityAsUserWithFeature(launchIntent, module.userId);
|
||||
}
|
||||
} else if (itemId == R.id.menu_compile_speed) {
|
||||
CompileDialogFragment.speed(fragment.getChildFragmentManager(), info, fragment.binding.snackbar);
|
||||
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);
|
||||
|
|
@ -305,10 +296,6 @@ public class ScopeAdapter extends EmptyStateRecyclerView.EmptyStateAdapter<Scope
|
|||
}
|
||||
|
||||
public void onPrepareOptionsMenu(@NonNull Menu menu) {
|
||||
Intent intent = AppHelper.getSettingsIntent(module.packageName, module.userId);
|
||||
if (intent == null) {
|
||||
menu.removeItem(R.id.menu_launch);
|
||||
}
|
||||
List<String> scopeList = module.getScopeList();
|
||||
if (scopeList == null || scopeList.isEmpty()) {
|
||||
menu.removeItem(R.id.use_recommended);
|
||||
|
|
@ -465,26 +452,17 @@ public class ScopeAdapter extends EmptyStateRecyclerView.EmptyStateAdapter<Scope
|
|||
return showList.size();
|
||||
}
|
||||
|
||||
public void onDestroy() {
|
||||
loadAppListHandler.removeCallbacksAndMessages(null);
|
||||
handlerThread.quit();
|
||||
public void refresh() {
|
||||
refresh(false);
|
||||
}
|
||||
|
||||
public void refresh() {
|
||||
synchronized (this) {
|
||||
if (refreshing) {
|
||||
return;
|
||||
}
|
||||
refreshing = true;
|
||||
}
|
||||
loadAppListHandler.removeCallbacksAndMessages(null);
|
||||
boolean force = fragment.binding.swipeRefreshLayout.isRefreshing();
|
||||
if (!force) fragment.binding.progress.setIndeterminate(true);
|
||||
public void refresh(boolean force) {
|
||||
setLoaded(false);
|
||||
enabled = moduleUtil.isModuleEnabled(module.packageName);
|
||||
fragment.binding.masterSwitch.setOnCheckedChangeListener(null);
|
||||
fragment.binding.masterSwitch.setChecked(enabled);
|
||||
fragment.binding.masterSwitch.setOnCheckedChangeListener(switchBarOnCheckedChangeListener);
|
||||
loadAppListHandler.post(() -> {
|
||||
fragment.runAsync(() -> {
|
||||
List<PackageInfo> appList = AppHelper.getAppList(force);
|
||||
denyList = AppHelper.getDenyList(force);
|
||||
var tmpRecList = new HashSet<ApplicationWithEquals>();
|
||||
|
|
@ -545,10 +523,7 @@ public class ScopeAdapter extends EmptyStateRecyclerView.EmptyStateAdapter<Scope
|
|||
|
||||
String queryStr = fragment.searchView != null ? fragment.searchView.getQuery().toString() : "";
|
||||
|
||||
getFilter().filter(queryStr, count -> {
|
||||
refreshing = false;
|
||||
fragment.runOnUiThread((this::notifyDataSetChanged));
|
||||
});
|
||||
fragment.runOnUiThread(() -> getFilter().filter(queryStr));
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -560,7 +535,7 @@ public class ScopeAdapter extends EmptyStateRecyclerView.EmptyStateAdapter<Scope
|
|||
tmpChkList.remove(appInfo.application);
|
||||
}
|
||||
if (!ConfigManager.setModuleScope(module.packageName, tmpChkList)) {
|
||||
Snackbar.make(fragment.binding.snackbar, R.string.failed_to_save_scope_list, Snackbar.LENGTH_SHORT).show();
|
||||
fragment.showHint(R.string.failed_to_save_scope_list, true);
|
||||
if (!isChecked) {
|
||||
tmpChkList.add(appInfo.application);
|
||||
} else {
|
||||
|
|
@ -568,19 +543,16 @@ public class ScopeAdapter extends EmptyStateRecyclerView.EmptyStateAdapter<Scope
|
|||
}
|
||||
buttonView.setChecked(!isChecked);
|
||||
} else if (appInfo.packageName.equals("android")) {
|
||||
Snackbar.make(fragment.binding.snackbar, R.string.reboot_required, Snackbar.LENGTH_SHORT)
|
||||
.setAction(R.string.reboot, v -> ConfigManager.reboot(false))
|
||||
.show();
|
||||
fragment.showHint(R.string.reboot_required, true, R.string.reboot, v -> ConfigManager.reboot(false));
|
||||
} else if (denyList.contains(appInfo.packageName)) {
|
||||
Snackbar.make(fragment.binding.snackbar, activity.getString(R.string.deny_list, appInfo.label), Snackbar.LENGTH_SHORT)
|
||||
.show();
|
||||
fragment.showHint(activity.getString(R.string.deny_list, appInfo.label), true);
|
||||
}
|
||||
checkedList = tmpChkList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLoaded() {
|
||||
return !refreshing;
|
||||
return isLoaded;
|
||||
}
|
||||
|
||||
static class ViewHolder extends RecyclerView.ViewHolder {
|
||||
|
|
@ -613,15 +585,11 @@ public class ScopeAdapter extends EmptyStateRecyclerView.EmptyStateAdapter<Scope
|
|||
protected FilterResults performFiltering(CharSequence constraint) {
|
||||
FilterResults filterResults = new FilterResults();
|
||||
List<AppInfo> filtered = new ArrayList<>();
|
||||
if (constraint.toString().isEmpty()) {
|
||||
filtered.addAll(searchList);
|
||||
} else {
|
||||
String filter = constraint.toString().toLowerCase();
|
||||
for (AppInfo info : searchList) {
|
||||
if (lowercaseContains(info.label.toString(), filter)
|
||||
|| lowercaseContains(info.packageName, filter)) {
|
||||
filtered.add(info);
|
||||
}
|
||||
String filter = constraint.toString().toLowerCase();
|
||||
for (AppInfo info : searchList) {
|
||||
if (lowercaseContains(info.label.toString(), filter)
|
||||
|| lowercaseContains(info.packageName, filter)) {
|
||||
filtered.add(info);
|
||||
}
|
||||
}
|
||||
filterResults.values = filtered;
|
||||
|
|
@ -633,6 +601,7 @@ public class ScopeAdapter extends EmptyStateRecyclerView.EmptyStateAdapter<Scope
|
|||
protected void publishResults(CharSequence constraint, FilterResults results) {
|
||||
//noinspection unchecked
|
||||
showList = (List<AppInfo>) results.values;
|
||||
setLoaded(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -640,13 +609,13 @@ public class ScopeAdapter extends EmptyStateRecyclerView.EmptyStateAdapter<Scope
|
|||
return new SearchView.OnQueryTextListener() {
|
||||
@Override
|
||||
public boolean onQueryTextSubmit(String query) {
|
||||
refresh();
|
||||
getFilter().filter(query);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onQueryTextChange(String newText) {
|
||||
refresh();
|
||||
public boolean onQueryTextChange(String query) {
|
||||
getFilter().filter(query);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
|
@ -654,13 +623,11 @@ public class ScopeAdapter extends EmptyStateRecyclerView.EmptyStateAdapter<Scope
|
|||
|
||||
public void onBackPressed() {
|
||||
fragment.searchView.clearFocus();
|
||||
if (!refreshing && fragment.binding.masterSwitch.isChecked() && checkedList.isEmpty()) {
|
||||
if (isLoaded && fragment.binding.masterSwitch.isChecked() && checkedList.isEmpty()) {
|
||||
var builder = new BlurBehindDialogBuilder(activity);
|
||||
builder.setMessage(!recommendedList.isEmpty() ? R.string.no_scope_selected_has_recommended : R.string.no_scope_selected);
|
||||
if (!recommendedList.isEmpty()) {
|
||||
builder.setPositiveButton(android.R.string.ok, (dialog, which) -> {
|
||||
checkRecommended();
|
||||
});
|
||||
builder.setPositiveButton(android.R.string.ok, (dialog, which) -> checkRecommended());
|
||||
} else {
|
||||
builder.setPositiveButton(android.R.string.cancel, null);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ package org.lsposed.manager.repo;
|
|||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
|
||||
|
|
@ -37,10 +38,9 @@ import java.nio.file.Paths;
|
|||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
import okhttp3.Call;
|
||||
import okhttp3.Callback;
|
||||
|
|
@ -51,24 +51,25 @@ import okhttp3.ResponseBody;
|
|||
public class RepoLoader {
|
||||
private static RepoLoader instance = null;
|
||||
private Map<String, OnlineModule> onlineModules = new HashMap<>();
|
||||
private Map<String, ModuleVersion> latestVersion = new ConcurrentHashMap<>();
|
||||
|
||||
public static class ModuleVersion {
|
||||
public String versionName;
|
||||
public long versionCode;
|
||||
|
||||
private ModuleVersion(long versionCode, String versionName) {
|
||||
this.versionName = versionName;
|
||||
this.versionCode = versionCode;
|
||||
}
|
||||
|
||||
public boolean upgradable(long versionCode, String versionName) {
|
||||
return this.versionCode > versionCode || (this.versionCode == versionCode && !versionName.equals(this.versionName));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private final Map<String, ModuleVersion> latestVersion = new ConcurrentHashMap<>();
|
||||
private final Path repoFile = Paths.get(App.getInstance().getFilesDir().getAbsolutePath(), "repo.json");
|
||||
private final List<Listener> listeners = new CopyOnWriteArrayList<>();
|
||||
private boolean isLoading = false;
|
||||
private final Set<RepoListener> listeners = ConcurrentHashMap.newKeySet();
|
||||
private boolean repoLoaded = false;
|
||||
private static final String originRepoUrl = "https://modules.lsposed.org/";
|
||||
private static final String backupRepoUrl = "https://cdn.jsdelivr.net/gh/Xposed-Modules-Repo/modules@gh-pages/";
|
||||
|
|
@ -81,91 +82,78 @@ public class RepoLoader {
|
|||
public static synchronized RepoLoader getInstance() {
|
||||
if (instance == null) {
|
||||
instance = new RepoLoader();
|
||||
instance.loadRemoteData();
|
||||
App.getExecutorService().submit(instance::loadRemoteData);
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
public void loadRemoteData() {
|
||||
synchronized (this) {
|
||||
if (isLoading) {
|
||||
return;
|
||||
}
|
||||
isLoading = true;
|
||||
}
|
||||
App.getOkHttpClient().newCall(new Request.Builder()
|
||||
.url(repoUrl + "modules.json")
|
||||
.build()).enqueue(new Callback() {
|
||||
@Override
|
||||
public void onFailure(@NonNull Call call, @NonNull IOException e) {
|
||||
Log.e(App.TAG, call.request().url().toString(), e);
|
||||
for (Listener listener : listeners) {
|
||||
listener.onThrowable(e);
|
||||
}
|
||||
synchronized (this) {
|
||||
isLoading = false;
|
||||
if (!repoUrl.equals(backupRepoUrl)) {
|
||||
repoUrl = backupRepoUrl;
|
||||
loadRemoteData();
|
||||
}
|
||||
}
|
||||
}
|
||||
synchronized public void loadRemoteData() {
|
||||
repoLoaded = true;
|
||||
try {
|
||||
var response = App.getOkHttpClient().newCall(new Request.Builder()
|
||||
.url(repoUrl + "modules.json")
|
||||
.build()).execute();
|
||||
|
||||
@Override
|
||||
public void onResponse(@NonNull Call call, @NonNull Response response) {
|
||||
if (response.isSuccessful()) {
|
||||
ResponseBody body = response.body();
|
||||
if (body != null) {
|
||||
try {
|
||||
String bodyString = body.string();
|
||||
Gson gson = new Gson();
|
||||
Map<String, OnlineModule> modules = new HashMap<>();
|
||||
OnlineModule[] repoModules = gson.fromJson(bodyString, OnlineModule[].class);
|
||||
Arrays.stream(repoModules).forEach(onlineModule -> modules.put(onlineModule.getName(), onlineModule));
|
||||
if (response.isSuccessful()) {
|
||||
ResponseBody body = response.body();
|
||||
if (body != null) {
|
||||
try {
|
||||
String bodyString = body.string();
|
||||
Gson gson = new Gson();
|
||||
Map<String, OnlineModule> modules = new HashMap<>();
|
||||
OnlineModule[] repoModules = gson.fromJson(bodyString, OnlineModule[].class);
|
||||
Arrays.stream(repoModules).forEach(onlineModule -> modules.put(onlineModule.getName(), onlineModule));
|
||||
|
||||
latestVersion.clear();
|
||||
for (var module : repoModules) {
|
||||
var release = module.getLatestRelease();
|
||||
if (release == null || release.isEmpty()) continue;
|
||||
var splits = release.split("-", 2);
|
||||
if (splits.length < 2) continue;
|
||||
long verCode;
|
||||
String verName;
|
||||
try {
|
||||
verCode = Long.parseLong(splits[0]);
|
||||
verName = splits[1];
|
||||
} catch (NumberFormatException ignored) {
|
||||
continue;
|
||||
}
|
||||
String pkgName = module.getName();
|
||||
latestVersion.put(pkgName, new ModuleVersion(verCode, verName));
|
||||
Map<String, ModuleVersion> versions = new ConcurrentHashMap<>();
|
||||
for (var module : repoModules) {
|
||||
var release = module.getLatestRelease();
|
||||
if (release == null || release.isEmpty()) continue;
|
||||
var splits = release.split("-", 2);
|
||||
if (splits.length < 2) continue;
|
||||
long verCode;
|
||||
String verName;
|
||||
try {
|
||||
verCode = Long.parseLong(splits[0]);
|
||||
verName = splits[1];
|
||||
} catch (NumberFormatException ignored) {
|
||||
continue;
|
||||
}
|
||||
String pkgName = module.getName();
|
||||
versions.put(pkgName, new ModuleVersion(verCode, verName));
|
||||
}
|
||||
|
||||
onlineModules = modules;
|
||||
Files.write(repoFile, bodyString.getBytes(StandardCharsets.UTF_8));
|
||||
synchronized (this) {
|
||||
repoLoaded = true;
|
||||
}
|
||||
for (Listener listener : listeners) {
|
||||
listener.repoLoaded();
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
Log.e(App.TAG, Log.getStackTraceString(t));
|
||||
for (Listener listener : listeners) {
|
||||
listener.onThrowable(t);
|
||||
}
|
||||
latestVersion = versions;
|
||||
onlineModules = modules;
|
||||
Files.write(repoFile, bodyString.getBytes(StandardCharsets.UTF_8));
|
||||
repoLoaded = true;
|
||||
for (RepoListener listener : listeners) {
|
||||
listener.onRepoLoaded();
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
Log.e(App.TAG, Log.getStackTraceString(t));
|
||||
for (RepoListener listener : listeners) {
|
||||
listener.onThrowable(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
synchronized (this) {
|
||||
isLoading = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (Throwable e) {
|
||||
Log.e(App.TAG, "load remote data", e);
|
||||
for (RepoListener listener : listeners) {
|
||||
listener.onThrowable(e);
|
||||
}
|
||||
if (!repoUrl.equals(backupRepoUrl)) {
|
||||
repoUrl = backupRepoUrl;
|
||||
loadRemoteData();
|
||||
}
|
||||
} finally {
|
||||
repoLoaded = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public ModuleVersion getModuleLatestVersion(String packageName) {
|
||||
return latestVersion.get(packageName);
|
||||
return repoLoaded ? latestVersion.getOrDefault(packageName, null) : null;
|
||||
}
|
||||
|
||||
public void loadRemoteReleases(String packageName) {
|
||||
|
|
@ -179,7 +167,7 @@ public class RepoLoader {
|
|||
repoUrl = backupRepoUrl;
|
||||
loadRemoteReleases(packageName);
|
||||
} else {
|
||||
for (Listener listener : listeners) {
|
||||
for (RepoListener listener : listeners) {
|
||||
listener.onThrowable(e);
|
||||
}
|
||||
}
|
||||
|
|
@ -196,12 +184,12 @@ public class RepoLoader {
|
|||
OnlineModule module = gson.fromJson(bodyString, OnlineModule.class);
|
||||
module.releasesLoaded = true;
|
||||
onlineModules.replace(packageName, module);
|
||||
for (Listener listener : listeners) {
|
||||
listener.moduleReleasesLoaded(module);
|
||||
for (RepoListener listener : listeners) {
|
||||
listener.onModuleReleasesLoaded(module);
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
Log.e(App.TAG, Log.getStackTraceString(t));
|
||||
for (Listener listener : listeners) {
|
||||
for (RepoListener listener : listeners) {
|
||||
listener.onThrowable(t);
|
||||
}
|
||||
}
|
||||
|
|
@ -211,28 +199,30 @@ public class RepoLoader {
|
|||
});
|
||||
}
|
||||
|
||||
public void addListener(Listener listener) {
|
||||
public void addListener(RepoListener listener) {
|
||||
if (!listeners.contains(listener))
|
||||
listeners.add(listener);
|
||||
}
|
||||
|
||||
public void removeListener(Listener listener) {
|
||||
public void removeListener(RepoListener listener) {
|
||||
listeners.remove(listener);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public OnlineModule getOnlineModule(String packageName) {
|
||||
return packageName == null ? null : onlineModules.get(packageName);
|
||||
return !repoLoaded || packageName == null ? null : onlineModules.get(packageName);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Collection<OnlineModule> getOnlineModules() {
|
||||
return onlineModules.values();
|
||||
return repoLoaded ? onlineModules.values() : null;
|
||||
}
|
||||
|
||||
public interface Listener {
|
||||
default void repoLoaded() {
|
||||
public interface RepoListener {
|
||||
default void onRepoLoaded() {
|
||||
}
|
||||
|
||||
default void moduleReleasesLoaded(OnlineModule module) {
|
||||
default void onModuleReleasesLoaded(OnlineModule module) {
|
||||
}
|
||||
|
||||
default void onThrowable(Throwable t) {
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import com.google.android.material.textview.MaterialTextView;
|
|||
import org.lsposed.manager.App;
|
||||
import org.lsposed.manager.ConfigManager;
|
||||
import org.lsposed.manager.R;
|
||||
import org.lsposed.manager.databinding.DialogTitleBinding;
|
||||
import org.lsposed.manager.databinding.DialogWarningBinding;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
|
|
@ -39,18 +40,23 @@ public class FlashDialogBuilder extends BlurBehindDialogBuilder {
|
|||
var pref = App.getPreferences();
|
||||
var notes = pref.getString("release_notes", "");
|
||||
this.zipPath = pref.getString("zip_file", null);
|
||||
setTitle(R.string.update_lsposed);
|
||||
LayoutInflater inflater = LayoutInflater.from(context);
|
||||
|
||||
var title = DialogTitleBinding.inflate(inflater).getRoot();
|
||||
title.setText(R.string.update_lsposed);
|
||||
setCustomTitle(title);
|
||||
|
||||
textView = new MaterialTextView(context);
|
||||
var text = notes + "\n\n\n" + context.getString(R.string.update_lsposed_msg) + "\n\n";
|
||||
textView.setText(text);
|
||||
textView.setMovementMethod(LinkMovementMethod.getInstance());
|
||||
textView.setTextIsSelectable(true);
|
||||
|
||||
LayoutInflater inflater = LayoutInflater.from(context);
|
||||
DialogWarningBinding binding = DialogWarningBinding.inflate(inflater, null, false);
|
||||
binding.container.addView(textView);
|
||||
rootView = binding.getRoot();
|
||||
setView(rootView);
|
||||
title.setOnClickListener(v -> rootView.smoothScrollTo(0, 0));
|
||||
|
||||
setNegativeButton(android.R.string.cancel, cancel);
|
||||
setPositiveButton(R.string.install, null);
|
||||
|
|
|
|||
|
|
@ -19,11 +19,14 @@
|
|||
|
||||
package org.lsposed.manager.ui.dialog;
|
||||
|
||||
import android.content.Context;
|
||||
import android.app.Dialog;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
|
||||
import org.lsposed.manager.BuildConfig;
|
||||
import org.lsposed.manager.ConfigManager;
|
||||
|
|
@ -34,12 +37,14 @@ import java.util.Locale;
|
|||
|
||||
import rikka.core.util.ClipboardUtils;
|
||||
|
||||
public class InfoDialogBuilder extends BlurBehindDialogBuilder {
|
||||
public class InfoDialogBuilder extends DialogFragment {
|
||||
|
||||
public InfoDialogBuilder(@NonNull Context context) {
|
||||
super(context);
|
||||
setTitle(R.string.info);
|
||||
DialogInfoBinding binding = DialogInfoBinding.inflate(LayoutInflater.from(context), null, false);
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
|
||||
var activity = requireActivity();
|
||||
var builder = new BlurBehindDialogBuilder(activity).setTitle(R.string.info);
|
||||
DialogInfoBinding binding = DialogInfoBinding.inflate(LayoutInflater.from(activity), null, false);
|
||||
|
||||
if (ConfigManager.isBinderAlive()) {
|
||||
binding.apiVersion.setText(String.valueOf(ConfigManager.getXposedApiVersion()));
|
||||
|
|
@ -61,37 +66,38 @@ public class InfoDialogBuilder extends BlurBehindDialogBuilder {
|
|||
binding.device.setText(getDevice());
|
||||
binding.systemAbi.setText(Build.SUPPORTED_ABIS[0]);
|
||||
|
||||
setView(binding.getRoot());
|
||||
builder.setView(binding.getRoot());
|
||||
|
||||
setPositiveButton(android.R.string.ok, null);
|
||||
String info = context.getString(R.string.info_api_version) +
|
||||
builder.setPositiveButton(android.R.string.ok, null);
|
||||
String info = activity.getString(R.string.info_api_version) +
|
||||
"\n" +
|
||||
binding.apiVersion.getText() +
|
||||
"\n\n" +
|
||||
context.getString(R.string.info_api) +
|
||||
activity.getString(R.string.info_api) +
|
||||
"\n" +
|
||||
binding.api.getText() +
|
||||
"\n\n" +
|
||||
context.getString(R.string.info_framework_version) +
|
||||
activity.getString(R.string.info_framework_version) +
|
||||
"\n" +
|
||||
binding.frameworkVersion.getText() +
|
||||
"\n\n" +
|
||||
context.getString(R.string.info_manager_version) +
|
||||
activity.getString(R.string.info_manager_version) +
|
||||
"\n" +
|
||||
binding.managerVersion.getText() +
|
||||
"\n\n" +
|
||||
context.getString(R.string.info_system_version) +
|
||||
activity.getString(R.string.info_system_version) +
|
||||
"\n" +
|
||||
binding.systemVersion.getText() +
|
||||
"\n\n" +
|
||||
context.getString(R.string.info_device) +
|
||||
activity.getString(R.string.info_device) +
|
||||
"\n" +
|
||||
binding.device.getText() +
|
||||
"\n\n" +
|
||||
context.getString(R.string.info_system_abi) +
|
||||
activity.getString(R.string.info_system_abi) +
|
||||
"\n" +
|
||||
binding.systemAbi.getText();
|
||||
setNeutralButton(android.R.string.copy, (dialog, which) -> ClipboardUtils.put(context, info));
|
||||
builder.setNeutralButton(android.R.string.copy, (dialog, which) -> ClipboardUtils.put(activity, info));
|
||||
return builder.create();
|
||||
}
|
||||
|
||||
private String getDevice() {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Copyright (C) 2021 LSPosed Contributors
|
||||
*/
|
||||
|
||||
package org.lsposed.manager.ui.dialog;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.os.Bundle;
|
||||
import android.os.RemoteException;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
|
||||
import org.lsposed.manager.App;
|
||||
import org.lsposed.manager.ConfigManager;
|
||||
import org.lsposed.manager.R;
|
||||
import org.lsposed.manager.receivers.LSPManagerServiceHolder;
|
||||
|
||||
public class ShortcutDialog extends DialogFragment {
|
||||
private static boolean shown = false;
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
|
||||
return new BlurBehindDialogBuilder(requireContext())
|
||||
.setTitle(R.string.parasitic_recommend)
|
||||
.setMessage(R.string.parasitic_recommend_summary)
|
||||
.setNegativeButton(R.string.never_show, (dialog, which) ->
|
||||
App.getPreferences().edit().putBoolean("never_show_shortcut", true).apply())
|
||||
.setNeutralButton(R.string.create_shortcut, (dialog, which) -> {
|
||||
try {
|
||||
LSPManagerServiceHolder.getService().createShortcut();
|
||||
} catch (RemoteException ignored) {
|
||||
}
|
||||
})
|
||||
.setPositiveButton(android.R.string.ok, null).create();
|
||||
}
|
||||
|
||||
public static void showIfNeed(FragmentManager fm) {
|
||||
if (App.isParasitic() || !ConfigManager.isBinderAlive()) return;
|
||||
if (App.getPreferences().getBoolean("never_show_shortcut", false)) return;
|
||||
if (shown) return;
|
||||
shown = true;
|
||||
new ShortcutDialog().show(fm, "shortcut");
|
||||
}
|
||||
}
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
package org.lsposed.manager.ui.dialog;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.RemoteException;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.lsposed.manager.App;
|
||||
import org.lsposed.manager.ConfigManager;
|
||||
import org.lsposed.manager.R;
|
||||
import org.lsposed.manager.receivers.LSPManagerServiceHolder;
|
||||
|
||||
public class ShortcutDialogBuilder extends BlurBehindDialogBuilder {
|
||||
private static boolean shown = false;
|
||||
|
||||
private ShortcutDialogBuilder(@NonNull Context context) {
|
||||
super(context);
|
||||
setTitle(R.string.parasitic_recommend);
|
||||
setMessage(R.string.parasitic_recommend_summary);
|
||||
setNegativeButton(R.string.never_show, (dialog, which) ->
|
||||
App.getPreferences().edit().putBoolean("never_show_shortcut", true).apply());
|
||||
setNeutralButton(R.string.create_shortcut, (dialog, which) -> {
|
||||
try {
|
||||
LSPManagerServiceHolder.getService().createShortcut();
|
||||
} catch (RemoteException ignored) {
|
||||
}
|
||||
});
|
||||
setPositiveButton(android.R.string.ok, null);
|
||||
}
|
||||
|
||||
public static void showIfNeed(@NonNull Context context) {
|
||||
if (App.isParasitic() || !ConfigManager.isBinderAlive()) return;
|
||||
if (App.getPreferences().getBoolean("never_show_shortcut", false)) return;
|
||||
if (shown) return;
|
||||
shown = true;
|
||||
new ShortcutDialogBuilder(context).show();
|
||||
}
|
||||
}
|
||||
|
|
@ -19,13 +19,15 @@
|
|||
|
||||
package org.lsposed.manager.ui.dialog;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.app.Dialog;
|
||||
import android.os.Bundle;
|
||||
import android.text.method.LinkMovementMethod;
|
||||
import android.view.LayoutInflater;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.text.HtmlCompat;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
|
||||
import org.lsposed.manager.ConfigManager;
|
||||
import org.lsposed.manager.R;
|
||||
|
|
@ -33,14 +35,15 @@ import org.lsposed.manager.databinding.DialogItemBinding;
|
|||
import org.lsposed.manager.databinding.DialogWarningBinding;
|
||||
import org.lsposed.manager.util.chrome.LinkTransformationMethod;
|
||||
|
||||
public class WarningDialogBuilder extends BlurBehindDialogBuilder {
|
||||
public class WarningDialogBuilder extends DialogFragment {
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
|
||||
var activity = requireActivity();
|
||||
var builder = new BlurBehindDialogBuilder(activity).
|
||||
setTitle(R.string.partial_activated);
|
||||
|
||||
public WarningDialogBuilder(@NonNull Context context) {
|
||||
super(context);
|
||||
Activity activity = (Activity) context;
|
||||
setTitle(R.string.partial_activated);
|
||||
|
||||
LayoutInflater inflater = LayoutInflater.from(context);
|
||||
LayoutInflater inflater = LayoutInflater.from(activity);
|
||||
DialogWarningBinding binding = DialogWarningBinding.inflate(inflater, null, false);
|
||||
|
||||
if (!ConfigManager.isSepolicyLoaded()) {
|
||||
|
|
@ -65,7 +68,9 @@ public class WarningDialogBuilder extends BlurBehindDialogBuilder {
|
|||
item.value.setTransformationMethod(new LinkTransformationMethod(activity));
|
||||
}
|
||||
|
||||
setView(binding.getRoot());
|
||||
setPositiveButton(android.R.string.ok, null);
|
||||
builder.setView(binding.getRoot());
|
||||
builder.setPositiveButton(android.R.string.ok, null);
|
||||
builder.setNeutralButton(R.string.info, (dialog, which) -> new InfoDialogBuilder().show(getParentFragmentManager(), "info"));
|
||||
return builder.create();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,13 +19,13 @@
|
|||
|
||||
package org.lsposed.manager.ui.fragment;
|
||||
|
||||
import android.content.Intent;
|
||||
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 android.widget.Toast;
|
||||
|
||||
import androidx.activity.OnBackPressedCallback;
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
|
|
@ -36,10 +36,10 @@ import androidx.appcompat.widget.SearchView;
|
|||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
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.adapters.AppHelper;
|
||||
import org.lsposed.manager.adapters.ScopeAdapter;
|
||||
import org.lsposed.manager.databinding.FragmentAppListBinding;
|
||||
import org.lsposed.manager.util.BackupUtils;
|
||||
|
|
@ -60,6 +60,15 @@ public class AppListFragment extends BaseFragment {
|
|||
public ActivityResultLauncher<String> backupLauncher;
|
||||
public ActivityResultLauncher<String[]> restoreLauncher;
|
||||
|
||||
private final RecyclerView.AdapterDataObserver observer = new RecyclerView.AdapterDataObserver() {
|
||||
@Override
|
||||
public void onChanged() {
|
||||
if (binding != null && scopeAdapter != null) {
|
||||
binding.swipeRefreshLayout.setRefreshing(!scopeAdapter.isLoaded());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
|
|
@ -68,7 +77,6 @@ public class AppListFragment extends BaseFragment {
|
|||
return binding.getRoot();
|
||||
}
|
||||
binding.appBar.setLiftable(true);
|
||||
binding.appBar.setLifted(true);
|
||||
String title;
|
||||
if (module.userId != 0) {
|
||||
title = String.format(Locale.ROOT, "%s (%d)", module.getAppName(), module.userId);
|
||||
|
|
@ -79,24 +87,33 @@ public class AppListFragment extends BaseFragment {
|
|||
|
||||
scopeAdapter = new ScopeAdapter(this, module);
|
||||
scopeAdapter.setHasStableIds(true);
|
||||
scopeAdapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
|
||||
@Override
|
||||
public void onChanged() {
|
||||
if (binding != null && scopeAdapter != null) {
|
||||
binding.progress.setVisibility(scopeAdapter.isLoaded() ? View.GONE : View.VISIBLE);
|
||||
binding.swipeRefreshLayout.setRefreshing(!scopeAdapter.isLoaded());
|
||||
}
|
||||
}
|
||||
});
|
||||
scopeAdapter.registerAdapterDataObserver(observer);
|
||||
binding.recyclerView.setAdapter(scopeAdapter);
|
||||
binding.recyclerView.setHasFixedSize(true);
|
||||
binding.recyclerView.setLayoutManager(new LinearLayoutManager(requireActivity()));
|
||||
binding.recyclerView.getBorderViewDelegate().setBorderVisibilityChangedListener((top, oldTop, bottom, oldBottom) -> binding.appBar.setLifted(!top));
|
||||
RecyclerViewKt.fixEdgeEffect(binding.recyclerView, false, true);
|
||||
binding.swipeRefreshLayout.setOnRefreshListener(() -> scopeAdapter.refresh());
|
||||
|
||||
binding.swipeRefreshLayout.setOnRefreshListener(() -> scopeAdapter.refresh(true));
|
||||
binding.swipeRefreshLayout.setProgressViewEndTarget(true, binding.swipeRefreshLayout.getProgressViewEndOffset());
|
||||
Intent intent = AppHelper.getSettingsIntent(module.packageName, module.userId);
|
||||
if (intent == null) {
|
||||
binding.fab.setVisibility(View.GONE);
|
||||
} else {
|
||||
binding.fab.setVisibility(View.VISIBLE);
|
||||
binding.fab.setOnClickListener(v -> ConfigManager.startActivityAsUserWithFeature(intent, module.userId));
|
||||
}
|
||||
searchListener = scopeAdapter.getSearchListener();
|
||||
|
||||
setupToolbar(binding.toolbar, title, R.menu.menu_app_list, view -> requireActivity().getOnBackPressedDispatcher().onBackPressed());
|
||||
setupToolbar(binding.toolbar, binding.clickView, title, R.menu.menu_app_list, view -> requireActivity().getOnBackPressedDispatcher().onBackPressed());
|
||||
View.OnClickListener l = v -> {
|
||||
if (searchView.isIconified()) {
|
||||
binding.recyclerView.smoothScrollToPosition(0);
|
||||
binding.appBar.setExpanded(true, true);
|
||||
}
|
||||
};
|
||||
binding.toolbar.setOnClickListener(l);
|
||||
binding.clickView.setOnClickListener(l);
|
||||
|
||||
return binding.getRoot();
|
||||
}
|
||||
|
||||
|
|
@ -116,6 +133,8 @@ public class AppListFragment extends BaseFragment {
|
|||
int moduleUserId = args.getModuleUserId();
|
||||
|
||||
module = ModuleUtil.getInstance().getModule(modulePackageName, moduleUserId);
|
||||
if (module == null)
|
||||
getNavController().navigate(R.id.action_app_list_fragment_to_modules_fragment);
|
||||
|
||||
backupLauncher = registerForActivityResult(new ActivityResultContracts.CreateDocument(),
|
||||
uri -> {
|
||||
|
|
@ -125,11 +144,7 @@ public class AppListFragment extends BaseFragment {
|
|||
BackupUtils.backup(uri, modulePackageName);
|
||||
} catch (Exception e) {
|
||||
var text = App.getInstance().getString(R.string.settings_backup_failed2, e.getMessage());
|
||||
if (binding != null && isResumed()) {
|
||||
Snackbar.make(binding.snackbar, text, Snackbar.LENGTH_LONG).show();
|
||||
} else {
|
||||
Toast.makeText(App.getInstance(), text, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
showHint(text, false);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
@ -141,11 +156,7 @@ public class AppListFragment extends BaseFragment {
|
|||
BackupUtils.restore(uri, modulePackageName);
|
||||
} catch (Exception e) {
|
||||
var text = App.getInstance().getString(R.string.settings_restore_failed2, e.getMessage());
|
||||
if (binding != null && isResumed()) {
|
||||
Snackbar.make(binding.snackbar, text, Snackbar.LENGTH_LONG).show();
|
||||
} else {
|
||||
Toast.makeText(App.getInstance(), text, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
showHint(text, false);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
@ -164,17 +175,10 @@ public class AppListFragment extends BaseFragment {
|
|||
if (scopeAdapter != null) scopeAdapter.refresh();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
if (scopeAdapter != null) scopeAdapter.onDestroy();
|
||||
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
|
||||
scopeAdapter.unregisterAdapterDataObserver(observer);
|
||||
binding = null;
|
||||
}
|
||||
|
||||
|
|
@ -191,6 +195,18 @@ public class AppListFragment extends BaseFragment {
|
|||
super.onPrepareOptionsMenu(menu);
|
||||
searchView = (SearchView) menu.findItem(R.id.menu_search).getActionView();
|
||||
searchView.setOnQueryTextListener(searchListener);
|
||||
searchView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
|
||||
@Override
|
||||
public void onViewAttachedToWindow(View arg0) {
|
||||
binding.appBar.setExpanded(false, true);
|
||||
binding.recyclerView.setNestedScrollingEnabled(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewDetachedFromWindow(View v) {
|
||||
binding.recyclerView.setNestedScrollingEnabled(true);
|
||||
}
|
||||
});
|
||||
scopeAdapter.onPrepareOptionsMenu(menu);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -21,12 +21,16 @@ package org.lsposed.manager.ui.fragment;
|
|||
|
||||
import android.app.Activity;
|
||||
import android.view.View;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.navigation.NavController;
|
||||
import androidx.navigation.fragment.NavHostFragment;
|
||||
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
|
||||
import org.lsposed.manager.App;
|
||||
import org.lsposed.manager.R;
|
||||
|
||||
|
|
@ -41,22 +45,24 @@ public class BaseFragment extends Fragment {
|
|||
return NavHostFragment.findNavController(this);
|
||||
}
|
||||
|
||||
public void setupToolbar(Toolbar toolbar, int title) {
|
||||
setupToolbar(toolbar, getString(title), -1);
|
||||
public void setupToolbar(Toolbar toolbar, View tipsView, int title) {
|
||||
setupToolbar(toolbar, tipsView, getString(title), -1);
|
||||
}
|
||||
|
||||
public void setupToolbar(Toolbar toolbar, int title, int menu) {
|
||||
setupToolbar(toolbar, getString(title), menu, null);
|
||||
public void setupToolbar(Toolbar toolbar, View tipsView, int title, int menu) {
|
||||
setupToolbar(toolbar, tipsView, getString(title), menu, null);
|
||||
}
|
||||
|
||||
public void setupToolbar(Toolbar toolbar, String title, int menu) {
|
||||
setupToolbar(toolbar, title, menu, null);
|
||||
public void setupToolbar(Toolbar toolbar, View tipsView, String title, int menu) {
|
||||
setupToolbar(toolbar, tipsView, title, menu, null);
|
||||
}
|
||||
|
||||
public void setupToolbar(Toolbar toolbar, String title, int menu, View.OnClickListener navigationOnClickListener) {
|
||||
public void setupToolbar(Toolbar toolbar, View tipsView, String title, int menu, View.OnClickListener navigationOnClickListener) {
|
||||
toolbar.setNavigationOnClickListener(navigationOnClickListener == null ? (v -> navigateUp()) : navigationOnClickListener);
|
||||
toolbar.setNavigationIcon(R.drawable.ic_baseline_arrow_back_24);
|
||||
toolbar.setTitle(title);
|
||||
toolbar.setTooltipText(title);
|
||||
if (tipsView != null) tipsView.setTooltipText(title);
|
||||
if (menu != -1) {
|
||||
toolbar.inflateMenu(menu);
|
||||
toolbar.setOnMenuItemClickListener(this::onOptionsItemSelected);
|
||||
|
|
@ -74,4 +80,29 @@ public class BaseFragment extends Fragment {
|
|||
activity.runOnUiThread(runnable);
|
||||
}
|
||||
}
|
||||
|
||||
public void showHint(@StringRes int res, boolean lengthShort, @StringRes int actionRes, View.OnClickListener action) {
|
||||
showHint(getString(res), lengthShort, getString(actionRes), action);
|
||||
}
|
||||
|
||||
public void showHint(@StringRes int res, boolean lengthShort) {
|
||||
showHint(getString(res), lengthShort, null, null);
|
||||
}
|
||||
|
||||
public void showHint(CharSequence str, boolean lengthShort) {
|
||||
showHint(str, lengthShort, null, null);
|
||||
}
|
||||
|
||||
public void showHint(CharSequence str, boolean lengthShort, CharSequence actionStr, View.OnClickListener action) {
|
||||
if (isResumed()) {
|
||||
var container = requireActivity().findViewById(R.id.container);
|
||||
if (container != null) {
|
||||
var snackbar = Snackbar.make(container, str, lengthShort ? Snackbar.LENGTH_SHORT : Snackbar.LENGTH_LONG);
|
||||
if (actionStr != null && action != null) snackbar.setAction(actionStr, action);
|
||||
snackbar.show();
|
||||
return;
|
||||
}
|
||||
}
|
||||
Toast.makeText(requireContext(), str, lengthShort ? Toast.LENGTH_SHORT : Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,11 +31,10 @@ import android.view.View;
|
|||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AppCompatDialogFragment;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
|
||||
import org.lsposed.manager.App;
|
||||
import org.lsposed.manager.R;
|
||||
import org.lsposed.manager.databinding.FragmentCompileDialogBinding;
|
||||
|
|
@ -47,19 +46,21 @@ import java.lang.ref.WeakReference;
|
|||
@SuppressWarnings("deprecation")
|
||||
public class CompileDialogFragment extends AppCompatDialogFragment {
|
||||
private ApplicationInfo appInfo;
|
||||
private View snackBar;
|
||||
|
||||
public static void speed(FragmentManager fragmentManager, ApplicationInfo info, View snackBar) {
|
||||
public static void speed(FragmentManager fragmentManager, ApplicationInfo info) {
|
||||
CompileDialogFragment fragment = new CompileDialogFragment();
|
||||
fragment.setCancelable(false);
|
||||
fragment.appInfo = info;
|
||||
fragment.snackBar = snackBar;
|
||||
var bundle = new Bundle();
|
||||
bundle.putParcelable("appInfo", info);
|
||||
fragment.setArguments(bundle);
|
||||
fragment.show(fragmentManager, "compile_dialog");
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
var arguments = getArguments();
|
||||
appInfo = arguments != null ? arguments.getParcelable("appInfo") : null;
|
||||
if (appInfo == null) {
|
||||
throw new IllegalStateException("appInfo should not be null.");
|
||||
}
|
||||
|
|
@ -67,7 +68,6 @@ public class CompileDialogFragment extends AppCompatDialogFragment {
|
|||
FragmentCompileDialogBinding binding = FragmentCompileDialogBinding.inflate(LayoutInflater.from(requireActivity()), null, false);
|
||||
final PackageManager pm = requireContext().getPackageManager();
|
||||
var builder = new BlurBehindDialogBuilder(requireActivity())
|
||||
.setIcon(appInfo.loadIcon(pm))
|
||||
.setTitle(appInfo.loadLabel(pm))
|
||||
.setView(binding.getRoot());
|
||||
|
||||
|
|
@ -75,8 +75,8 @@ public class CompileDialogFragment extends AppCompatDialogFragment {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(@NonNull Context context) {
|
||||
super.onAttach(context);
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
new CompileTask(this).executeOnExecutor(App.getExecutorService(), appInfo.packageName);
|
||||
}
|
||||
|
||||
|
|
@ -118,9 +118,8 @@ public class CompileDialogFragment extends AppCompatDialogFragment {
|
|||
if (fragment != null) {
|
||||
fragment.dismissAllowingStateLoss();
|
||||
var parent = fragment.getParentFragment();
|
||||
if (fragment.snackBar != null && parent != null && parent.isResumed()) {
|
||||
Snackbar.make(fragment.snackBar, text, Snackbar.LENGTH_LONG).show();
|
||||
return;
|
||||
if (parent instanceof BaseFragment) {
|
||||
((BaseFragment) parent).showHint(text, true);
|
||||
}
|
||||
}
|
||||
Toast.makeText(context, text, Toast.LENGTH_LONG).show();
|
||||
|
|
|
|||
|
|
@ -20,17 +20,18 @@
|
|||
package org.lsposed.manager.ui.fragment;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Dialog;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.text.method.LinkMovementMethod;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.text.HtmlCompat;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
|
||||
import com.google.android.material.color.MaterialColors;
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
|
|
@ -44,7 +45,7 @@ import org.lsposed.manager.repo.RepoLoader;
|
|||
import org.lsposed.manager.ui.dialog.BlurBehindDialogBuilder;
|
||||
import org.lsposed.manager.ui.dialog.FlashDialogBuilder;
|
||||
import org.lsposed.manager.ui.dialog.InfoDialogBuilder;
|
||||
import org.lsposed.manager.ui.dialog.ShortcutDialogBuilder;
|
||||
import org.lsposed.manager.ui.dialog.ShortcutDialog;
|
||||
import org.lsposed.manager.ui.dialog.WarningDialogBuilder;
|
||||
import org.lsposed.manager.util.ModuleUtil;
|
||||
import org.lsposed.manager.util.NavUtil;
|
||||
|
|
@ -56,7 +57,7 @@ import java.util.Locale;
|
|||
|
||||
import rikka.core.util.ResourceUtils;
|
||||
|
||||
public class HomeFragment extends BaseFragment implements RepoLoader.Listener, ModuleUtil.ModuleListener {
|
||||
public class HomeFragment extends BaseFragment implements RepoLoader.RepoListener, ModuleUtil.ModuleListener {
|
||||
|
||||
private FragmentHomeBinding binding;
|
||||
|
||||
|
|
@ -66,15 +67,16 @@ public class HomeFragment extends BaseFragment implements RepoLoader.Listener, M
|
|||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
ShortcutDialogBuilder.showIfNeed(requireContext());
|
||||
ShortcutDialog.showIfNeed(getChildFragmentManager());
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
binding = FragmentHomeBinding.inflate(inflater, container, false);
|
||||
|
||||
setupToolbar(binding.toolbar, getString(R.string.app_name), R.menu.menu_home);
|
||||
setupToolbar(binding.toolbar, null, R.string.app_name);
|
||||
binding.toolbar.setNavigationIcon(null);
|
||||
binding.toolbar.setOnClickListener(v -> showAbout());
|
||||
binding.appBar.setLiftable(true);
|
||||
binding.nestedScrollView.getBorderViewDelegate().setBorderVisibilityChangedListener((top, oldTop, bottom, oldBottom) -> binding.appBar.setLifted(!top));
|
||||
|
||||
|
|
@ -82,9 +84,9 @@ public class HomeFragment extends BaseFragment implements RepoLoader.Listener, M
|
|||
binding.status.setOnClickListener(v -> {
|
||||
if (ConfigManager.isBinderAlive() && !UpdateUtil.needUpdate()) {
|
||||
if (!ConfigManager.isSepolicyLoaded() || !ConfigManager.systemServerRequested() || !ConfigManager.dex2oatFlagsLoaded()) {
|
||||
new WarningDialogBuilder(activity).show();
|
||||
new WarningDialogBuilder().show(getChildFragmentManager(), "warning");
|
||||
} else {
|
||||
new InfoDialogBuilder(activity).show();
|
||||
new InfoDialogBuilder().show(getChildFragmentManager(), "info");
|
||||
}
|
||||
} else {
|
||||
if (UpdateUtil.canInstall()) {
|
||||
|
|
@ -105,43 +107,16 @@ public class HomeFragment extends BaseFragment implements RepoLoader.Listener, M
|
|||
binding.download.setOnClickListener(new StartFragmentListener(R.id.action_repo_fragment, false));
|
||||
binding.logs.setOnClickListener(new StartFragmentListener(R.id.action_logs_fragment, true));
|
||||
binding.settings.setOnClickListener(new StartFragmentListener(R.id.action_settings_fragment, false));
|
||||
binding.issue.setOnClickListener(view -> NavUtil.startURL(activity, "https://github.com/LSPosed/LSPosed/issues"));
|
||||
binding.issue.setOnClickListener(view -> NavUtil.startURL(activity, "https://github.com/LSPosed/LSPosed/issues/new/choose"));
|
||||
|
||||
updateStates(requireActivity(), ConfigManager.isBinderAlive(), UpdateUtil.needUpdate());
|
||||
|
||||
repoLoader.addListener(this);
|
||||
moduleUtil.addListener(this);
|
||||
if (repoLoader.isRepoLoaded()) {
|
||||
repoLoaded();
|
||||
}
|
||||
onModulesReloaded();
|
||||
return binding.getRoot();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
|
||||
int itemId = item.getItemId();
|
||||
if (itemId == R.id.menu_refresh) {
|
||||
updateStates(requireActivity(), ConfigManager.isBinderAlive(), UpdateUtil.needUpdate());
|
||||
} else if (itemId == R.id.menu_info) {
|
||||
new InfoDialogBuilder(requireActivity()).setTitle(R.string.info).show();
|
||||
} else if (itemId == R.id.menu_about) {
|
||||
Activity activity = requireActivity();
|
||||
DialogAboutBinding binding = DialogAboutBinding.inflate(LayoutInflater.from(requireActivity()), null, false);
|
||||
binding.designAboutTitle.setText(R.string.app_name);
|
||||
binding.designAboutInfo.setMovementMethod(LinkMovementMethod.getInstance());
|
||||
binding.designAboutInfo.setTransformationMethod(new LinkTransformationMethod(activity));
|
||||
binding.designAboutInfo.setText(HtmlCompat.fromHtml(getString(
|
||||
R.string.about_view_source_code,
|
||||
"<b><a href=\"https://github.com/LSPosed/LSPosed\">GitHub</a></b>",
|
||||
"<b><a href=\"https://t.me/LSPosed\">Telegram</a></b>"), HtmlCompat.FROM_HTML_MODE_LEGACY));
|
||||
binding.designAboutVersion.setText(String.format(Locale.ROOT, "%s (%s)", BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE));
|
||||
new BlurBehindDialogBuilder(activity)
|
||||
.setView(binding.getRoot())
|
||||
.show();
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
private void updateStates(Activity activity, boolean binderAlive, boolean needUpdate) {
|
||||
int cardBackgroundColor;
|
||||
if (binderAlive) {
|
||||
|
|
@ -186,7 +161,7 @@ public class HomeFragment extends BaseFragment implements RepoLoader.Listener, M
|
|||
binding.download.setVisibility(View.GONE);
|
||||
}
|
||||
binding.statusIcon.setImageResource(R.drawable.ic_round_error_outline_24);
|
||||
Snackbar.make(binding.snackbar, R.string.lsposed_not_active, Snackbar.LENGTH_INDEFINITE).show();
|
||||
showHint(R.string.lsposed_not_active, false);
|
||||
}
|
||||
cardBackgroundColor = MaterialColors.harmonizeWithPrimary(activity, cardBackgroundColor);
|
||||
binding.status.setCardBackgroundColor(cardBackgroundColor);
|
||||
|
|
@ -194,10 +169,33 @@ public class HomeFragment extends BaseFragment implements RepoLoader.Listener, M
|
|||
binding.status.setOutlineSpotShadowColor(cardBackgroundColor);
|
||||
binding.status.setOutlineAmbientShadowColor(cardBackgroundColor);
|
||||
}
|
||||
binding.about.setOnClickListener(v -> showAbout());
|
||||
}
|
||||
|
||||
public static class AboutDialog extends DialogFragment {
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
|
||||
DialogAboutBinding binding = DialogAboutBinding.inflate(LayoutInflater.from(requireActivity()), null, false);
|
||||
binding.designAboutTitle.setText(R.string.app_name);
|
||||
binding.designAboutInfo.setMovementMethod(LinkMovementMethod.getInstance());
|
||||
binding.designAboutInfo.setTransformationMethod(new LinkTransformationMethod(requireActivity()));
|
||||
binding.designAboutInfo.setText(HtmlCompat.fromHtml(getString(
|
||||
R.string.about_view_source_code,
|
||||
"<b><a href=\"https://github.com/LSPosed/LSPosed\">GitHub</a></b>",
|
||||
"<b><a href=\"https://t.me/LSPosed\">Telegram</a></b>"), HtmlCompat.FROM_HTML_MODE_LEGACY));
|
||||
binding.designAboutVersion.setText(String.format(Locale.ROOT, "%s (%s)", BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE));
|
||||
return new BlurBehindDialogBuilder(requireContext())
|
||||
.setView(binding.getRoot()).create();
|
||||
}
|
||||
}
|
||||
|
||||
private void showAbout() {
|
||||
new AboutDialog().show(getChildFragmentManager(), "about");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void repoLoaded() {
|
||||
public void onRepoLoaded() {
|
||||
final int[] count = new int[]{0};
|
||||
HashSet<String> processedModules = new HashSet<>();
|
||||
var modules = moduleUtil.getModules();
|
||||
|
|
@ -228,7 +226,7 @@ public class HomeFragment extends BaseFragment implements RepoLoader.Listener, M
|
|||
|
||||
@Override
|
||||
public void onModulesReloaded() {
|
||||
if (repoLoader.isRepoLoaded()) repoLoaded();
|
||||
onRepoLoaded();
|
||||
setModulesSummary(moduleUtil.getEnabledModulesCount());
|
||||
}
|
||||
|
||||
|
|
@ -244,7 +242,7 @@ public class HomeFragment extends BaseFragment implements RepoLoader.Listener, M
|
|||
@Override
|
||||
public void onClick(View v) {
|
||||
if (requireInstalled && !ConfigManager.isBinderAlive()) {
|
||||
Snackbar.make(binding.snackbar, R.string.lsposed_not_active, Snackbar.LENGTH_LONG).show();
|
||||
showHint(R.string.lsposed_not_active, false);
|
||||
} else {
|
||||
getNavController().navigate(fragment);
|
||||
}
|
||||
|
|
@ -260,7 +258,7 @@ public class HomeFragment extends BaseFragment implements RepoLoader.Listener, M
|
|||
}
|
||||
|
||||
private void setModulesSummary(int moduleCount) {
|
||||
runOnUiThread(() -> binding.modulesSummary.setText(moduleCount == - 1? getString(R.string.loading) : getResources().getQuantityString(R.plurals.modules_enabled_count, moduleCount, moduleCount)));
|
||||
runOnUiThread(() -> binding.modulesSummary.setText(moduleCount == -1 ? getString(R.string.loading) : getResources().getQuantityString(R.plurals.modules_enabled_count, moduleCount, moduleCount)));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -14,57 +14,74 @@
|
|||
* You should have received a copy of the GNU General Public License
|
||||
* along with LSPosed. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Copyright (C) 2020 EdXposed Contributors
|
||||
* Copyright (C) 2021 LSPosed Contributors
|
||||
*/
|
||||
|
||||
package org.lsposed.manager.ui.fragment;
|
||||
|
||||
import static org.lsposed.manager.App.TAG;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ScrollView;
|
||||
import android.widget.Toast;
|
||||
import android.widget.HorizontalScrollView;
|
||||
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
import androidx.activity.result.contract.ActivityResultContracts;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.viewpager2.adapter.FragmentStateAdapter;
|
||||
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
import com.google.android.material.tabs.TabLayout;
|
||||
import com.google.android.material.tabs.TabLayoutMediator;
|
||||
import com.google.android.material.textview.MaterialTextView;
|
||||
|
||||
import org.lsposed.manager.App;
|
||||
import org.lsposed.manager.ConfigManager;
|
||||
import org.lsposed.manager.R;
|
||||
import org.lsposed.manager.databinding.FragmentLogsBinding;
|
||||
import org.lsposed.manager.ui.dialog.BlurBehindDialogBuilder;
|
||||
import org.lsposed.manager.databinding.FragmentPagerBinding;
|
||||
import org.lsposed.manager.databinding.ItemLogTextviewBinding;
|
||||
import org.lsposed.manager.databinding.SwiperefreshRecyclerviewBinding;
|
||||
import org.lsposed.manager.ui.widget.EmptyStateRecyclerView;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
import java.util.zip.Deflater;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
import rikka.core.os.FileUtils;
|
||||
import rikka.recyclerview.RecyclerViewKt;
|
||||
|
||||
public class LogsFragment extends BaseFragment {
|
||||
private boolean verbose = false;
|
||||
private final Handler handler = new Handler(Looper.getMainLooper());
|
||||
private FragmentLogsBinding binding;
|
||||
private FragmentPagerBinding binding;
|
||||
private LogPageAdapter adapter;
|
||||
private MenuItem wordWrap;
|
||||
|
||||
interface OptionsItemSelectListener {
|
||||
boolean onOptionsItemSelected(@NonNull MenuItem item);
|
||||
}
|
||||
|
||||
private OptionsItemSelectListener optionsItemSelectListener;
|
||||
|
||||
private final ActivityResultLauncher<String> saveLogsLauncher = registerForActivityResult(
|
||||
new ActivityResultContracts.CreateDocument(),
|
||||
uri -> {
|
||||
|
|
@ -78,11 +95,7 @@ public class LogsFragment extends BaseFragment {
|
|||
os.finish();
|
||||
} catch (IOException e) {
|
||||
var text = context.getString(R.string.logs_save_failed2, e.getMessage());
|
||||
if (binding != null && isResumed()) {
|
||||
Snackbar.make(binding.snackbar, text, Snackbar.LENGTH_LONG).show();
|
||||
} else {
|
||||
Toast.makeText(context, text, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
showHint(text, false);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
@ -90,54 +103,57 @@ public class LogsFragment extends BaseFragment {
|
|||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
binding = FragmentLogsBinding.inflate(inflater, container, false);
|
||||
setupToolbar(binding.toolbar, R.string.Logs, R.menu.menu_logs);
|
||||
|
||||
binding.slidingTabs.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
|
||||
@Override
|
||||
public void onTabSelected(TabLayout.Tab tab) {
|
||||
verbose = tab.getPosition() == 1;
|
||||
reloadLogs();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTabUnselected(TabLayout.Tab tab) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTabReselected(TabLayout.Tab tab) {
|
||||
binding = FragmentPagerBinding.inflate(inflater, container, false);
|
||||
binding.appBar.setLiftable(true);
|
||||
setupToolbar(binding.toolbar, binding.clickView, R.string.Logs, R.menu.menu_logs);
|
||||
binding.toolbar.setSubtitle(ConfigManager.isVerboseLogEnabled() ? R.string.enabled_verbose_log : R.string.disabled_verbose_log);
|
||||
adapter = new LogPageAdapter(this);
|
||||
binding.viewPager.setAdapter(adapter);
|
||||
new TabLayoutMediator(binding.tabLayout, binding.viewPager, (tab, position) -> tab.setText((int) adapter.getItemId(position))).attach();
|
||||
|
||||
binding.tabLayout.addOnLayoutChangeListener((view, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
|
||||
ViewGroup vg = (ViewGroup) binding.tabLayout.getChildAt(0);
|
||||
int tabLayoutWidth = IntStream.range(0, binding.tabLayout.getTabCount()).map(i -> vg.getChildAt(i).getWidth()).sum();
|
||||
if (tabLayoutWidth <= binding.getRoot().getWidth()) {
|
||||
binding.tabLayout.setTabMode(TabLayout.MODE_FIXED);
|
||||
binding.tabLayout.setTabGravity(TabLayout.GRAVITY_FILL);
|
||||
}
|
||||
});
|
||||
|
||||
return binding.getRoot();
|
||||
}
|
||||
|
||||
public void setOptionsItemSelectListener(OptionsItemSelectListener optionsItemSelectListener) {
|
||||
this.optionsItemSelectListener = optionsItemSelectListener;
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
reloadLogs();
|
||||
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
|
||||
var itemId = item.getItemId();
|
||||
if (itemId == R.id.menu_save) {
|
||||
save();
|
||||
return true;
|
||||
} else if (itemId == R.id.menu_word_wrap) {
|
||||
item.setChecked(!item.isChecked());
|
||||
App.getPreferences().edit().putBoolean("enable_word_wrap", item.isChecked()).apply();
|
||||
binding.viewPager.setUserInputEnabled(item.isChecked());
|
||||
adapter.refresh();
|
||||
return true;
|
||||
}
|
||||
if (optionsItemSelectListener != null) {
|
||||
if (optionsItemSelectListener.onOptionsItemSelected(item))
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
|
||||
int itemId = item.getItemId();
|
||||
if (itemId == R.id.menu_scroll_top) {
|
||||
binding.scrollView.fullScroll(ScrollView.FOCUS_UP);
|
||||
} else if (itemId == R.id.menu_scroll_down) {
|
||||
binding.scrollView.fullScroll(ScrollView.FOCUS_DOWN);
|
||||
} else if (itemId == R.id.menu_refresh) {
|
||||
reloadLogs();
|
||||
return true;
|
||||
} else if (itemId == R.id.menu_save) {
|
||||
save();
|
||||
return true;
|
||||
} else if (itemId == R.id.menu_clear) {
|
||||
clear();
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
public void onPrepareOptionsMenu(@NonNull Menu menu) {
|
||||
super.onPrepareOptionsMenu(menu);
|
||||
wordWrap = menu.findItem(R.id.menu_word_wrap);
|
||||
wordWrap.setChecked(App.getPreferences().getBoolean("enable_word_wrap", false));
|
||||
binding.viewPager.setUserInputEnabled(wordWrap.isChecked());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -147,21 +163,6 @@ public class LogsFragment extends BaseFragment {
|
|||
binding = null;
|
||||
}
|
||||
|
||||
private void reloadLogs() {
|
||||
var parcelFileDescriptor = ConfigManager.getLog(verbose);
|
||||
if (parcelFileDescriptor != null)
|
||||
new LogsReader().execute(parcelFileDescriptor);
|
||||
}
|
||||
|
||||
private void clear() {
|
||||
if (ConfigManager.clearLogs(verbose)) {
|
||||
Snackbar.make(binding.snackbar, R.string.logs_cleared, Snackbar.LENGTH_SHORT).show();
|
||||
binding.body.setText("");
|
||||
} else {
|
||||
Snackbar.make(binding.snackbar, R.string.logs_clear_failed_2, Snackbar.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
|
||||
private void save() {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
String filename = String.format(Locale.ROOT, "LSPosed_%s.zip", now.toString());
|
||||
|
|
@ -176,7 +177,7 @@ public class LogsFragment extends BaseFragment {
|
|||
FileUtils.copy(is, os);
|
||||
os.closeEntry();
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, name, e);
|
||||
Log.w(App.TAG, name, e);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -187,53 +188,7 @@ public class LogsFragment extends BaseFragment {
|
|||
FileUtils.copy(is, os);
|
||||
os.closeEntry();
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, name, e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
private class LogsReader extends AsyncTask<ParcelFileDescriptor, Integer, String> {
|
||||
private AlertDialog mProgressDialog;
|
||||
private final Runnable mRunnable = () -> {
|
||||
synchronized (LogsReader.this) {
|
||||
if (!requireActivity().isFinishing()) {
|
||||
mProgressDialog.show();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
synchronized protected void onPreExecute() {
|
||||
mProgressDialog = new BlurBehindDialogBuilder(requireActivity()).create();
|
||||
mProgressDialog.setMessage(getString(R.string.loading));
|
||||
mProgressDialog.setCancelable(false);
|
||||
handler.postDelayed(mRunnable, 300);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String doInBackground(ParcelFileDescriptor... log) {
|
||||
Thread.currentThread().setPriority(Thread.NORM_PRIORITY + 2);
|
||||
try (var pfd = log[0];
|
||||
var inputStream = new FileInputStream(pfd.getFileDescriptor())) {
|
||||
int size = Math.toIntExact(pfd.getStatSize()); // max 4MiB
|
||||
var logs = new ByteArrayOutputStream(size);
|
||||
FileUtils.copy(inputStream, logs);
|
||||
return logs.toString();
|
||||
} catch (IOException e) {
|
||||
return requireActivity().getResources().getString(R.string.logs_cannot_read)
|
||||
+ "\n" + Log.getStackTraceString(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
synchronized protected void onPostExecute(String logs) {
|
||||
binding.body.setText(logs);
|
||||
|
||||
handler.removeCallbacks(mRunnable);
|
||||
if (mProgressDialog.isShowing()) {
|
||||
mProgressDialog.dismiss();
|
||||
}
|
||||
Log.w(App.TAG, name, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -243,19 +198,245 @@ public class LogsFragment extends BaseFragment {
|
|||
super.onDestroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(@NonNull Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
outState.putInt(LogsFragment.class.getName() + "." + "tab", binding.slidingTabs.getSelectedTabPosition());
|
||||
public static class LogFragment extends BaseFragment {
|
||||
public static final int SCROLL_THRESHOLD = 500;
|
||||
protected boolean verbose;
|
||||
protected SwiperefreshRecyclerviewBinding binding;
|
||||
protected LogAdaptor adaptor;
|
||||
protected LinearLayoutManager layoutManager;
|
||||
|
||||
class LogAdaptor extends EmptyStateRecyclerView.EmptyStateAdapter<LogAdaptor.ViewHolder> {
|
||||
private List<CharSequence> log = Collections.emptyList();
|
||||
private boolean isLoaded = false;
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
return new ViewHolder(ItemLogTextviewBinding.inflate(getLayoutInflater(), parent, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
|
||||
holder.item.setText(log.get(position));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return log.size();
|
||||
}
|
||||
|
||||
void refresh() {
|
||||
isLoaded = true;
|
||||
runOnUiThread(this::notifyDataSetChanged);
|
||||
}
|
||||
|
||||
void fullRefresh() {
|
||||
runAsync(() -> {
|
||||
isLoaded = false;
|
||||
try (var parcelFileDescriptor = ConfigManager.getLog(verbose);
|
||||
var br = new BufferedReader(new InputStreamReader(new FileInputStream(parcelFileDescriptor != null ? parcelFileDescriptor.getFileDescriptor() : null)))) {
|
||||
log = br.lines().parallel().collect(Collectors.toList());
|
||||
} catch (Throwable e) {
|
||||
log = Arrays.asList(Log.getStackTraceString(e).split("\n"));
|
||||
} finally {
|
||||
refresh();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLoaded() {
|
||||
return isLoaded;
|
||||
}
|
||||
|
||||
class ViewHolder extends RecyclerView.ViewHolder {
|
||||
final MaterialTextView item;
|
||||
|
||||
public ViewHolder(ItemLogTextviewBinding binding) {
|
||||
super(binding.getRoot());
|
||||
item = binding.logItem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected LogAdaptor createAdaptor() {
|
||||
return new LogAdaptor();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
binding = SwiperefreshRecyclerviewBinding.inflate(getLayoutInflater(), container, false);
|
||||
var arguments = getArguments();
|
||||
if (arguments == null) return null;
|
||||
verbose = arguments.getBoolean("verbose");
|
||||
adaptor = createAdaptor();
|
||||
binding.recyclerView.setAdapter(adaptor);
|
||||
layoutManager = new LinearLayoutManager(requireActivity());
|
||||
binding.recyclerView.setLayoutManager(layoutManager);
|
||||
binding.swipeRefreshLayout.setProgressViewEndTarget(true, binding.swipeRefreshLayout.getProgressViewEndOffset());
|
||||
RecyclerViewKt.fixEdgeEffect(binding.recyclerView, false, true);
|
||||
binding.swipeRefreshLayout.setOnRefreshListener(adaptor::fullRefresh);
|
||||
adaptor.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
|
||||
@Override
|
||||
public void onChanged() {
|
||||
binding.swipeRefreshLayout.setRefreshing(!adaptor.isLoaded());
|
||||
}
|
||||
});
|
||||
adaptor.fullRefresh();
|
||||
return binding.getRoot();
|
||||
}
|
||||
|
||||
public void scrollToTop(LogsFragment logsFragment) {
|
||||
logsFragment.binding.appBar.setExpanded(true, true);
|
||||
if (layoutManager.findFirstVisibleItemPosition() > SCROLL_THRESHOLD) {
|
||||
binding.recyclerView.scrollToPosition(0);
|
||||
} else {
|
||||
binding.recyclerView.smoothScrollToPosition(0);
|
||||
}
|
||||
}
|
||||
|
||||
public void scrollToBottom(LogsFragment logsFragment) {
|
||||
logsFragment.binding.appBar.setExpanded(false, true);
|
||||
var end = Math.max(adaptor.getItemCount() - 1, 0);
|
||||
if (adaptor.getItemCount() - layoutManager.findLastVisibleItemPosition() > SCROLL_THRESHOLD) {
|
||||
binding.recyclerView.scrollToPosition(end);
|
||||
} else {
|
||||
binding.recyclerView.smoothScrollToPosition(end);
|
||||
}
|
||||
}
|
||||
|
||||
void attachListeners() {
|
||||
var parent = getParentFragment();
|
||||
if (parent instanceof LogsFragment) {
|
||||
var logsFragment = (LogsFragment) parent;
|
||||
logsFragment.binding.appBar.setLifted(!binding.recyclerView.getBorderViewDelegate().isShowingTopBorder());
|
||||
binding.recyclerView.getBorderViewDelegate().setBorderVisibilityChangedListener((top, oldTop, bottom, oldBottom) -> logsFragment.binding.appBar.setLifted(!top));
|
||||
logsFragment.setOptionsItemSelectListener(item -> {
|
||||
int itemId = item.getItemId();
|
||||
if (itemId == R.id.menu_scroll_top) {
|
||||
scrollToTop(logsFragment);
|
||||
} else if (itemId == R.id.menu_scroll_down) {
|
||||
scrollToBottom(logsFragment);
|
||||
} else if (itemId == R.id.menu_clear) {
|
||||
if (ConfigManager.clearLogs(verbose)) {
|
||||
logsFragment.showHint(R.string.logs_cleared, true);
|
||||
adaptor.fullRefresh();
|
||||
} else {
|
||||
logsFragment.showHint(R.string.logs_clear_failed_2, true);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
View.OnClickListener l = v -> scrollToTop(logsFragment);
|
||||
logsFragment.binding.clickView.setOnClickListener(l);
|
||||
logsFragment.binding.toolbar.setOnClickListener(l);
|
||||
}
|
||||
}
|
||||
|
||||
void detachListeners() {
|
||||
binding.recyclerView.getBorderViewDelegate().setBorderVisibilityChangedListener(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
attachListeners();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
adaptor.refresh();
|
||||
attachListeners();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
detachListeners();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
super.onStop();
|
||||
detachListeners();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewStateRestored(@Nullable Bundle savedInstanceState) {
|
||||
if (savedInstanceState != null) {
|
||||
var tabPosition = savedInstanceState.getInt(LogsFragment.class.getName() + "." + "tab", 0);
|
||||
if (tabPosition < binding.slidingTabs.getTabCount())
|
||||
binding.slidingTabs.selectTab(binding.slidingTabs.getTabAt(tabPosition));
|
||||
public static class UnwrapLogFragment extends LogFragment {
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
var root = super.onCreateView(inflater, container, savedInstanceState);
|
||||
binding.swipeRefreshLayout.removeView(binding.recyclerView);
|
||||
HorizontalScrollView horizontalScrollView = new HorizontalScrollView(getContext());
|
||||
horizontalScrollView.setFillViewport(true);
|
||||
horizontalScrollView.setHorizontalScrollBarEnabled(false);
|
||||
binding.swipeRefreshLayout.addView(horizontalScrollView);
|
||||
horizontalScrollView.addView(binding.recyclerView);
|
||||
binding.recyclerView.getLayoutParams().width = ViewGroup.LayoutParams.WRAP_CONTENT;
|
||||
return root;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected LogAdaptor createAdaptor() {
|
||||
return new LogAdaptor() {
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
|
||||
super.onBindViewHolder(holder, position);
|
||||
holder.item.measure(0, 0);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class LogPageAdapter extends FragmentStateAdapter {
|
||||
|
||||
public LogPageAdapter(@NonNull Fragment fragment) {
|
||||
super(fragment);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Fragment createFragment(int position) {
|
||||
var bundle = new Bundle();
|
||||
bundle.putBoolean("verbose", verbose(position));
|
||||
var f = getItemViewType(position) == 0 ? new LogFragment() : new UnwrapLogFragment();
|
||||
f.setArguments(bundle);
|
||||
return f;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return 2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
return verbose(position) ? R.string.nav_item_logs_lsp : R.string.nav_item_logs_module;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsItem(long itemId) {
|
||||
return itemId == R.string.nav_item_logs_lsp || itemId == R.string.nav_item_logs_module;
|
||||
}
|
||||
|
||||
public boolean verbose(int position) {
|
||||
return position != 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position) {
|
||||
return wordWrap.isChecked() ? 0 : 1;
|
||||
}
|
||||
|
||||
public void refresh() {
|
||||
runOnUiThread(this::notifyDataSetChanged);
|
||||
}
|
||||
super.onViewStateRestored(savedInstanceState);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,7 +46,6 @@ import android.widget.Filter;
|
|||
import android.widget.Filterable;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
|
@ -65,7 +64,6 @@ import com.bumptech.glide.request.transition.Transition;
|
|||
import com.google.android.material.behavior.HideBottomViewOnScrollBehavior;
|
||||
import com.google.android.material.checkbox.MaterialCheckBox;
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
import com.google.android.material.tabs.TabLayout;
|
||||
import com.google.android.material.tabs.TabLayoutMediator;
|
||||
|
||||
|
|
@ -74,10 +72,9 @@ import org.lsposed.manager.App;
|
|||
import org.lsposed.manager.ConfigManager;
|
||||
import org.lsposed.manager.R;
|
||||
import org.lsposed.manager.adapters.AppHelper;
|
||||
import org.lsposed.manager.databinding.DialogRecyclerviewBinding;
|
||||
import org.lsposed.manager.databinding.SwiperefreshRecyclerviewBinding;
|
||||
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.dialog.BlurBehindDialogBuilder;
|
||||
import org.lsposed.manager.ui.widget.EmptyStateRecyclerView;
|
||||
|
|
@ -94,7 +91,7 @@ import java.util.stream.IntStream;
|
|||
import rikka.core.util.ResourceUtils;
|
||||
import rikka.recyclerview.RecyclerViewKt;
|
||||
|
||||
public class ModulesFragment extends BaseFragment implements ModuleUtil.ModuleListener {
|
||||
public class ModulesFragment extends BaseFragment implements ModuleUtil.ModuleListener, RepoLoader.RepoListener {
|
||||
private static final PackageManager pm = App.getInstance().getPackageManager();
|
||||
private static final ModuleUtil moduleUtil = ModuleUtil.getInstance();
|
||||
private static final RepoLoader repoLoader = RepoLoader.getInstance();
|
||||
|
|
@ -103,14 +100,7 @@ public class ModulesFragment extends BaseFragment implements ModuleUtil.ModuleLi
|
|||
protected SearchView searchView;
|
||||
private SearchView.OnQueryTextListener searchListener;
|
||||
|
||||
private final ArrayList<ModuleAdapter> adapters = new ArrayList<>();
|
||||
|
||||
private final RecyclerView.AdapterDataObserver observer = new RecyclerView.AdapterDataObserver() {
|
||||
@Override
|
||||
public void onChanged() {
|
||||
updateProgress();
|
||||
}
|
||||
};
|
||||
final ArrayList<ModuleAdapter> adapters = new ArrayList<>();
|
||||
|
||||
private ModuleUtil.InstalledModule selectedModule;
|
||||
|
||||
|
|
@ -125,8 +115,8 @@ public class ModulesFragment extends BaseFragment implements ModuleUtil.ModuleLi
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean onQueryTextChange(String newText) {
|
||||
adapters.forEach(adapter -> adapter.getFilter().filter(newText));
|
||||
public boolean onQueryTextChange(String query) {
|
||||
adapters.forEach(adapter -> adapter.getFilter().filter(query));
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
|
@ -137,7 +127,6 @@ public class ModulesFragment extends BaseFragment implements ModuleUtil.ModuleLi
|
|||
adapter.setHasStableIds(true);
|
||||
adapter.setStateRestorationPolicy(PREVENT_WHEN_EMPTY);
|
||||
adapters.add(adapter);
|
||||
adapter.registerAdapterDataObserver(observer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -154,29 +143,16 @@ public class ModulesFragment extends BaseFragment implements ModuleUtil.ModuleLi
|
|||
}
|
||||
}
|
||||
|
||||
private void updateProgress() {
|
||||
if (binding != null) {
|
||||
var position = binding.viewPager.getCurrentItem();
|
||||
binding.progress.setVisibility(adapters.get(position).isLoaded ? View.GONE : View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
moduleUtil.addListener(this);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
binding = FragmentPagerBinding.inflate(inflater, container, false);
|
||||
setupToolbar(binding.toolbar, R.string.Modules, R.menu.menu_modules);
|
||||
binding.appBar.setLiftable(true);
|
||||
setupToolbar(binding.toolbar, binding.clickView, R.string.Modules, R.menu.menu_modules);
|
||||
binding.viewPager.setAdapter(new PagerAdapter(this));
|
||||
binding.viewPager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
|
||||
@Override
|
||||
public void onPageSelected(int position) {
|
||||
updateProgress();
|
||||
showFab();
|
||||
}
|
||||
});
|
||||
|
|
@ -204,31 +180,18 @@ public class ModulesFragment extends BaseFragment implements ModuleUtil.ModuleLi
|
|||
binding.tabLayout.setVisibility(View.GONE);
|
||||
}
|
||||
binding.fab.setOnClickListener(v -> {
|
||||
var pickAdaptor = new ModuleAdapter(adapters.get(binding.viewPager.getCurrentItem()).getUser(), true);
|
||||
var position = binding.viewPager.getCurrentItem();
|
||||
var user = adapters.get(position).getUser();
|
||||
var binding = DialogRecyclerviewBinding.inflate(getLayoutInflater());
|
||||
binding.list.setAdapter(pickAdaptor);
|
||||
binding.list.setLayoutManager(new LinearLayoutManager(requireActivity()));
|
||||
pickAdaptor.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
|
||||
@Override
|
||||
public void onChanged() {
|
||||
binding.progress.setVisibility(pickAdaptor.isLoaded() ? View.GONE : View.VISIBLE);
|
||||
}
|
||||
});
|
||||
pickAdaptor.refresh();
|
||||
var dialog = new BlurBehindDialogBuilder(requireActivity())
|
||||
.setTitle(getString(R.string.install_to_user, user.name))
|
||||
.setView(binding.getRoot())
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.show();
|
||||
pickAdaptor.setOnPickListener(picked -> {
|
||||
var module = (ModuleUtil.InstalledModule) picked.getTag();
|
||||
installModuleToUser(module, user);
|
||||
dialog.dismiss();
|
||||
});
|
||||
var bundle = new Bundle();
|
||||
var user = adapters.get(binding.viewPager.getCurrentItem()).getUser();
|
||||
bundle.putParcelable("userInfo", user);
|
||||
var f = new RecyclerViewDialogFragment();
|
||||
f.setArguments(bundle);
|
||||
f.show(getChildFragmentManager(), "install_to_user" + user.id);
|
||||
});
|
||||
|
||||
moduleUtil.addListener(this);
|
||||
repoLoader.addListener(this);
|
||||
updateModuleSummary();
|
||||
|
||||
return binding.getRoot();
|
||||
}
|
||||
|
||||
|
|
@ -236,6 +199,16 @@ public class ModulesFragment extends BaseFragment implements ModuleUtil.ModuleLi
|
|||
public void onPrepareOptionsMenu(Menu menu) {
|
||||
searchView = (SearchView) menu.findItem(R.id.menu_search).getActionView();
|
||||
searchView.setOnQueryTextListener(searchListener);
|
||||
searchView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
|
||||
@Override
|
||||
public void onViewAttachedToWindow(View arg0) {
|
||||
binding.appBar.setExpanded(false, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewDetachedFromWindow(View v) {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -245,31 +218,30 @@ public class ModulesFragment extends BaseFragment implements ModuleUtil.ModuleLi
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
moduleUtil.removeListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSingleInstalledModuleReloaded(ModuleUtil.InstalledModule module) {
|
||||
public void onSingleModuleReloaded(ModuleUtil.InstalledModule module) {
|
||||
adapters.forEach(ModuleAdapter::refresh);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onModulesReloaded() {
|
||||
adapters.forEach(ModuleAdapter::refresh);
|
||||
updateModuleSummary();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
|
||||
int itemId = item.getItemId();
|
||||
if (itemId == R.id.menu_refresh) {
|
||||
adapters.forEach(adapter -> adapter.refresh(true));
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
public void onRepoLoaded() {
|
||||
adapters.forEach(ModuleAdapter::refresh);
|
||||
}
|
||||
|
||||
private void installModuleToUser(ModuleUtil.InstalledModule module, UserInfo user) {
|
||||
private void updateModuleSummary() {
|
||||
var moduleCount = moduleUtil.getEnabledModulesCount();
|
||||
runOnUiThread(() -> {
|
||||
binding.toolbar.setSubtitle(moduleCount == -1 ? getString(R.string.loading) : getResources().getQuantityString(R.plurals.modules_enabled_count, moduleCount, moduleCount));
|
||||
binding.toolbarLayout.setSubtitle(binding.toolbar.getSubtitle());
|
||||
});
|
||||
}
|
||||
|
||||
void installModuleToUser(ModuleUtil.InstalledModule module, UserInfo user) {
|
||||
new BlurBehindDialogBuilder(requireActivity())
|
||||
.setTitle(getString(R.string.install_to_user, user.name))
|
||||
.setMessage(getString(R.string.install_to_user_message, module.getAppName(), user.name))
|
||||
|
|
@ -279,11 +251,7 @@ public class ModulesFragment extends BaseFragment implements ModuleUtil.ModuleLi
|
|||
String text = success ?
|
||||
getString(R.string.module_installed, module.getAppName(), user.name) :
|
||||
getString(R.string.module_install_failed);
|
||||
if (binding != null && isResumed()) {
|
||||
Snackbar.make(binding.snackbar, text, Snackbar.LENGTH_LONG).show();
|
||||
} else {
|
||||
Toast.makeText(App.getInstance(), text, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
showHint(text, false);
|
||||
if (success)
|
||||
moduleUtil.reloadSingleModule(module.packageName, user.id);
|
||||
}))
|
||||
|
|
@ -305,8 +273,6 @@ public class ModulesFragment extends BaseFragment implements ModuleUtil.ModuleLi
|
|||
Intent intent = AppHelper.getSettingsIntent(packageName, selectedModule.userId);
|
||||
if (intent != null) {
|
||||
ConfigManager.startActivityAsUserWithFeature(intent, selectedModule.userId);
|
||||
} else {
|
||||
Snackbar.make(binding.snackbar, R.string.module_no_ui, Snackbar.LENGTH_LONG).show();
|
||||
}
|
||||
return true;
|
||||
} else if (itemId == R.id.menu_other_app) {
|
||||
|
|
@ -326,11 +292,7 @@ public class ModulesFragment extends BaseFragment implements ModuleUtil.ModuleLi
|
|||
runAsync(() -> {
|
||||
boolean success = ConfigManager.uninstallPackage(selectedModule.packageName, selectedModule.userId);
|
||||
String text = success ? getString(R.string.module_uninstalled, selectedModule.getAppName()) : getString(R.string.module_uninstall_failed);
|
||||
if (binding != null && isResumed()) {
|
||||
Snackbar.make(binding.snackbar, text, Snackbar.LENGTH_LONG).show();
|
||||
} else {
|
||||
Toast.makeText(App.getInstance(), text, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
showHint(text, false);
|
||||
if (success)
|
||||
moduleUtil.reloadSingleModule(selectedModule.packageName, selectedModule.userId);
|
||||
}))
|
||||
|
|
@ -341,7 +303,7 @@ public class ModulesFragment extends BaseFragment implements ModuleUtil.ModuleLi
|
|||
getNavController().navigate(ModulesFragmentDirections.actionModulesFragmentToRepoItemFragment(selectedModule.packageName, selectedModule.getAppName()));
|
||||
return true;
|
||||
} else if (itemId == R.id.menu_compile_speed) {
|
||||
CompileDialogFragment.speed(getChildFragmentManager(), selectedModule.pkg.applicationInfo, binding.snackbar);
|
||||
CompileDialogFragment.speed(getChildFragmentManager(), selectedModule.pkg.applicationInfo);
|
||||
}
|
||||
return super.onContextItemSelected(item);
|
||||
}
|
||||
|
|
@ -349,12 +311,33 @@ public class ModulesFragment extends BaseFragment implements ModuleUtil.ModuleLi
|
|||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
|
||||
moduleUtil.removeListener(this);
|
||||
repoLoader.removeListener(this);
|
||||
binding = null;
|
||||
}
|
||||
|
||||
public static class ModuleListFragment extends Fragment {
|
||||
public SwiperefreshRecyclerviewBinding binding;
|
||||
private ModuleAdapter adapter;
|
||||
private final RecyclerView.AdapterDataObserver observer = new RecyclerView.AdapterDataObserver() {
|
||||
@Override
|
||||
public void onChanged() {
|
||||
binding.swipeRefreshLayout.setRefreshing(!adapter.isLoaded());
|
||||
}
|
||||
};
|
||||
|
||||
private final View.OnAttachStateChangeListener searchViewLocker = new View.OnAttachStateChangeListener() {
|
||||
@Override
|
||||
public void onViewAttachedToWindow(View v) {
|
||||
binding.recyclerView.setNestedScrollingEnabled(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewDetachedFromWindow(View v) {
|
||||
binding.recyclerView.setNestedScrollingEnabled(true);
|
||||
}
|
||||
};
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
|
|
@ -364,13 +347,75 @@ public class ModulesFragment extends BaseFragment implements ModuleUtil.ModuleLi
|
|||
return null;
|
||||
}
|
||||
int position = arguments.getInt("position");
|
||||
ItemRepoRecyclerviewBinding binding = ItemRepoRecyclerviewBinding.inflate(getLayoutInflater(), container, false);
|
||||
binding.recyclerView.setAdapter(fragment.adapters.get(position));
|
||||
RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(requireActivity());
|
||||
binding.recyclerView.setLayoutManager(layoutManager);
|
||||
binding = SwiperefreshRecyclerviewBinding.inflate(getLayoutInflater(), container, false);
|
||||
adapter = fragment.adapters.get(position);
|
||||
binding.recyclerView.setAdapter(adapter);
|
||||
binding.recyclerView.setLayoutManager(new LinearLayoutManager(requireActivity()));
|
||||
binding.swipeRefreshLayout.setOnRefreshListener(adapter::fullRefresh);
|
||||
binding.swipeRefreshLayout.setProgressViewEndTarget(true, binding.swipeRefreshLayout.getProgressViewEndOffset());
|
||||
RecyclerViewKt.fixEdgeEffect(binding.recyclerView, false, true);
|
||||
adapter.registerAdapterDataObserver(observer);
|
||||
return binding.getRoot();
|
||||
}
|
||||
|
||||
void attachListeners() {
|
||||
var parent = getParentFragment();
|
||||
if (parent instanceof ModulesFragment) {
|
||||
var moduleFragment = (ModulesFragment) parent;
|
||||
binding.recyclerView.getBorderViewDelegate().setBorderVisibilityChangedListener((top, oldTop, bottom, oldBottom) -> moduleFragment.binding.appBar.setLifted(!top));
|
||||
moduleFragment.binding.appBar.setLifted(!binding.recyclerView.getBorderViewDelegate().isShowingTopBorder());
|
||||
moduleFragment.searchView.addOnAttachStateChangeListener(searchViewLocker);
|
||||
binding.recyclerView.setNestedScrollingEnabled(moduleFragment.searchView.isIconified());
|
||||
View.OnClickListener l = v -> {
|
||||
if (moduleFragment.searchView.isIconified()) {
|
||||
binding.recyclerView.smoothScrollToPosition(0);
|
||||
moduleFragment.binding.appBar.setExpanded(true, true);
|
||||
}
|
||||
};
|
||||
moduleFragment.binding.clickView.setOnClickListener(l);
|
||||
moduleFragment.binding.toolbar.setOnClickListener(l);
|
||||
}
|
||||
}
|
||||
|
||||
void detachListeners() {
|
||||
binding.recyclerView.getBorderViewDelegate().setBorderVisibilityChangedListener(null);
|
||||
var parent = getParentFragment();
|
||||
if (parent instanceof ModulesFragment) {
|
||||
var moduleFragment = (ModulesFragment) parent;
|
||||
moduleFragment.searchView.removeOnAttachStateChangeListener(searchViewLocker);
|
||||
binding.recyclerView.setNestedScrollingEnabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
attachListeners();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
attachListeners();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
adapter.unregisterAdapterDataObserver(observer);
|
||||
super.onDestroyView();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
detachListeners();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
super.onStop();
|
||||
detachListeners();
|
||||
}
|
||||
}
|
||||
|
||||
private class PagerAdapter extends FragmentStateAdapter {
|
||||
|
|
@ -400,7 +445,11 @@ public class ModulesFragment extends BaseFragment implements ModuleUtil.ModuleLi
|
|||
}
|
||||
}
|
||||
|
||||
private class ModuleAdapter extends EmptyStateRecyclerView.EmptyStateAdapter<ModuleAdapter.ViewHolder> implements Filterable {
|
||||
ModuleAdapter createPickModuleAdapter(UserInfo userInfo) {
|
||||
return new ModuleAdapter(userInfo, true);
|
||||
}
|
||||
|
||||
class ModuleAdapter extends EmptyStateRecyclerView.EmptyStateAdapter<ModuleAdapter.ViewHolder> implements Filterable {
|
||||
private List<ModuleUtil.InstalledModule> searchList = new ArrayList<>();
|
||||
private List<ModuleUtil.InstalledModule> showList = new ArrayList<>();
|
||||
private final UserInfo user;
|
||||
|
|
@ -487,22 +536,20 @@ public class ModulesFragment extends BaseFragment implements ModuleUtil.ModuleLi
|
|||
}
|
||||
sb.setSpan(foregroundColorSpan, sb.length() - warningText.length(), sb.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
|
||||
}
|
||||
if (repoLoader.isRepoLoaded()) {
|
||||
var ver = repoLoader.getModuleLatestVersion(item.packageName);
|
||||
if (ver != null && ver.upgradable(item.versionCode, item.versionName)) {
|
||||
if (warningText != null) sb.append("\n");
|
||||
String recommended = getString(R.string.update_available, ver.versionName);
|
||||
sb.append(recommended);
|
||||
final ForegroundColorSpan foregroundColorSpan = new ForegroundColorSpan(ResourceUtils.resolveColor(requireActivity().getTheme(), androidx.appcompat.R.attr.colorAccent));
|
||||
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() - recommended.length(), sb.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
|
||||
} else {
|
||||
final StyleSpan styleSpan = new StyleSpan(Typeface.BOLD);
|
||||
sb.setSpan(styleSpan, sb.length() - recommended.length(), sb.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
|
||||
}
|
||||
sb.setSpan(foregroundColorSpan, sb.length() - recommended.length(), sb.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
|
||||
var ver = repoLoader.getModuleLatestVersion(item.packageName);
|
||||
if (ver != null && ver.upgradable(item.versionCode, item.versionName)) {
|
||||
if (warningText != null) sb.append("\n");
|
||||
String recommended = getString(R.string.update_available, ver.versionName);
|
||||
sb.append(recommended);
|
||||
final ForegroundColorSpan foregroundColorSpan = new ForegroundColorSpan(ResourceUtils.resolveColor(requireActivity().getTheme(), androidx.appcompat.R.attr.colorAccent));
|
||||
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() - recommended.length(), sb.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
|
||||
} else {
|
||||
final StyleSpan styleSpan = new StyleSpan(Typeface.BOLD);
|
||||
sb.setSpan(styleSpan, sb.length() - recommended.length(), sb.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
|
||||
}
|
||||
sb.setSpan(foregroundColorSpan, sb.length() - recommended.length(), sb.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
|
||||
}
|
||||
if (sb.length() == 0) {
|
||||
holder.hint.setVisibility(View.GONE);
|
||||
|
|
@ -515,7 +562,6 @@ public class ModulesFragment extends BaseFragment implements ModuleUtil.ModuleLi
|
|||
holder.root.setAlpha(moduleUtil.isModuleEnabled(item.packageName) ? 1.0f : .5f);
|
||||
holder.itemView.setOnClickListener(v -> {
|
||||
searchView.clearFocus();
|
||||
searchView.onActionViewCollapsed();
|
||||
getNavController().navigate(ModulesFragmentDirections.actionModulesFragmentToAppListFragment(item.packageName, item.userId));
|
||||
});
|
||||
holder.itemView.setOnLongClickListener(v -> {
|
||||
|
|
@ -585,12 +631,15 @@ public class ModulesFragment extends BaseFragment implements ModuleUtil.ModuleLi
|
|||
}
|
||||
|
||||
public void refresh() {
|
||||
refresh(false);
|
||||
runAsync(reloadModules);
|
||||
}
|
||||
|
||||
public void refresh(boolean force) {
|
||||
if (force) runAsync(moduleUtil::reloadInstalledModules);
|
||||
runAsync(reloadModules);
|
||||
public void fullRefresh() {
|
||||
runAsync(() -> {
|
||||
setLoaded(false);
|
||||
moduleUtil.reloadInstalledModules();
|
||||
refresh();
|
||||
});
|
||||
}
|
||||
|
||||
private final Runnable reloadModules = () -> {
|
||||
|
|
@ -634,7 +683,7 @@ public class ModulesFragment extends BaseFragment implements ModuleUtil.ModuleLi
|
|||
});
|
||||
String queryStr = searchView != null ? searchView.getQuery().toString() : "";
|
||||
searchList = tmpList;
|
||||
runOnUiThread(() -> getFilter().filter(queryStr, count -> setLoaded(true)));
|
||||
runOnUiThread(() -> getFilter().filter(queryStr));
|
||||
};
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
|
|
@ -647,7 +696,7 @@ public class ModulesFragment extends BaseFragment implements ModuleUtil.ModuleLi
|
|||
|
||||
@Override
|
||||
public boolean isLoaded() {
|
||||
return isLoaded;
|
||||
return isLoaded && moduleUtil.isModulesLoaded();
|
||||
}
|
||||
|
||||
class ViewHolder extends RecyclerView.ViewHolder {
|
||||
|
|
@ -681,16 +730,12 @@ public class ModulesFragment extends BaseFragment implements ModuleUtil.ModuleLi
|
|||
protected FilterResults performFiltering(CharSequence constraint) {
|
||||
FilterResults filterResults = new FilterResults();
|
||||
List<ModuleUtil.InstalledModule> filtered = new ArrayList<>();
|
||||
if (constraint.toString().isEmpty()) {
|
||||
filtered.addAll(searchList);
|
||||
} else {
|
||||
String filter = constraint.toString().toLowerCase();
|
||||
for (ModuleUtil.InstalledModule info : searchList) {
|
||||
if (lowercaseContains(info.getAppName(), filter) ||
|
||||
lowercaseContains(info.packageName, filter) ||
|
||||
lowercaseContains(info.getDescription(), filter)) {
|
||||
filtered.add(info);
|
||||
}
|
||||
String filter = constraint.toString().toLowerCase();
|
||||
for (ModuleUtil.InstalledModule info : searchList) {
|
||||
if (lowercaseContains(info.getAppName(), filter) ||
|
||||
lowercaseContains(info.packageName, filter) ||
|
||||
lowercaseContains(info.getDescription(), filter)) {
|
||||
filtered.add(info);
|
||||
}
|
||||
}
|
||||
filterResults.values = filtered;
|
||||
|
|
@ -702,6 +747,7 @@ public class ModulesFragment extends BaseFragment implements ModuleUtil.ModuleLi
|
|||
protected void publishResults(CharSequence constraint, FilterResults results) {
|
||||
//noinspection unchecked
|
||||
showList = (List<ModuleUtil.InstalledModule>) results.values;
|
||||
setLoaded(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,85 @@
|
|||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Copyright (C) 2021 LSPosed Contributors
|
||||
*/
|
||||
|
||||
package org.lsposed.manager.ui.fragment;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AppCompatDialogFragment;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import org.lsposed.lspd.models.UserInfo;
|
||||
import org.lsposed.manager.R;
|
||||
import org.lsposed.manager.databinding.DialogTitleBinding;
|
||||
import org.lsposed.manager.databinding.SwiperefreshRecyclerviewBinding;
|
||||
import org.lsposed.manager.ui.dialog.BlurBehindDialogBuilder;
|
||||
import org.lsposed.manager.util.ModuleUtil;
|
||||
|
||||
public class RecyclerViewDialogFragment extends AppCompatDialogFragment {
|
||||
@Override
|
||||
@NonNull
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
var parent = getParentFragment();
|
||||
var arguments = getArguments();
|
||||
if (!(parent instanceof ModulesFragment) || arguments == null) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
var modulesFragment = (ModulesFragment) parent;
|
||||
var user = (UserInfo) arguments.getParcelable("userInfo");
|
||||
|
||||
var pickAdaptor = modulesFragment.createPickModuleAdapter(user);
|
||||
var binding = SwiperefreshRecyclerviewBinding.inflate(LayoutInflater.from(requireActivity()), null, false);
|
||||
|
||||
binding.recyclerView.setAdapter(pickAdaptor);
|
||||
binding.recyclerView.setLayoutManager(new LinearLayoutManager(requireActivity()));
|
||||
pickAdaptor.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
|
||||
@Override
|
||||
public void onChanged() {
|
||||
binding.swipeRefreshLayout.setRefreshing(!pickAdaptor.isLoaded());
|
||||
}
|
||||
});
|
||||
binding.swipeRefreshLayout.setOnRefreshListener(pickAdaptor::fullRefresh);
|
||||
pickAdaptor.refresh();
|
||||
var title = DialogTitleBinding.inflate(getLayoutInflater()).getRoot();
|
||||
title.setText(getString(R.string.install_to_user, user.name));
|
||||
var dialog = new BlurBehindDialogBuilder(requireActivity())
|
||||
.setCustomTitle(title)
|
||||
.setView(binding.getRoot())
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.create();
|
||||
title.setOnClickListener(s -> binding.recyclerView.smoothScrollToPosition(0));
|
||||
pickAdaptor.setOnPickListener(picked -> {
|
||||
var module = (ModuleUtil.InstalledModule) picked.getTag();
|
||||
modulesFragment.installModuleToUser(module, user);
|
||||
dialog.dismiss();
|
||||
});
|
||||
return dialog;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
}
|
||||
}
|
||||
|
|
@ -44,26 +44,23 @@ 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.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
|
||||
import org.lsposed.manager.App;
|
||||
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.widget.EmptyStateRecyclerView;
|
||||
import org.lsposed.manager.util.ModuleUtil;
|
||||
import org.lsposed.manager.util.SimpleStatefulAdaptor;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
|
|
@ -71,7 +68,7 @@ import rikka.core.util.LabelComparator;
|
|||
import rikka.core.util.ResourceUtils;
|
||||
import rikka.recyclerview.RecyclerViewKt;
|
||||
|
||||
public class RepoFragment extends BaseFragment implements RepoLoader.Listener {
|
||||
public class RepoFragment extends BaseFragment implements RepoLoader.RepoListener, ModuleUtil.ModuleListener {
|
||||
protected FragmentRepoBinding binding;
|
||||
protected SearchView searchView;
|
||||
private SearchView.OnQueryTextListener mSearchListener;
|
||||
|
|
@ -79,7 +76,14 @@ public class RepoFragment extends BaseFragment implements RepoLoader.Listener {
|
|||
private boolean preLoadWebview = true;
|
||||
|
||||
private final RepoLoader repoLoader = RepoLoader.getInstance();
|
||||
private final ModuleUtil moduleUtil = ModuleUtil.getInstance();
|
||||
private RepoAdapter adapter;
|
||||
private final RecyclerView.AdapterDataObserver observer = new RecyclerView.AdapterDataObserver() {
|
||||
@Override
|
||||
public void onChanged() {
|
||||
binding.swipeRefreshLayout.setRefreshing(!adapter.isLoaded());
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
|
|
@ -103,33 +107,78 @@ public class RepoFragment extends BaseFragment implements RepoLoader.Listener {
|
|||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
binding = FragmentRepoBinding.inflate(getLayoutInflater(), container, false);
|
||||
setupToolbar(binding.toolbar, R.string.module_repo, R.menu.menu_repo);
|
||||
binding.appBar.setLiftable(true);
|
||||
binding.recyclerView.getBorderViewDelegate().setBorderVisibilityChangedListener((top, oldTop, bottom, oldBottom) -> binding.appBar.setLifted(!top));
|
||||
setupToolbar(binding.toolbar, binding.clickView, R.string.module_repo, R.menu.menu_repo);
|
||||
adapter = new RepoAdapter();
|
||||
adapter.setHasStableIds(true);
|
||||
adapter.registerAdapterDataObserver(observer);
|
||||
binding.recyclerView.setAdapter(adapter);
|
||||
binding.recyclerView.setHasFixedSize(true);
|
||||
binding.recyclerView.setLayoutManager(new LinearLayoutManager(requireActivity()));
|
||||
RecyclerViewKt.fixEdgeEffect(binding.recyclerView, false, true);
|
||||
binding.progress.setVisibilityAfterHide(View.GONE);
|
||||
binding.swipeRefreshLayout.setOnRefreshListener(adapter::fullRefresh);
|
||||
binding.swipeRefreshLayout.setProgressViewEndTarget(true, binding.swipeRefreshLayout.getProgressViewEndOffset());
|
||||
View.OnClickListener l = v -> {
|
||||
if (searchView.isIconified()) {
|
||||
binding.recyclerView.smoothScrollToPosition(0);
|
||||
binding.appBar.setExpanded(true, true);
|
||||
}
|
||||
};
|
||||
binding.toolbar.setOnClickListener(l);
|
||||
binding.clickView.setOnClickListener(l);
|
||||
repoLoader.addListener(this);
|
||||
|
||||
/*
|
||||
CollapsingToolbarLayout consumes window insets, causing child views not
|
||||
receiving window insets.
|
||||
See https://github.com/material-components/material-components-android/issues/1310
|
||||
|
||||
Insets can be handled by RikkaX Insets, so we can manually set
|
||||
OnApplyWindowInsetsListener to null.
|
||||
*/
|
||||
|
||||
binding.collapsingToolbarLayout.setOnApplyWindowInsetsListener(null);
|
||||
moduleUtil.addListener(this);
|
||||
updateRepoSummary();
|
||||
return binding.getRoot();
|
||||
}
|
||||
|
||||
private void updateRepoSummary() {
|
||||
final int[] count = new int[]{0};
|
||||
HashSet<String> processedModules = new HashSet<>();
|
||||
var modules = moduleUtil.getModules();
|
||||
if (modules != null && repoLoader.isRepoLoaded()) {
|
||||
modules.forEach((k, v) -> {
|
||||
if (!processedModules.contains(k.first)) {
|
||||
var ver = repoLoader.getModuleLatestVersion(k.first);
|
||||
if (ver != null && ver.upgradable(v.versionCode, v.versionName)) {
|
||||
++count[0];
|
||||
}
|
||||
processedModules.add(k.first);
|
||||
}
|
||||
}
|
||||
);
|
||||
} else {
|
||||
count[0] = -1;
|
||||
}
|
||||
runOnUiThread(() -> {
|
||||
if (count[0] > 0) {
|
||||
binding.toolbar.setSubtitle(getResources().getQuantityString(R.plurals.module_repo_upgradable, count[0], count[0]));
|
||||
} else if (count[0] == 0) {
|
||||
binding.toolbar.setSubtitle(getResources().getString(R.string.module_repo_up_to_date));
|
||||
} else {
|
||||
binding.toolbar.setSubtitle(getResources().getString(R.string.loading));
|
||||
}
|
||||
binding.toolbarLayout.setSubtitle(binding.toolbar.getSubtitle());
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPrepareOptionsMenu(Menu menu) {
|
||||
searchView = (SearchView) menu.findItem(R.id.menu_search).getActionView();
|
||||
searchView.setOnQueryTextListener(mSearchListener);
|
||||
searchView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
|
||||
@Override
|
||||
public void onViewAttachedToWindow(View arg0) {
|
||||
binding.appBar.setExpanded(false, true);
|
||||
binding.recyclerView.setNestedScrollingEnabled(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewDetachedFromWindow(View v) {
|
||||
binding.recyclerView.setNestedScrollingEnabled(true);
|
||||
}
|
||||
});
|
||||
int sort = App.getPreferences().getInt("repo_sort", 0);
|
||||
if (sort == 0) {
|
||||
menu.findItem(R.id.item_sort_by_name).setChecked(true);
|
||||
|
|
@ -144,13 +193,15 @@ public class RepoFragment extends BaseFragment implements RepoLoader.Listener {
|
|||
|
||||
mHandler.removeCallbacksAndMessages(null);
|
||||
repoLoader.removeListener(this);
|
||||
moduleUtil.removeListener(this);
|
||||
adapter.unregisterAdapterDataObserver(observer);
|
||||
binding = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
adapter.initData();
|
||||
adapter.refresh();
|
||||
if (preLoadWebview) {
|
||||
mHandler.postDelayed(() -> new WebView(requireContext()), 500);
|
||||
preLoadWebview = false;
|
||||
|
|
@ -158,41 +209,41 @@ public class RepoFragment extends BaseFragment implements RepoLoader.Listener {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void repoLoaded() {
|
||||
runOnUiThread(() -> {
|
||||
binding.progress.hide();
|
||||
adapter.setData(repoLoader.getOnlineModules());
|
||||
});
|
||||
public void onRepoLoaded() {
|
||||
adapter.refresh();
|
||||
updateRepoSummary();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onThrowable(Throwable t) {
|
||||
if (getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.RESUMED)) {
|
||||
Snackbar.make(binding.snackbar, getString(R.string.repo_load_failed, t.getLocalizedMessage()), Snackbar.LENGTH_SHORT).show();
|
||||
}
|
||||
showHint(getString(R.string.repo_load_failed, t.getLocalizedMessage()), true);
|
||||
updateRepoSummary();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onModulesReloaded() {
|
||||
updateRepoSummary();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
|
||||
int itemId = item.getItemId();
|
||||
if (itemId == R.id.menu_refresh) {
|
||||
binding.progress.show();
|
||||
repoLoader.loadRemoteData();
|
||||
} else if (itemId == R.id.item_sort_by_name) {
|
||||
if (itemId == R.id.item_sort_by_name) {
|
||||
item.setChecked(true);
|
||||
App.getPreferences().edit().putInt("repo_sort", 0).apply();
|
||||
adapter.setData(repoLoader.getOnlineModules());
|
||||
adapter.refresh();
|
||||
} else if (itemId == R.id.item_sort_by_update_time) {
|
||||
item.setChecked(true);
|
||||
App.getPreferences().edit().putInt("repo_sort", 1).apply();
|
||||
adapter.setData(repoLoader.getOnlineModules());
|
||||
adapter.refresh();
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
private class RepoAdapter extends SimpleStatefulAdaptor<RepoAdapter.ViewHolder> implements Filterable {
|
||||
private class RepoAdapter extends EmptyStateRecyclerView.EmptyStateAdapter<RepoAdapter.ViewHolder> implements Filterable {
|
||||
private List<OnlineModule> fullList, showList;
|
||||
private final LabelComparator labelComparator = new LabelComparator();
|
||||
private boolean isLoaded = false;
|
||||
|
||||
RepoAdapter() {
|
||||
fullList = showList = Collections.emptyList();
|
||||
|
|
@ -219,7 +270,7 @@ public class RepoFragment extends BaseFragment implements RepoLoader.Listener {
|
|||
holder.appDescription.setVisibility(View.VISIBLE);
|
||||
holder.appDescription.setText(sb);
|
||||
sb = new SpannableStringBuilder();
|
||||
ModuleUtil.InstalledModule installedModule = ModuleUtil.getInstance().getModule(module.getName());
|
||||
ModuleUtil.InstalledModule installedModule = moduleUtil.getModule(module.getName());
|
||||
if (installedModule != null) {
|
||||
var ver = repoLoader.getModuleLatestVersion(installedModule.packageName);
|
||||
if (ver != null && ver.upgradable(installedModule.versionCode, installedModule.versionName)) {
|
||||
|
|
@ -245,9 +296,9 @@ public class RepoFragment extends BaseFragment implements RepoLoader.Listener {
|
|||
|
||||
holder.itemView.setOnClickListener(v -> {
|
||||
searchView.clearFocus();
|
||||
searchView.onActionViewCollapsed();
|
||||
getNavController().navigate(RepoFragmentDirections.actionRepoFragmentToRepoItemFragment(module.getName(), module.getDescription()));
|
||||
});
|
||||
holder.itemView.setTooltipText(module.getDescription());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -255,27 +306,37 @@ public class RepoFragment extends BaseFragment implements RepoLoader.Listener {
|
|||
return showList.size();
|
||||
}
|
||||
|
||||
public void setData(Collection<OnlineModule> modules) {
|
||||
fullList = new ArrayList<>(modules);
|
||||
fullList = fullList.stream().filter((onlineModule -> !onlineModule.isHide() && !onlineModule.getReleases().isEmpty())).collect(Collectors.toList());
|
||||
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() : "";
|
||||
requireActivity().runOnUiThread(() -> getFilter().filter(queryStr));
|
||||
private void setLoaded(boolean isLoaded) {
|
||||
this.isLoaded = isLoaded;
|
||||
runOnUiThread(this::notifyDataSetChanged);
|
||||
}
|
||||
|
||||
public void initData() {
|
||||
Collection<OnlineModule> modules = repoLoader.getOnlineModules();
|
||||
if (!repoLoader.isRepoLoaded()) {
|
||||
binding.progress.show();
|
||||
public void setData(Collection<OnlineModule> modules) {
|
||||
if (modules == null) return;
|
||||
setLoaded(false);
|
||||
int sort = App.getPreferences().getInt("repo_sort", 0);
|
||||
fullList = modules.parallelStream().filter((onlineModule -> !onlineModule.isHide() && !onlineModule.getReleases().isEmpty()))
|
||||
.sorted((a, b) -> {
|
||||
if (sort == 0) {
|
||||
return labelComparator.compare(a.getDescription(), b.getDescription());
|
||||
} else {
|
||||
return Instant.parse(b.getReleases().get(0).getUpdatedAt()).compareTo(Instant.parse(a.getReleases().get(0).getUpdatedAt()));
|
||||
}
|
||||
}).collect(Collectors.toList());
|
||||
String queryStr = searchView != null ? searchView.getQuery().toString() : "";
|
||||
runOnUiThread(() -> getFilter().filter(queryStr));
|
||||
}
|
||||
|
||||
public void fullRefresh() {
|
||||
runAsync(() -> {
|
||||
setLoaded(false);
|
||||
repoLoader.loadRemoteData();
|
||||
} else {
|
||||
adapter.setData(modules);
|
||||
}
|
||||
refresh();
|
||||
});
|
||||
}
|
||||
|
||||
public void refresh() {
|
||||
runAsync(() -> adapter.setData(repoLoader.getOnlineModules()));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -288,6 +349,11 @@ public class RepoFragment extends BaseFragment implements RepoLoader.Listener {
|
|||
return new RepoAdapter.ModuleFilter();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLoaded() {
|
||||
return isLoaded && repoLoader.isRepoLoaded();
|
||||
}
|
||||
|
||||
class ViewHolder extends RecyclerView.ViewHolder {
|
||||
ConstraintLayout root;
|
||||
TextView appName;
|
||||
|
|
@ -311,26 +377,26 @@ public class RepoFragment extends BaseFragment implements RepoLoader.Listener {
|
|||
|
||||
@Override
|
||||
protected FilterResults performFiltering(CharSequence constraint) {
|
||||
if (constraint.toString().isEmpty()) {
|
||||
showList = fullList;
|
||||
} else {
|
||||
ArrayList<OnlineModule> filtered = new ArrayList<>();
|
||||
String filter = constraint.toString().toLowerCase();
|
||||
for (OnlineModule info : fullList) {
|
||||
if (lowercaseContains(info.getDescription(), filter) ||
|
||||
lowercaseContains(info.getName(), filter) ||
|
||||
lowercaseContains(info.getSummary(), filter)) {
|
||||
filtered.add(info);
|
||||
}
|
||||
FilterResults filterResults = new FilterResults();
|
||||
ArrayList<OnlineModule> filtered = new ArrayList<>();
|
||||
String filter = constraint.toString().toLowerCase();
|
||||
for (OnlineModule info : fullList) {
|
||||
if (lowercaseContains(info.getDescription(), filter) ||
|
||||
lowercaseContains(info.getName(), filter) ||
|
||||
lowercaseContains(info.getSummary(), filter)) {
|
||||
filtered.add(info);
|
||||
}
|
||||
showList = filtered;
|
||||
}
|
||||
return null;
|
||||
filterResults.values = filtered;
|
||||
filterResults.count = filtered.size();
|
||||
return filterResults;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void publishResults(CharSequence constraint, FilterResults results) {
|
||||
notifyDataSetChanged();
|
||||
//noinspection unchecked
|
||||
showList = (List<OnlineModule>) results.values;
|
||||
setLoaded(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
|
||||
package org.lsposed.manager.ui.fragment;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Color;
|
||||
import android.os.Bundle;
|
||||
|
|
@ -36,16 +37,20 @@ import android.webkit.WebResourceResponse;
|
|||
import android.webkit.WebSettings;
|
||||
import android.webkit.WebView;
|
||||
import android.webkit.WebViewClient;
|
||||
import android.widget.ScrollView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.viewpager2.adapter.FragmentStateAdapter;
|
||||
|
||||
import com.google.android.material.button.MaterialButton;
|
||||
import com.google.android.material.progressindicator.CircularProgressIndicator;
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
import com.google.android.material.tabs.TabLayout;
|
||||
import com.google.android.material.tabs.TabLayoutMediator;
|
||||
|
||||
|
|
@ -83,13 +88,13 @@ import okhttp3.Request;
|
|||
import okhttp3.Response;
|
||||
import rikka.core.util.ResourceUtils;
|
||||
import rikka.recyclerview.RecyclerViewKt;
|
||||
import rikka.widget.borderview.BorderNestedScrollView;
|
||||
import rikka.widget.borderview.BorderRecyclerView;
|
||||
import rikka.widget.borderview.BorderView;
|
||||
|
||||
public class RepoItemFragment extends BaseFragment implements RepoLoader.Listener {
|
||||
public class RepoItemFragment extends BaseFragment implements RepoLoader.RepoListener {
|
||||
FragmentPagerBinding binding;
|
||||
private OnlineModule module;
|
||||
OnlineModule module;
|
||||
private ReleaseAdapter releaseAdapter;
|
||||
private InformationAdapter informationAdapter;
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
|
|
@ -98,9 +103,11 @@ public class RepoItemFragment extends BaseFragment implements RepoLoader.Listene
|
|||
if (module == null) return binding.getRoot();
|
||||
String modulePackageName = module.getName();
|
||||
String moduleName = module.getDescription();
|
||||
setupToolbar(binding.toolbar, moduleName, R.menu.menu_repo_item);
|
||||
binding.appBar.setLiftable(true);
|
||||
setupToolbar(binding.toolbar, binding.clickView, moduleName, R.menu.menu_repo_item);
|
||||
binding.clickView.setTooltipText(moduleName);
|
||||
binding.toolbar.setSubtitle(modulePackageName);
|
||||
binding.viewPager.setAdapter(new PagerAdapter());
|
||||
binding.viewPager.setAdapter(new PagerAdapter(this));
|
||||
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();
|
||||
|
||||
|
|
@ -112,15 +119,13 @@ public class RepoItemFragment extends BaseFragment implements RepoLoader.Listene
|
|||
binding.tabLayout.setTabGravity(TabLayout.GRAVITY_FILL);
|
||||
}
|
||||
});
|
||||
|
||||
binding.toolbar.setOnClickListener(v -> binding.appBar.setExpanded(true, true));
|
||||
releaseAdapter = new ReleaseAdapter();
|
||||
informationAdapter = new InformationAdapter();
|
||||
RepoLoader.getInstance().addListener(this);
|
||||
return binding.getRoot();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
RepoLoader.getInstance().addListener(this);
|
||||
|
|
@ -208,44 +213,35 @@ public class RepoItemFragment extends BaseFragment implements RepoLoader.Listene
|
|||
}
|
||||
|
||||
@Override
|
||||
public void moduleReleasesLoaded(OnlineModule module) {
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
RepoLoader.getInstance().removeListener(this);
|
||||
binding = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onModuleReleasesLoaded(OnlineModule module) {
|
||||
this.module = module;
|
||||
if (releaseAdapter != null) {
|
||||
runAsync(releaseAdapter::loadItems);
|
||||
if (isResumed() && module.getReleases().size() == 1) {
|
||||
Snackbar.make(binding.snackbar, R.string.module_release_no_more, Snackbar.LENGTH_SHORT).show();
|
||||
}
|
||||
runAsync(releaseAdapter::loadItems);
|
||||
if (module.getReleases().size() == 1) {
|
||||
showHint(R.string.module_release_no_more, true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onThrowable(Throwable t) {
|
||||
if (releaseAdapter != null) {
|
||||
runAsync(releaseAdapter::loadItems);
|
||||
if (isResumed()) {
|
||||
Snackbar.make(binding.snackbar, getString(R.string.repo_load_failed, t.getLocalizedMessage()), Snackbar.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
|
||||
RepoLoader.getInstance().removeListener(this);
|
||||
binding = null;
|
||||
runAsync(releaseAdapter::loadItems);
|
||||
showHint(getString(R.string.repo_load_failed, t.getLocalizedMessage()), true);
|
||||
}
|
||||
|
||||
private class InformationAdapter extends SimpleStatefulAdaptor<InformationAdapter.ViewHolder> {
|
||||
private final OnlineModule module;
|
||||
|
||||
private int rowCount = 0;
|
||||
private int homepageRow = -1;
|
||||
private int collaboratorsRow = -1;
|
||||
private int sourceUrlRow = -1;
|
||||
|
||||
public InformationAdapter(OnlineModule module) {
|
||||
this.module = module;
|
||||
public InformationAdapter() {
|
||||
if (!TextUtils.isEmpty(module.getHomepageUrl())) {
|
||||
homepageRow = rowCount++;
|
||||
}
|
||||
|
|
@ -260,7 +256,7 @@ public class RepoItemFragment extends BaseFragment implements RepoLoader.Listene
|
|||
@NonNull
|
||||
@Override
|
||||
public InformationAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
return new InformationAdapter.ViewHolder(ItemRepoTitleDescriptionBinding.inflate(getLayoutInflater(), parent, false));
|
||||
return new ViewHolder(ItemRepoTitleDescriptionBinding.inflate(getLayoutInflater(), parent, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -302,7 +298,6 @@ public class RepoItemFragment extends BaseFragment implements RepoLoader.Listene
|
|||
NavUtil.startURL(requireActivity(), module.getSourceUrl());
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -322,6 +317,27 @@ public class RepoItemFragment extends BaseFragment implements RepoLoader.Listene
|
|||
}
|
||||
}
|
||||
|
||||
public static class DownloadDialog extends DialogFragment {
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
|
||||
var args = getArguments();
|
||||
if (args == null) throw new IllegalArgumentException();
|
||||
return new BlurBehindDialogBuilder(requireActivity())
|
||||
.setItems(args.getCharSequenceArray("names"), (dialog, which) -> NavUtil.startURL(requireActivity(), args.getStringArrayList("urls").get(which)))
|
||||
.create();
|
||||
}
|
||||
|
||||
static void create(FragmentManager fm, String[] names, ArrayList<String> urls) {
|
||||
var f = new DownloadDialog();
|
||||
var bundle = new Bundle();
|
||||
bundle.putStringArray("names", names);
|
||||
bundle.putStringArrayList("urls", urls);
|
||||
f.setArguments(bundle);
|
||||
f.show(fm, "download");
|
||||
}
|
||||
}
|
||||
|
||||
private class ReleaseAdapter extends EmptyStateRecyclerView.EmptyStateAdapter<ReleaseAdapter.ViewHolder> {
|
||||
private List<Release> items = new ArrayList<>();
|
||||
private final Resources resources = App.getInstance().getResources();
|
||||
|
|
@ -353,9 +369,9 @@ public class RepoItemFragment extends BaseFragment implements RepoLoader.Listene
|
|||
@Override
|
||||
public ReleaseAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
if (viewType == 0) {
|
||||
return new ReleaseAdapter.ReleaseViewHolder(ItemRepoReleaseBinding.inflate(getLayoutInflater(), parent, false));
|
||||
return new ReleaseViewHolder(ItemRepoReleaseBinding.inflate(getLayoutInflater(), parent, false));
|
||||
} else {
|
||||
return new ReleaseAdapter.LoadmoreViewHolder(ItemRepoLoadmoreBinding.inflate(getLayoutInflater(), parent, false));
|
||||
return new LoadmoreViewHolder(ItemRepoLoadmoreBinding.inflate(getLayoutInflater(), parent, false));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -381,9 +397,7 @@ public class RepoItemFragment extends BaseFragment implements RepoLoader.Listene
|
|||
holder.viewAssets.setOnClickListener(v -> {
|
||||
ArrayList<String> names = new ArrayList<>();
|
||||
assets.forEach(releaseAsset -> names.add(releaseAsset.getName()));
|
||||
new BlurBehindDialogBuilder(requireActivity())
|
||||
.setItems(names.toArray(new String[0]), (dialog, which) -> NavUtil.startURL(requireActivity(), assets.get(which).getDownloadUrl()))
|
||||
.show();
|
||||
DownloadDialog.create(getChildFragmentManager(), names.toArray(new String[0]), assets.stream().map(ReleaseAsset::getDownloadUrl).collect(Collectors.toCollection(ArrayList::new)));
|
||||
});
|
||||
} else {
|
||||
holder.viewAssets.setVisibility(View.GONE);
|
||||
|
|
@ -437,38 +451,27 @@ public class RepoItemFragment extends BaseFragment implements RepoLoader.Listene
|
|||
}
|
||||
}
|
||||
|
||||
private class PagerAdapter extends SimpleStatefulAdaptor<PagerAdapter.ViewHolder> {
|
||||
private static class PagerAdapter extends FragmentStateAdapter {
|
||||
|
||||
public PagerAdapter(@NonNull Fragment fragment) {
|
||||
super(fragment);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public PagerAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
if (viewType == 0) {
|
||||
return new PagerAdapter.ReadmeViewHolder(ItemRepoReadmeBinding.inflate(getLayoutInflater(), parent, false));
|
||||
public Fragment createFragment(int position) {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putInt("position", position);
|
||||
Fragment f;
|
||||
if (position == 0) {
|
||||
f = new ReadmeFragment();
|
||||
} else if (position == 1) {
|
||||
f = new RecyclerviewFragment();
|
||||
} else {
|
||||
return new PagerAdapter.RecyclerviewBinding(ItemRepoRecyclerviewBinding.inflate(getLayoutInflater(), parent, false));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull PagerAdapter.ViewHolder holder, int position) {
|
||||
switch (position) {
|
||||
case 0:
|
||||
if (module != null)
|
||||
renderGithubMarkdown(holder.webView, module.getReadmeHTML());
|
||||
break;
|
||||
case 1:
|
||||
case 2:
|
||||
RecyclerView.Adapter adapter;
|
||||
if (position == 1) {
|
||||
adapter = releaseAdapter = new ReleaseAdapter();
|
||||
} else {
|
||||
adapter = new InformationAdapter(module);
|
||||
}
|
||||
holder.recyclerView.setAdapter(adapter);
|
||||
holder.recyclerView.setLayoutManager(new LinearLayoutManager(requireActivity()));
|
||||
RecyclerViewKt.fixEdgeEffect(holder.recyclerView, false, true);
|
||||
break;
|
||||
f = new RecyclerviewFragment();
|
||||
}
|
||||
f.setArguments(bundle);
|
||||
return f;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -481,29 +484,112 @@ public class RepoItemFragment extends BaseFragment implements RepoLoader.Listene
|
|||
return position == 0 ? 0 : 1;
|
||||
}
|
||||
|
||||
class ViewHolder extends RecyclerView.ViewHolder {
|
||||
WebView webView;
|
||||
BorderNestedScrollView scrollView;
|
||||
BorderRecyclerView recyclerView;
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
return position;
|
||||
}
|
||||
}
|
||||
|
||||
public ViewHolder(@NonNull View itemView) {
|
||||
super(itemView);
|
||||
public static abstract class BorderFragment extends BaseFragment {
|
||||
BorderView borderView;
|
||||
|
||||
void attachListeners() {
|
||||
var parent = getParentFragment();
|
||||
if (parent instanceof RepoItemFragment) {
|
||||
var repoItemFragment = (RepoItemFragment) parent;
|
||||
borderView.getBorderViewDelegate().setBorderVisibilityChangedListener((top, oldTop, bottom, oldBottom) -> repoItemFragment.binding.appBar.setLifted(!top));
|
||||
repoItemFragment.binding.appBar.setLifted(!borderView.getBorderViewDelegate().isShowingTopBorder());
|
||||
repoItemFragment.binding.toolbar.setOnClickListener(v -> {
|
||||
repoItemFragment.binding.appBar.setExpanded(true, true);
|
||||
scrollToTop();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class ReadmeViewHolder extends PagerAdapter.ViewHolder {
|
||||
public ReadmeViewHolder(ItemRepoReadmeBinding binding) {
|
||||
super(binding.getRoot());
|
||||
webView = binding.readme;
|
||||
scrollView = binding.scrollView;
|
||||
}
|
||||
abstract void scrollToTop();
|
||||
|
||||
void detachListeners() {
|
||||
borderView.getBorderViewDelegate().setBorderVisibilityChangedListener(null);
|
||||
}
|
||||
|
||||
class RecyclerviewBinding extends PagerAdapter.ViewHolder {
|
||||
public RecyclerviewBinding(ItemRepoRecyclerviewBinding binding) {
|
||||
super(binding.getRoot());
|
||||
recyclerView = binding.recyclerView;
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
attachListeners();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
attachListeners();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
super.onStop();
|
||||
detachListeners();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
detachListeners();
|
||||
}
|
||||
}
|
||||
|
||||
public static class ReadmeFragment extends BorderFragment {
|
||||
ItemRepoReadmeBinding binding;
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
var parent = getParentFragment();
|
||||
if (!(parent instanceof RepoItemFragment)) {
|
||||
getNavController().navigate(R.id.action_repo_item_fragment_to_repo_fragment);
|
||||
return null;
|
||||
}
|
||||
var repoItemFragment = (RepoItemFragment) parent;
|
||||
binding = ItemRepoReadmeBinding.inflate(getLayoutInflater(), container, false);
|
||||
repoItemFragment.renderGithubMarkdown(binding.readme, repoItemFragment.module.getReadmeHTML());
|
||||
borderView = binding.scrollView;
|
||||
return binding.getRoot();
|
||||
}
|
||||
|
||||
@Override
|
||||
void scrollToTop() {
|
||||
binding.scrollView.fullScroll(ScrollView.FOCUS_UP);
|
||||
}
|
||||
}
|
||||
|
||||
public static class RecyclerviewFragment extends BorderFragment {
|
||||
ItemRepoRecyclerviewBinding binding;
|
||||
RecyclerView.Adapter<?> adapter;
|
||||
|
||||
@Override
|
||||
void scrollToTop() {
|
||||
binding.recyclerView.smoothScrollToPosition(0);
|
||||
}
|
||||
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
var arguments = getArguments();
|
||||
var parent = getParentFragment();
|
||||
if (arguments == null || !(parent instanceof RepoItemFragment)) {
|
||||
getNavController().navigate(R.id.action_repo_item_fragment_to_repo_fragment);
|
||||
return null;
|
||||
}
|
||||
var repoItemFragment = (RepoItemFragment) parent;
|
||||
var position = arguments.getInt("position", 0);
|
||||
if (position == 1)
|
||||
adapter = repoItemFragment.releaseAdapter;
|
||||
else if (position == 2)
|
||||
adapter = repoItemFragment.informationAdapter;
|
||||
else return null;
|
||||
binding = ItemRepoRecyclerviewBinding.inflate(getLayoutInflater(), container, false);
|
||||
binding.recyclerView.setAdapter(adapter);
|
||||
binding.recyclerView.setLayoutManager(new LinearLayoutManager(requireActivity()));
|
||||
RecyclerViewKt.fixEdgeEffect(binding.recyclerView, false, true);
|
||||
borderView = binding.recyclerView;
|
||||
return binding.getRoot();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,7 +29,6 @@ import android.text.TextUtils;
|
|||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
import androidx.activity.result.contract.ActivityResultContracts;
|
||||
|
|
@ -42,7 +41,6 @@ import androidx.preference.SwitchPreference;
|
|||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.google.android.material.color.DynamicColors;
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
|
||||
import org.lsposed.manager.App;
|
||||
import org.lsposed.manager.BuildConfig;
|
||||
|
|
@ -72,22 +70,19 @@ public class SettingsFragment extends BaseFragment {
|
|||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
binding = FragmentSettingsBinding.inflate(inflater, container, false);
|
||||
setupToolbar(binding.toolbar, R.string.Settings);
|
||||
binding.appBar.setLiftable(true);
|
||||
setupToolbar(binding.toolbar, binding.clickView, R.string.Settings);
|
||||
if (savedInstanceState == null) {
|
||||
getChildFragmentManager().beginTransaction()
|
||||
.add(R.id.container, new PreferenceFragment()).commitNow();
|
||||
}
|
||||
|
||||
/*
|
||||
CollapsingToolbarLayout consumes window insets, causing child views not
|
||||
receiving window insets.
|
||||
See https://github.com/material-components/material-components-android/issues/1310
|
||||
|
||||
Insets can be handled by RikkaX Insets, so we can manually set
|
||||
OnApplyWindowInsetsListener to null.
|
||||
*/
|
||||
|
||||
binding.collapsingToolbarLayout.setOnApplyWindowInsetsListener(null);
|
||||
if (ConfigManager.isBinderAlive()) {
|
||||
binding.toolbar.setSubtitle(String.format(Locale.ROOT, "%s (%d) - %s",
|
||||
ConfigManager.getXposedVersionName(), ConfigManager.getXposedVersionCode(), ConfigManager.getApi()));
|
||||
} else {
|
||||
binding.toolbar.setSubtitle(String.format(Locale.ROOT, "%s (%d) - %s",
|
||||
BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE, getString(R.string.not_installed)));
|
||||
}
|
||||
return binding.getRoot();
|
||||
}
|
||||
|
||||
|
|
@ -109,11 +104,7 @@ public class SettingsFragment extends BaseFragment {
|
|||
BackupUtils.backup(uri);
|
||||
} catch (Exception e) {
|
||||
var text = App.getInstance().getString(R.string.settings_backup_failed2, e.getMessage());
|
||||
if (parentFragment != null && parentFragment.binding != null && isResumed()) {
|
||||
Snackbar.make(parentFragment.binding.snackbar, text, Snackbar.LENGTH_LONG).show();
|
||||
} else {
|
||||
Toast.makeText(App.getInstance(), text, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
parentFragment.showHint(text, false);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
@ -125,11 +116,7 @@ public class SettingsFragment extends BaseFragment {
|
|||
BackupUtils.restore(uri);
|
||||
} catch (Exception e) {
|
||||
var text = App.getInstance().getString(R.string.settings_restore_failed2, e.getMessage());
|
||||
if (parentFragment != null && parentFragment.binding != null && isResumed()) {
|
||||
Snackbar.make(parentFragment.binding.snackbar, text, Snackbar.LENGTH_LONG).show();
|
||||
} else {
|
||||
Toast.makeText(App.getInstance(), text, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
parentFragment.showHint(text, false);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
@ -311,6 +298,17 @@ public class SettingsFragment extends BaseFragment {
|
|||
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) -> parentFragment.binding.appBar.setLifted(!top));
|
||||
var fragment = getParentFragment();
|
||||
if (fragment instanceof SettingsFragment) {
|
||||
var settingsFragment = (SettingsFragment) fragment;
|
||||
View.OnClickListener l = v -> {
|
||||
settingsFragment.binding.appBar.setExpanded(true, true);
|
||||
recyclerView.smoothScrollToPosition(0);
|
||||
};
|
||||
settingsFragment.binding.toolbar.setOnClickListener(l);
|
||||
settingsFragment.binding.clickView.setOnClickListener(l);
|
||||
}
|
||||
return recyclerView;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -40,22 +40,27 @@ import java.util.HashMap;
|
|||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public final class ModuleUtil {
|
||||
// xposedminversion below this
|
||||
public static int MIN_MODULE_VERSION = 2; // reject modules with
|
||||
private static ModuleUtil instance = null;
|
||||
private final PackageManager pm;
|
||||
private final List<ModuleListener> listeners = new CopyOnWriteArrayList<>();
|
||||
private final Set<ModuleListener> listeners = ConcurrentHashMap.newKeySet();
|
||||
private HashSet<String> enabledModules = new HashSet<>();
|
||||
private Map<Pair<String, Integer>, InstalledModule> installedModules = new HashMap<>();
|
||||
private boolean isReloading = false;
|
||||
private boolean modulesLoaded = false;
|
||||
|
||||
private ModuleUtil() {
|
||||
pm = App.getInstance().getPackageManager();
|
||||
}
|
||||
|
||||
public boolean isModulesLoaded() {
|
||||
return modulesLoaded;
|
||||
}
|
||||
|
||||
public static synchronized ModuleUtil getInstance() {
|
||||
if (instance == null) {
|
||||
instance = new ModuleUtil();
|
||||
|
|
@ -76,16 +81,10 @@ public final class ModuleUtil {
|
|||
return result;
|
||||
}
|
||||
|
||||
public void reloadInstalledModules() {
|
||||
synchronized (this) {
|
||||
if (isReloading)
|
||||
return;
|
||||
isReloading = true;
|
||||
}
|
||||
synchronized public void reloadInstalledModules() {
|
||||
modulesLoaded = false;
|
||||
if (!ConfigManager.isBinderAlive()) {
|
||||
synchronized (this) {
|
||||
isReloading = false;
|
||||
}
|
||||
modulesLoaded = true;
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -102,11 +101,9 @@ public final class ModuleUtil {
|
|||
installedModules = modules;
|
||||
|
||||
enabledModules = new HashSet<>(Arrays.asList(ConfigManager.getEnabledModules()));
|
||||
synchronized (this) {
|
||||
isReloading = false;
|
||||
}
|
||||
modulesLoaded = true;
|
||||
|
||||
for (var listener: listeners) {
|
||||
for (var listener : listeners) {
|
||||
listener.onModulesReloaded();
|
||||
}
|
||||
}
|
||||
|
|
@ -126,7 +123,7 @@ public final class ModuleUtil {
|
|||
InstalledModule old = installedModules.remove(Pair.create(packageName, userId));
|
||||
if (old != null) {
|
||||
for (ModuleListener listener : listeners) {
|
||||
listener.onSingleInstalledModuleReloaded(old);
|
||||
listener.onSingleModuleReloaded(old);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
|
@ -137,31 +134,33 @@ public final class ModuleUtil {
|
|||
InstalledModule module = new InstalledModule(pkg);
|
||||
installedModules.put(Pair.create(packageName, userId), module);
|
||||
for (ModuleListener listener : listeners) {
|
||||
listener.onSingleInstalledModuleReloaded(module);
|
||||
listener.onSingleModuleReloaded(module);
|
||||
}
|
||||
return module;
|
||||
} else {
|
||||
InstalledModule old = installedModules.remove(Pair.create(packageName, userId));
|
||||
if (old != null) {
|
||||
for (ModuleListener listener : listeners) {
|
||||
listener.onSingleInstalledModuleReloaded(old);
|
||||
listener.onSingleModuleReloaded(old);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public InstalledModule getModule(String packageName, int userId) {
|
||||
return installedModules.get(Pair.create(packageName, userId));
|
||||
return modulesLoaded ? installedModules.get(Pair.create(packageName, userId)) : null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public InstalledModule getModule(String packageName) {
|
||||
return getModule(packageName, 0);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
synchronized public Map<Pair<String, Integer>, InstalledModule> getModules() {
|
||||
return isReloading ? null : installedModules;
|
||||
return modulesLoaded ? installedModules : null;
|
||||
}
|
||||
|
||||
public boolean setModuleEnabled(String packageName, boolean enabled) {
|
||||
|
|
@ -181,12 +180,11 @@ public final class ModuleUtil {
|
|||
}
|
||||
|
||||
public int getEnabledModulesCount() {
|
||||
return isReloading ? -1 : enabledModules.size();
|
||||
return modulesLoaded ? enabledModules.size() : -1;
|
||||
}
|
||||
|
||||
public void addListener(ModuleListener listener) {
|
||||
if (!listeners.contains(listener))
|
||||
listeners.add(listener);
|
||||
listeners.add(listener);
|
||||
}
|
||||
|
||||
public void removeListener(ModuleListener listener) {
|
||||
|
|
@ -198,7 +196,7 @@ public final class ModuleUtil {
|
|||
* Called whenever one (previously or now) installed module has been
|
||||
* reloaded
|
||||
*/
|
||||
default void onSingleInstalledModuleReloaded(InstalledModule module) {
|
||||
default void onSingleModuleReloaded(InstalledModule module) {
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -290,7 +288,7 @@ public final class ModuleUtil {
|
|||
e.printStackTrace();
|
||||
}
|
||||
RepoLoader repoLoader = RepoLoader.getInstance();
|
||||
if (scopeList == null && repoLoader.isRepoLoaded()) {
|
||||
if (scopeList == null) {
|
||||
OnlineModule module = repoLoader.getOnlineModule(packageName);
|
||||
if (module != null && module.getScope() != null) {
|
||||
scopeList = module.getScope();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M20,2L4,2c-1.1,0 -1.99,0.9 -1.99,2L2,22l4,-4h14c1.1,0 2,-0.9 2,-2L22,4c0,-1.1 -0.9,-2 -2,-2zM6,9h12v2L6,11L6,9zM14,14L6,14v-2h8v2zM18,8L6,8L6,6h12v2z"/>
|
||||
</vector>
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM13,17h-2v-6h2v6zM13,9h-2L11,7h2v2z"/>
|
||||
</vector>
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M15.5,14h-0.79l-0.28,-0.27C15.41,12.59 16,11.11 16,9.5 16,5.91 13.09,3 9.5,3S3,5.91 3,9.5 5.91,16 9.5,16c1.61,0 3.09,-0.59 4.23,-1.57l0.27,0.28v0.79l5,4.99L20.49,19l-4.99,-5zM9.5,14C7.01,14 5,11.99 5,9.5S7.01,5 9.5,5 14,7.01 14,9.5 11.99,14 9.5,14z" />
|
||||
</vector>
|
||||
|
|
@ -19,13 +19,13 @@
|
|||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item>
|
||||
<shape>
|
||||
<solid android:color="?attr/colorSurface"/>
|
||||
<solid android:color="?attr/colorSurface" />
|
||||
<corners android:radius="?popupBackgroundRadius" />
|
||||
</shape>
|
||||
</item>
|
||||
<item>
|
||||
<shape>
|
||||
<solid android:color="@color/m3_popupmenu_overlay_color"/>
|
||||
<solid android:color="@color/m3_popupmenu_overlay_color" />
|
||||
<corners android:radius="?popupBackgroundRadius" />
|
||||
</shape>
|
||||
</item>
|
||||
|
|
|
|||
|
|
@ -115,6 +115,7 @@
|
|||
style="@style/DeviceInfoDialogValue"
|
||||
android:id="@+id/system_abi"
|
||||
android:layout_width="match_parent"
|
||||
android:paddingBottom="0dp"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
</LinearLayout>
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@
|
|||
style="@style/DeviceInfoDialogValue"
|
||||
android:id="@+id/value"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingBottom="0dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
</LinearLayout>
|
||||
|
|
|
|||
|
|
@ -14,20 +14,12 @@
|
|||
~ You should have received a copy of the GNU General Public License
|
||||
~ along with LSPosed. If not, see <https://www.gnu.org/licenses/>.
|
||||
~
|
||||
~ Copyright (C) 2020 EdXposed Contributors
|
||||
~ Copyright (C) 2021 LSPosed Contributors
|
||||
-->
|
||||
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<item
|
||||
android:id="@+id/menu_refresh"
|
||||
android:title="@string/refresh" />
|
||||
<item
|
||||
android:id="@+id/menu_info"
|
||||
android:title="@string/info" />
|
||||
<item
|
||||
android:id="@+id/menu_about"
|
||||
android:title="@string/About" />
|
||||
|
||||
</menu>
|
||||
<com.google.android.material.textview.MaterialTextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/title"
|
||||
style="@style/MaterialAlertDialog.Material3.Title.Text.CenterStacked"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="?attr/dialogPreferredPadding"
|
||||
android:paddingBottom="?attr/dialogPreferredPadding" />
|
||||
|
|
@ -18,8 +18,8 @@
|
|||
-->
|
||||
|
||||
<rikka.widget.borderview.BorderNestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="match_parent">
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/container"
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@
|
|||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/snackbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:consumeSystemWindowsInsets="start|end"
|
||||
|
|
@ -36,22 +35,26 @@
|
|||
android:fitsSystemWindows="false"
|
||||
app:fitsSystemWindowsInsets="top">
|
||||
|
||||
<com.google.android.material.appbar.CollapsingToolbarLayout
|
||||
android:id="@+id/collapsing_toolbar_layout"
|
||||
style="?attr/collapsingToolbarLayoutMediumStyle"
|
||||
<com.google.android.material.appbar.SubtitleCollapsingToolbarLayout
|
||||
style="?attr/collapsingToolbarLayoutLargeStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/collapsingToolbarLayoutMediumSize"
|
||||
android:layout_height="?attr/collapsingToolbarLayoutLargeSize"
|
||||
app:layout_scrollFlags="scroll|exitUntilCollapsed"
|
||||
app:titleCollapseMode="scale">
|
||||
|
||||
<View
|
||||
android:id="@+id/click_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:elevation="0dp"
|
||||
android:theme="@style/ThemeOverlay.MaterialComponents.ActionBar"
|
||||
app:layout_collapseMode="pin" />
|
||||
|
||||
</com.google.android.material.appbar.CollapsingToolbarLayout>
|
||||
</com.google.android.material.appbar.SubtitleCollapsingToolbarLayout>
|
||||
|
||||
<rikka.widget.switchbar.SwitchBar
|
||||
android:id="@+id/master_switch"
|
||||
|
|
@ -60,12 +63,27 @@
|
|||
android:layout_gravity="bottom"
|
||||
app:layout_collapseMode="parallax"
|
||||
app:layout_collapseParallaxMultiplier="1"
|
||||
app:layout_scrollFlags="scroll|exitUntilCollapsed"
|
||||
app:layout_scrollEffect="compress"
|
||||
app:layout_scrollFlags="scroll|exitUntilCollapsed"
|
||||
app:switchOffText="@string/enable_module"
|
||||
app:switchOnText="@string/enable_module" />
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
android:id="@+id/fab"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="end|bottom"
|
||||
android:layout_margin="?attr/dialogPreferredPadding"
|
||||
android:contentDescription="@string/module_settings"
|
||||
android:src="@drawable/ic_round_settings_24"
|
||||
android:tooltipText="@string/module_settings"
|
||||
android:visibility="gone"
|
||||
app:backgroundTint="?attr/colorPrimary"
|
||||
app:layout_behavior="@string/hide_bottom_view_on_scroll_behavior"
|
||||
app:layout_fitsSystemWindowsInsets="bottom"
|
||||
app:tint="?attr/colorSurface" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
|
|
@ -73,20 +91,13 @@
|
|||
app:layout_behavior="@string/appbar_scrolling_view_behavior"
|
||||
tools:ignore="MissingPrefix">
|
||||
|
||||
<com.google.android.material.progressindicator.LinearProgressIndicator
|
||||
android:id="@+id/progress"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:indeterminate="true"
|
||||
app:hideAnimationBehavior="outward" />
|
||||
|
||||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
android:id="@+id/swipeRefreshLayout"
|
||||
android:id="@+id/swipe_refresh_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<org.lsposed.manager.ui.widget.EmptyStateRecyclerView
|
||||
android:id="@+id/recyclerView"
|
||||
android:id="@+id/recycler_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clipToPadding="false"
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@
|
|||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/snackbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:consumeSystemWindowsInsets="start|end"
|
||||
|
|
@ -116,7 +115,8 @@
|
|||
android:id="@+id/modules"
|
||||
style="@style/HomeCard.Secondary"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
android:layout_height="wrap_content"
|
||||
android:tooltipText="@string/Modules">
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
|
|
@ -156,7 +156,8 @@
|
|||
android:id="@+id/download"
|
||||
style="@style/HomeCard.Secondary"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
android:layout_height="wrap_content"
|
||||
android:tooltipText="@string/module_repo">
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
|
|
@ -196,7 +197,8 @@
|
|||
android:id="@+id/logs"
|
||||
style="@style/HomeCard.Tertiary"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
android:layout_height="wrap_content"
|
||||
android:tooltipText="@string/Logs">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
|
|
@ -225,7 +227,8 @@
|
|||
android:id="@+id/settings"
|
||||
style="@style/HomeCard.Tertiary"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
android:layout_height="wrap_content"
|
||||
android:tooltipText="@string/Settings">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
|
|
@ -253,7 +256,8 @@
|
|||
android:id="@+id/issue"
|
||||
style="@style/HomeCard.Tertiary"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
android:layout_height="wrap_content"
|
||||
android:tooltipText="@string/feedback_or_suggestion">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
|
|
@ -265,14 +269,44 @@
|
|||
<ImageView
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:contentDescription="@string/report_issue"
|
||||
app:srcCompat="@drawable/ic_round_bug_report_24" />
|
||||
android:contentDescription="@string/feedback_or_suggestion"
|
||||
app:srcCompat="@drawable/ic_baseline_chat_24" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="24dp"
|
||||
android:text="@string/report_issue"
|
||||
android:text="@string/feedback_or_suggestion"
|
||||
android:textAppearance="?android:attr/textAppearanceListItem"
|
||||
android:textSize="16sp" />
|
||||
</LinearLayout>
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:id="@+id/about"
|
||||
style="@style/HomeCard.Tertiary"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:tooltipText="@string/About">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
android:padding="16dp">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:contentDescription="@string/About"
|
||||
app:srcCompat="@drawable/ic_baseline_info_24" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="24dp"
|
||||
android:text="@string/About"
|
||||
android:textAppearance="?android:attr/textAppearanceListItem"
|
||||
android:textSize="16sp" />
|
||||
</LinearLayout>
|
||||
|
|
|
|||
|
|
@ -1,104 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ 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 <https://www.gnu.org/licenses/>.
|
||||
~
|
||||
~ Copyright (C) 2020 EdXposed Contributors
|
||||
~ Copyright (C) 2021 LSPosed Contributors
|
||||
-->
|
||||
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/snackbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:consumeSystemWindowsInsets="start|end"
|
||||
app:edgeToEdge="true"
|
||||
app:fitsSystemWindowsInsets="start|end">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/app_bar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:fitsSystemWindows="false"
|
||||
app:fitsSystemWindowsInsets="top">
|
||||
|
||||
<com.google.android.material.appbar.CollapsingToolbarLayout
|
||||
android:id="@+id/collapsing_toolbar_layout"
|
||||
style="?attr/collapsingToolbarLayoutMediumStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/collapsingToolbarLayoutMediumSize"
|
||||
app:layout_scrollFlags="scroll|exitUntilCollapsed"
|
||||
app:titleCollapseMode="scale">
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:elevation="0dp"
|
||||
app:layout_collapseMode="pin" />
|
||||
</com.google.android.material.appbar.CollapsingToolbarLayout>
|
||||
|
||||
<com.google.android.material.tabs.TabLayout
|
||||
android:id="@+id/sliding_tabs"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/tab_layout_height"
|
||||
android:layout_gravity="bottom"
|
||||
android:background="@android:color/transparent"
|
||||
app:layout_scrollFlags="scroll|enterAlways"
|
||||
app:tabIndicatorAnimationMode="elastic">
|
||||
|
||||
<com.google.android.material.tabs.TabItem
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/nav_item_logs_module" />
|
||||
|
||||
<com.google.android.material.tabs.TabItem
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/nav_item_logs_lsp" />
|
||||
</com.google.android.material.tabs.TabLayout>
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<HorizontalScrollView
|
||||
android:id="@+id/horizontalScrollView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fillViewport="true"
|
||||
android:scrollbars="none"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
||||
|
||||
<ScrollView
|
||||
android:id="@+id/scrollView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:fillViewport="true"
|
||||
android:scrollbars="none">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/body"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:clipToPadding="false"
|
||||
android:fontFamily="monospace"
|
||||
android:textColor="?attr/colorOnSurface"
|
||||
android:textIsSelectable="true"
|
||||
android:textSize="12sp"
|
||||
app:borderBottomVisibility="never"
|
||||
app:borderTopDrawable="@null"
|
||||
app:borderTopVisibility="whenTop"
|
||||
app:fitsSystemWindowsInsets="bottom" />
|
||||
</ScrollView>
|
||||
</HorizontalScrollView>
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
|
@ -19,7 +19,6 @@
|
|||
-->
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/snackbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:animateLayoutChanges="true"
|
||||
|
|
@ -35,22 +34,28 @@
|
|||
android:fitsSystemWindows="false"
|
||||
app:fitsSystemWindowsInsets="top">
|
||||
|
||||
<com.google.android.material.appbar.CollapsingToolbarLayout
|
||||
android:id="@+id/collapsing_toolbar_layout"
|
||||
style="?attr/collapsingToolbarLayoutMediumStyle"
|
||||
<com.google.android.material.appbar.SubtitleCollapsingToolbarLayout
|
||||
android:id="@+id/toolbar_layout"
|
||||
style="?attr/collapsingToolbarLayoutLargeStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/collapsingToolbarLayoutMediumSize"
|
||||
android:layout_height="?attr/collapsingToolbarLayoutLargeSize"
|
||||
app:layout_scrollFlags="scroll|exitUntilCollapsed"
|
||||
app:titleCollapseMode="scale">
|
||||
|
||||
<View
|
||||
android:id="@+id/click_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:elevation="0dp"
|
||||
android:theme="@style/ThemeOverlay.MaterialComponents.ActionBar"
|
||||
app:layout_collapseMode="pin" />
|
||||
|
||||
</com.google.android.material.appbar.CollapsingToolbarLayout>
|
||||
</com.google.android.material.appbar.SubtitleCollapsingToolbarLayout>
|
||||
|
||||
<com.google.android.material.tabs.TabLayout
|
||||
android:id="@+id/tab_layout"
|
||||
|
|
@ -58,7 +63,6 @@
|
|||
android:layout_height="@dimen/tab_layout_height"
|
||||
android:layout_gravity="bottom"
|
||||
android:background="@android:color/transparent"
|
||||
app:layout_scrollFlags="scroll|enterAlways"
|
||||
app:tabGravity="center"
|
||||
app:tabIndicatorAnimationMode="elastic"
|
||||
app:tabMode="scrollable" />
|
||||
|
|
@ -69,14 +73,6 @@
|
|||
android:layout_height="match_parent"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
||||
|
||||
<com.google.android.material.progressindicator.LinearProgressIndicator
|
||||
android:id="@+id/progress"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:indeterminate="true"
|
||||
android:visibility="gone"
|
||||
app:hideAnimationBehavior="outward" />
|
||||
|
||||
<androidx.viewpager2.widget.ViewPager2
|
||||
android:id="@+id/view_pager"
|
||||
android:layout_width="match_parent"
|
||||
|
|
@ -89,12 +85,13 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="end|bottom"
|
||||
android:layout_margin="16dp"
|
||||
android:layout_margin="?attr/dialogPreferredPadding"
|
||||
android:contentDescription="@string/add_module_to_user"
|
||||
android:src="@drawable/ic_baseline_add_24"
|
||||
android:tooltipText="@string/add_module_to_user"
|
||||
android:visibility="gone"
|
||||
app:backgroundTint="?attr/colorPrimary"
|
||||
app:tint="@color/primary_text_material_inverse"
|
||||
app:layout_behavior="@string/hide_bottom_view_on_scroll_behavior"
|
||||
app:layout_fitsSystemWindowsInsets="bottom" />
|
||||
app:layout_fitsSystemWindowsInsets="bottom"
|
||||
app:tint="?attr/colorSurface" />
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@
|
|||
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/snackbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:consumeSystemWindowsInsets="start|end"
|
||||
|
|
@ -34,22 +33,28 @@
|
|||
android:fitsSystemWindows="false"
|
||||
app:fitsSystemWindowsInsets="top">
|
||||
|
||||
<com.google.android.material.appbar.CollapsingToolbarLayout
|
||||
android:id="@+id/collapsing_toolbar_layout"
|
||||
style="?attr/collapsingToolbarLayoutMediumStyle"
|
||||
<com.google.android.material.appbar.SubtitleCollapsingToolbarLayout
|
||||
android:id="@+id/toolbar_layout"
|
||||
style="?attr/collapsingToolbarLayoutLargeStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/collapsingToolbarLayoutMediumSize"
|
||||
android:layout_height="?attr/collapsingToolbarLayoutLargeSize"
|
||||
app:forceApplySystemWindowInsetTop="true"
|
||||
app:titleCollapseMode="scale"
|
||||
app:layout_scrollFlags="scroll|exitUntilCollapsed|snap">
|
||||
app:layout_scrollFlags="scroll|exitUntilCollapsed"
|
||||
app:titleCollapseMode="scale">
|
||||
|
||||
<View
|
||||
android:id="@+id/click_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:elevation="0dp"
|
||||
android:theme="@style/ThemeOverlay.MaterialComponents.ActionBar"
|
||||
app:layout_collapseMode="pin" />
|
||||
</com.google.android.material.appbar.CollapsingToolbarLayout>
|
||||
</com.google.android.material.appbar.SubtitleCollapsingToolbarLayout>
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
|
|
@ -58,25 +63,23 @@
|
|||
android:layout_height="wrap_content"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
||||
|
||||
<com.google.android.material.progressindicator.LinearProgressIndicator
|
||||
android:id="@+id/progress"
|
||||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
android:id="@+id/swipe_refresh_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:indeterminate="true"
|
||||
android:visibility="gone"
|
||||
app:hideAnimationBehavior="outward" />
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<org.lsposed.manager.ui.widget.StatefulRecyclerView
|
||||
android:id="@+id/recyclerView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clipToPadding="false"
|
||||
android:fadeScrollbars="true"
|
||||
android:scrollbarStyle="insideOverlay"
|
||||
android:scrollbars="vertical"
|
||||
app:borderBottomVisibility="never"
|
||||
app:borderTopDrawable="@null"
|
||||
app:borderTopVisibility="whenTop"
|
||||
app:fitsSystemWindowsInsets="bottom" />
|
||||
<org.lsposed.manager.ui.widget.EmptyStateRecyclerView
|
||||
android:id="@+id/recycler_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clipToPadding="false"
|
||||
android:fadeScrollbars="true"
|
||||
android:scrollbarStyle="insideOverlay"
|
||||
android:scrollbars="vertical"
|
||||
app:borderBottomVisibility="never"
|
||||
app:borderTopDrawable="@null"
|
||||
app:borderTopVisibility="whenTop"
|
||||
app:fitsSystemWindowsInsets="bottom" />
|
||||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||
</FrameLayout>
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@
|
|||
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/snackbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:consumeSystemWindowsInsets="start|end"
|
||||
|
|
@ -34,28 +33,32 @@
|
|||
android:fitsSystemWindows="false"
|
||||
app:fitsSystemWindowsInsets="top">
|
||||
|
||||
<com.google.android.material.appbar.CollapsingToolbarLayout
|
||||
android:id="@+id/collapsing_toolbar_layout"
|
||||
style="?attr/collapsingToolbarLayoutMediumStyle"
|
||||
<com.google.android.material.appbar.SubtitleCollapsingToolbarLayout
|
||||
style="?attr/collapsingToolbarLayoutLargeStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/collapsingToolbarLayoutMediumSize"
|
||||
app:layout_scrollFlags="scroll|exitUntilCollapsed|snap"
|
||||
android:layout_height="?attr/collapsingToolbarLayoutLargeSize"
|
||||
app:layout_scrollFlags="scroll|exitUntilCollapsed"
|
||||
app:titleCollapseMode="scale">
|
||||
|
||||
<View
|
||||
android:id="@+id/click_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:elevation="0dp"
|
||||
app:layout_collapseMode="pin" />
|
||||
</com.google.android.material.appbar.CollapsingToolbarLayout>
|
||||
</com.google.android.material.appbar.SubtitleCollapsingToolbarLayout>
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<androidx.fragment.app.FragmentContainerView
|
||||
android:id="@+id/container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ 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 <https://www.gnu.org/licenses/>.
|
||||
~
|
||||
~ Copyright (C) 2021 LSPosed Contributors
|
||||
-->
|
||||
<com.google.android.material.textview.MaterialTextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/log_item"
|
||||
style="@style/TextAppearance.AppCompat.Small"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:clipToPadding="false"
|
||||
android:fontFamily="monospace"
|
||||
android:textColor="?attr/colorOnSurface"
|
||||
android:textSize="12sp" />
|
||||
|
|
@ -39,8 +39,8 @@
|
|||
|
||||
<ImageView
|
||||
android:id="@+id/app_icon"
|
||||
android:layout_width="36dp"
|
||||
android:layout_height="36dp"
|
||||
android:layout_width="@dimen/app_icon_size"
|
||||
android:layout_height="@dimen/app_icon_size"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
|
|
@ -52,7 +52,8 @@
|
|||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="20dp"
|
||||
android:singleLine="false"
|
||||
android:ellipsize="marquee"
|
||||
android:scrollbars="none"
|
||||
android:textAppearance="?android:attr/textAppearanceListItem"
|
||||
android:textIsSelectable="false"
|
||||
android:textSize="16sp"
|
||||
|
|
@ -67,6 +68,7 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:maxLines="5"
|
||||
android:scrollbars="none"
|
||||
android:textAppearance="?android:attr/textAppearanceListItemSecondary"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
app:layout_constraintBottom_toBottomOf="@id/hint"
|
||||
|
|
|
|||
|
|
@ -42,7 +42,9 @@
|
|||
android:id="@+id/app_name"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="marquee"
|
||||
android:maxLines="1"
|
||||
android:scrollbars="none"
|
||||
android:textAppearance="?android:attr/textAppearanceListItem"
|
||||
android:textSize="16sp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
|
|
|
|||
|
|
@ -19,14 +19,14 @@
|
|||
-->
|
||||
<org.lsposed.manager.ui.widget.EmptyStateRecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/recyclerView"
|
||||
android:id="@+id/recycler_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clipToPadding="false"
|
||||
android:fadeScrollbars="true"
|
||||
android:scrollbarStyle="insideOverlay"
|
||||
android:scrollbars="vertical"
|
||||
app:borderTopVisibility="whenTop"
|
||||
app:borderTopDrawable="@null"
|
||||
app:borderBottomVisibility="never"
|
||||
app:borderTopDrawable="@null"
|
||||
app:borderTopVisibility="whenTop"
|
||||
app:fitsSystemWindowsInsets="bottom" />
|
||||
|
|
|
|||
|
|
@ -17,29 +17,22 @@
|
|||
~ Copyright (C) 2021 LSPosed Contributors
|
||||
-->
|
||||
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:paddingTop="?attr/dialogPreferredPadding"
|
||||
android:id="@+id/swipe_refresh_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<com.google.android.material.progressindicator.CircularProgressIndicator
|
||||
android:id="@+id/progress"
|
||||
android:layout_width="36dp"
|
||||
android:layout_height="36dp"
|
||||
android:layout_gravity="center"
|
||||
android:indeterminate="true"
|
||||
android:visibility="visible"
|
||||
app:hideAnimationBehavior="inward"
|
||||
app:showAnimationBehavior="outward" />
|
||||
|
||||
<org.lsposed.manager.ui.widget.EmptyStateRecyclerView
|
||||
android:id="@+id/list"
|
||||
android:id="@+id/recycler_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clipToPadding="false"
|
||||
android:fadeScrollbars="true"
|
||||
android:minHeight="?attr/listPreferredItemHeight"
|
||||
android:scrollbarStyle="insideOverlay"
|
||||
android:scrollbars="vertical" />
|
||||
</FrameLayout>
|
||||
android:scrollbars="vertical"
|
||||
app:borderBottomVisibility="never"
|
||||
app:borderTopDrawable="@null"
|
||||
app:borderTopVisibility="whenTop"
|
||||
app:fitsSystemWindowsInsets="bottom" />
|
||||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||
|
|
@ -18,19 +18,15 @@
|
|||
~ Copyright (C) 2021 LSPosed Contributors
|
||||
-->
|
||||
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<item
|
||||
android:id="@+id/menu_search"
|
||||
android:actionViewClass="androidx.appcompat.widget.SearchView"
|
||||
android:showAsAction="ifRoom"
|
||||
android:title="" />
|
||||
|
||||
<item
|
||||
android:id="@+id/menu_launch"
|
||||
android:icon="@drawable/ic_settings"
|
||||
android:showAsAction="ifRoom"
|
||||
android:title="@string/module_settings" />
|
||||
android:icon="@drawable/ic_baseline_search_24"
|
||||
android:showAsAction="always|collapseActionView"
|
||||
tools:ignore="AlwaysShowAction" />
|
||||
|
||||
<item
|
||||
android:id="@+id/use_recommended"
|
||||
|
|
|
|||
|
|
@ -25,12 +25,6 @@
|
|||
android:showAsAction="ifRoom"
|
||||
android:title="@string/menuSaveToSd" />
|
||||
|
||||
<item
|
||||
android:id="@+id/menu_refresh"
|
||||
android:icon="@drawable/ic_refresh"
|
||||
android:showAsAction="ifRoom"
|
||||
android:title="@string/menuReload" />
|
||||
|
||||
<item
|
||||
android:id="@+id/menu_scroll_top"
|
||||
android:showAsAction="never"
|
||||
|
|
@ -46,4 +40,9 @@
|
|||
android:showAsAction="never"
|
||||
android:title="@string/menuClearLog" />
|
||||
|
||||
<item
|
||||
android:id="@+id/menu_word_wrap"
|
||||
android:checkable="true"
|
||||
android:checked="false"
|
||||
android:title="@string/menu_enable_word_wrap" />
|
||||
</menu>
|
||||
|
|
|
|||
|
|
@ -18,18 +18,12 @@
|
|||
~ Copyright (C) 2021 LSPosed Contributors
|
||||
-->
|
||||
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<item
|
||||
android:id="@+id/menu_search"
|
||||
app:actionViewClass="androidx.appcompat.widget.SearchView"
|
||||
app:showAsAction="ifRoom" />
|
||||
|
||||
<item
|
||||
android:id="@+id/menu_refresh"
|
||||
android:title="@string/refresh"
|
||||
android:icon="@drawable/ic_refresh"
|
||||
app:showAsAction="ifRoom" />
|
||||
android:actionViewClass="androidx.appcompat.widget.SearchView"
|
||||
android:icon="@drawable/ic_baseline_search_24"
|
||||
android:showAsAction="ifRoom|collapseActionView" />
|
||||
|
||||
</menu>
|
||||
|
|
|
|||
|
|
@ -19,23 +19,19 @@
|
|||
-->
|
||||
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<item
|
||||
android:id="@+id/menu_search"
|
||||
app:actionViewClass="androidx.appcompat.widget.SearchView"
|
||||
app:showAsAction="ifRoom" />
|
||||
|
||||
<item
|
||||
android:id="@+id/menu_refresh"
|
||||
android:title="@string/refresh"
|
||||
android:icon="@drawable/ic_refresh"
|
||||
app:showAsAction="ifRoom" />
|
||||
android:actionViewClass="androidx.appcompat.widget.SearchView"
|
||||
android:icon="@drawable/ic_baseline_search_24"
|
||||
android:showAsAction="always|collapseActionView"
|
||||
tools:ignore="AlwaysShowAction" />
|
||||
|
||||
<item
|
||||
android:id="@+id/item_list_sort"
|
||||
android:title="@string/menu_sort"
|
||||
app:showAsAction="never">
|
||||
android:showAsAction="never"
|
||||
android:title="@string/menu_sort">
|
||||
<menu>
|
||||
<group android:checkableBehavior="single">
|
||||
<item
|
||||
|
|
@ -49,4 +45,4 @@
|
|||
</group>
|
||||
</menu>
|
||||
</item>
|
||||
</menu>
|
||||
</menu>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ This file is part of LSPosed.
|
||||
~
|
||||
~ LSPosed is free software: you can redistribute it and/or modify
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ This file is part of LSPosed.
|
||||
~
|
||||
~ LSPosed is free software: you can redistribute it and/or modify
|
||||
|
|
|
|||
|
|
@ -21,4 +21,65 @@
|
|||
<resources>
|
||||
<attr name="colorNormal" format="color" />
|
||||
<attr name="colorInstall" format="color" />
|
||||
|
||||
|
||||
<declare-styleable name="SubtitleCollapsingToolbarLayout">
|
||||
<!-- Specifies extra space on the start, top, end and bottom sides of the the expanded title text.
|
||||
Margin values should be positive,
|
||||
subtitle will also be affected. -->
|
||||
<attr name="expandedTitleMargin" />
|
||||
<!-- Specifies extra space on the start side of the the expanded title text. Margin values should be positive,
|
||||
subtitle will also be affected. -->
|
||||
<attr name="expandedTitleMarginStart" />
|
||||
<!-- Specifies extra space on the top side of the the expanded title text. Margin values should be positive,
|
||||
subtitle will also be affected. -->
|
||||
<attr name="expandedTitleMarginTop" />
|
||||
<!-- Specifies extra space on the end side of the the expanded title text. Margin values should be positive,
|
||||
subtitle will also be affected. -->
|
||||
<attr name="expandedTitleMarginEnd" />
|
||||
<!-- Specifies extra space on the bottom side of the the expanded title text. Margin values should be positive,
|
||||
subtitle will also be affected. -->
|
||||
<attr name="expandedTitleMarginBottom" />
|
||||
|
||||
<!-- The text appearance of the CollapsingToolbarLayout's title when it is fully 'expanded' -->
|
||||
<attr name="expandedTitleTextAppearance" />
|
||||
<!-- The text appearance of the CollapsingToolbarLayout's subtitle when it is fully 'expanded' -->
|
||||
<attr name="expandedSubtitleTextAppearance" format="reference" />
|
||||
<!-- The text appearance of the CollapsingToolbarLayouts title when it is fully 'collapsed' -->
|
||||
<attr name="collapsedTitleTextAppearance" />
|
||||
<!-- The text appearance of the CollapsingToolbarLayouts subtitle when it is fully 'collapsed' -->
|
||||
<attr name="collapsedSubtitleTextAppearance" format="reference" />
|
||||
|
||||
<!-- The drawable to use as a scrim on top of the CollapsingToolbarLayouts
|
||||
content when it has been scrolled sufficiently off screen. -->
|
||||
<attr name="contentScrim" />
|
||||
<!-- The drawable to use as a scrim for the status bar content when the
|
||||
CollapsingToolbarLayout has been scrolled sufficiently off screen.
|
||||
Only works on Lollipop with the correct setup. -->
|
||||
<attr name="statusBarScrim" />
|
||||
<!-- The id of the primary Toolbar child that you wish to use for the purpose of collapsing.
|
||||
This Toolbar descendant view does not need to be a direct child of the layout.
|
||||
If you do not set this, the first direct Toolbar child found will be used. -->
|
||||
<attr name="toolbarId" />
|
||||
<!-- Specifies the amount of visible height in pixels used to define when to trigger a
|
||||
scrim visibility change. -->
|
||||
<attr name="scrimVisibleHeightTrigger" />
|
||||
<!-- Specifies the duration used for scrim visibility animations. -->
|
||||
<attr name="scrimAnimationDuration" />
|
||||
|
||||
<!-- Specifies how the title should be positioned when collapsed,
|
||||
subtitle will also be affected. -->
|
||||
<attr name="collapsedTitleGravity" />
|
||||
<!-- Specifies how the title should be positioned when expanded,
|
||||
subtitle will also be affected. -->
|
||||
<attr name="expandedTitleGravity" />
|
||||
|
||||
<!-- Whether the CollapsingToolbarLayout should draw its own shrinking/growing title. -->
|
||||
<attr name="titleEnabled" />
|
||||
<!-- The title to show when titleEnabled is set to true. -->
|
||||
<attr name="title" />
|
||||
<!-- The subtitle to show when titleEnabled is set to true. -->
|
||||
<attr name="subtitle" />
|
||||
</declare-styleable>
|
||||
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@
|
|||
~ You should have received a copy of the GNU General Public License
|
||||
~ along with LSPosed. If not, see <https://www.gnu.org/licenses/>.
|
||||
~
|
||||
~ Copyright (C) 2020 EdXposed Contributors
|
||||
~ Copyright (C) 2021 LSPosed Contributors
|
||||
-->
|
||||
|
||||
|
|
@ -84,6 +83,8 @@
|
|||
<string name="menuReload">Reload</string>
|
||||
<string name="logs_clear_failed_2">Failed to clear the log</string>
|
||||
<string name="menu_enable_word_wrap">Word Wrap</string>
|
||||
<string name="enabled_verbose_log">Verbose log enabled</string>
|
||||
<string name="disabled_verbose_log">Verbose log disabled</string>
|
||||
|
||||
<!-- Notification -->
|
||||
<string name="module_is_not_activated_yet">Xposed module is not activated yet</string>
|
||||
|
|
@ -95,7 +96,6 @@
|
|||
|
||||
<!-- ModulesActivity -->
|
||||
<string name="module_empty_description">(no description provided)</string>
|
||||
<string name="module_no_ui">This module does not provide a user interface</string>
|
||||
<string name="warning_xposed_min_version">This module requires a newer Xposed version (%d) and thus cannot be activated</string>
|
||||
<string name="no_min_version_specified">This module does not specify the Xposed version it needs.</string>
|
||||
<string name="warning_min_version_too_low">This module was created for Xposed version %1$d, but due to incompatible changes in version %2$d, it has been disabled</string>
|
||||
|
|
@ -234,4 +234,5 @@
|
|||
<string name="color_brown">Brown</string>
|
||||
<string name="color_grey">Grey</string>
|
||||
<string name="color_blue_grey">Blue grey</string>
|
||||
<string name="feedback_or_suggestion">Feedback or suggestion</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -21,4 +21,14 @@
|
|||
<resources>
|
||||
|
||||
<style name="AppTheme" parent="Theme.Light" />
|
||||
|
||||
<!-- SubtitleCollapsingToolbarLayout styles -->
|
||||
<style name="Widget.Design.SubtitleCollapsingToolbar" parent="Widget.Design.CollapsingToolbar" />
|
||||
|
||||
<style name="TextAppearance.Design.SubtitleCollapsingToolbar.ExpandedTitle" parent="TextAppearance.Design.CollapsingToolbar.Expanded" />
|
||||
|
||||
<style name="TextAppearance.Design.SubtitleCollapsingToolbar.ExpandedSubtitle" parent="TextAppearance.AppCompat.Title">
|
||||
<item name="android:textColor">?android:attr/textColorSecondary</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
||||
|
|
|
|||
Loading…
Reference in New Issue