[app] Add update available to repo list (#1011)
* [app] Fix possible crashes * [app] Add update available to repo list * [app] Fix scrollbar
This commit is contained in:
parent
48c642e778
commit
5cf522b656
|
|
@ -35,14 +35,12 @@ import android.net.Uri;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.HandlerThread;
|
import android.os.HandlerThread;
|
||||||
import android.os.Message;
|
|
||||||
import android.text.Spannable;
|
import android.text.Spannable;
|
||||||
import android.text.SpannableStringBuilder;
|
import android.text.SpannableStringBuilder;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.text.style.ForegroundColorSpan;
|
import android.text.style.ForegroundColorSpan;
|
||||||
import android.text.style.StyleSpan;
|
import android.text.style.StyleSpan;
|
||||||
import android.text.style.TypefaceSpan;
|
import android.text.style.TypefaceSpan;
|
||||||
import android.util.Log;
|
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
@ -91,12 +89,13 @@ import rikka.core.res.ResourcesKt;
|
||||||
import rikka.widget.switchbar.SwitchBar;
|
import rikka.widget.switchbar.SwitchBar;
|
||||||
|
|
||||||
@SuppressLint("NotifyDataSetChanged")
|
@SuppressLint("NotifyDataSetChanged")
|
||||||
public class ScopeAdapter extends RecyclerView.Adapter<ScopeAdapter.ViewHolder> implements Filterable, Handler.Callback {
|
public class ScopeAdapter extends RecyclerView.Adapter<ScopeAdapter.ViewHolder> implements Filterable {
|
||||||
|
|
||||||
private final Activity activity;
|
private final Activity activity;
|
||||||
private final AppListFragment fragment;
|
private final AppListFragment fragment;
|
||||||
private final PackageManager pm;
|
private final PackageManager pm;
|
||||||
private final SharedPreferences preferences;
|
private final SharedPreferences preferences;
|
||||||
|
private final HandlerThread handlerThread = new HandlerThread("appList");
|
||||||
private final Handler loadAppListHandler;
|
private final Handler loadAppListHandler;
|
||||||
private final ModuleUtil moduleUtil;
|
private final ModuleUtil moduleUtil;
|
||||||
|
|
||||||
|
|
@ -143,9 +142,8 @@ public class ScopeAdapter extends RecyclerView.Adapter<ScopeAdapter.ViewHolder>
|
||||||
this.activity = fragment.requireActivity();
|
this.activity = fragment.requireActivity();
|
||||||
this.module = module;
|
this.module = module;
|
||||||
moduleUtil = ModuleUtil.getInstance();
|
moduleUtil = ModuleUtil.getInstance();
|
||||||
HandlerThread handlerThread = new HandlerThread("appList");
|
|
||||||
handlerThread.start();
|
handlerThread.start();
|
||||||
loadAppListHandler = new Handler(handlerThread.getLooper(), this);
|
loadAppListHandler = new Handler(handlerThread.getLooper());
|
||||||
preferences = App.getPreferences();
|
preferences = App.getPreferences();
|
||||||
pm = activity.getPackageManager();
|
pm = activity.getPackageManager();
|
||||||
}
|
}
|
||||||
|
|
@ -269,7 +267,7 @@ public class ScopeAdapter extends RecyclerView.Adapter<ScopeAdapter.ViewHolder>
|
||||||
} else if (!AppHelper.onOptionsItemSelected(item, preferences)) {
|
} else if (!AppHelper.onOptionsItemSelected(item, preferences)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
refresh(false);
|
refresh();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -444,52 +442,27 @@ public class ScopeAdapter extends RecyclerView.Adapter<ScopeAdapter.ViewHolder>
|
||||||
return showList.size();
|
return showList.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void refresh(boolean force) {
|
public void onDestroy() {
|
||||||
|
loadAppListHandler.removeCallbacksAndMessages(null);
|
||||||
|
handlerThread.quit();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void refresh() {
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
if (refreshing) {
|
if (refreshing) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
refreshing = true;
|
refreshing = true;
|
||||||
}
|
}
|
||||||
loadAppListHandler.removeMessages(0);
|
loadAppListHandler.removeCallbacksAndMessages(null);
|
||||||
if (!force) {
|
boolean force = fragment.binding.swipeRefreshLayout.isRefreshing();
|
||||||
fragment.binding.progress.setIndeterminate(true);
|
if (!force) fragment.binding.progress.setIndeterminate(true);
|
||||||
}
|
|
||||||
enabled = moduleUtil.isModuleEnabled(module.packageName);
|
enabled = moduleUtil.isModuleEnabled(module.packageName);
|
||||||
fragment.binding.masterSwitch.setOnCheckedChangeListener(null);
|
fragment.binding.masterSwitch.setOnCheckedChangeListener(null);
|
||||||
fragment.binding.masterSwitch.setChecked(enabled);
|
fragment.binding.masterSwitch.setChecked(enabled);
|
||||||
fragment.binding.masterSwitch.setOnCheckedChangeListener(switchBarOnCheckedChangeListener);
|
fragment.binding.masterSwitch.setOnCheckedChangeListener(switchBarOnCheckedChangeListener);
|
||||||
loadAppListHandler.sendMessage(Message.obtain(loadAppListHandler, 0, force));
|
loadAppListHandler.post(() -> {
|
||||||
}
|
List<PackageInfo> appList = AppHelper.getAppList(force);
|
||||||
|
|
||||||
protected void onCheckedChange(CompoundButton buttonView, boolean isChecked, AppInfo appInfo) {
|
|
||||||
if (isChecked) {
|
|
||||||
checkedList.add(appInfo.application);
|
|
||||||
} else {
|
|
||||||
checkedList.remove(appInfo.application);
|
|
||||||
}
|
|
||||||
if (!ConfigManager.setModuleScope(module.packageName, checkedList)) {
|
|
||||||
Snackbar.make(fragment.binding.snackbar, R.string.failed_to_save_scope_list, Snackbar.LENGTH_SHORT).show();
|
|
||||||
if (!isChecked) {
|
|
||||||
checkedList.add(appInfo.application);
|
|
||||||
} else {
|
|
||||||
checkedList.remove(appInfo.application);
|
|
||||||
}
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean handleMessage(@NonNull Message msg) {
|
|
||||||
if (msg.what != 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
List<PackageInfo> appList = AppHelper.getAppList((Boolean) msg.obj);
|
|
||||||
checkedList.clear();
|
checkedList.clear();
|
||||||
recommendedList.clear();
|
recommendedList.clear();
|
||||||
var tmpList = new ArrayList<AppInfo>();
|
var tmpList = new ArrayList<AppInfo>();
|
||||||
|
|
@ -543,12 +516,33 @@ public class ScopeAdapter extends RecyclerView.Adapter<ScopeAdapter.ViewHolder>
|
||||||
refreshing = false;
|
refreshing = false;
|
||||||
}
|
}
|
||||||
activity.runOnUiThread(dataReadyRunnable);
|
activity.runOnUiThread(dataReadyRunnable);
|
||||||
|
try {
|
||||||
dataReadyRunnable.wait();
|
dataReadyRunnable.wait();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
return true;
|
}
|
||||||
} catch (Exception e) {
|
});
|
||||||
Log.e(App.TAG, Log.getStackTraceString(e));
|
}
|
||||||
return false;
|
|
||||||
|
protected void onCheckedChange(CompoundButton buttonView, boolean isChecked, AppInfo appInfo) {
|
||||||
|
if (isChecked) {
|
||||||
|
checkedList.add(appInfo.application);
|
||||||
|
} else {
|
||||||
|
checkedList.remove(appInfo.application);
|
||||||
|
}
|
||||||
|
if (!ConfigManager.setModuleScope(module.packageName, checkedList)) {
|
||||||
|
Snackbar.make(fragment.binding.snackbar, R.string.failed_to_save_scope_list, Snackbar.LENGTH_SHORT).show();
|
||||||
|
if (!isChecked) {
|
||||||
|
checkedList.add(appInfo.application);
|
||||||
|
} else {
|
||||||
|
checkedList.remove(appInfo.application);
|
||||||
|
}
|
||||||
|
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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -609,13 +603,13 @@ public class ScopeAdapter extends RecyclerView.Adapter<ScopeAdapter.ViewHolder>
|
||||||
return new SearchView.OnQueryTextListener() {
|
return new SearchView.OnQueryTextListener() {
|
||||||
@Override
|
@Override
|
||||||
public boolean onQueryTextSubmit(String query) {
|
public boolean onQueryTextSubmit(String query) {
|
||||||
refresh(false);
|
refresh();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onQueryTextChange(String newText) {
|
public boolean onQueryTextChange(String newText) {
|
||||||
refresh(false);
|
refresh();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@
|
||||||
package org.lsposed.manager.repo;
|
package org.lsposed.manager.repo;
|
||||||
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
import android.util.Pair;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
|
@ -39,6 +40,7 @@ import java.util.Collection;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.CopyOnWriteArrayList;
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
|
|
||||||
import okhttp3.Call;
|
import okhttp3.Call;
|
||||||
|
|
@ -50,6 +52,7 @@ import okhttp3.ResponseBody;
|
||||||
public class RepoLoader {
|
public class RepoLoader {
|
||||||
private static RepoLoader instance = null;
|
private static RepoLoader instance = null;
|
||||||
private Map<String, OnlineModule> onlineModules = new HashMap<>();
|
private Map<String, OnlineModule> onlineModules = new HashMap<>();
|
||||||
|
private final Map<String, Pair<Integer, String>> latestVersion = new ConcurrentHashMap<>();
|
||||||
private final Path repoFile = Paths.get(App.getInstance().getFilesDir().getAbsolutePath(), "repo.json");
|
private final Path repoFile = Paths.get(App.getInstance().getFilesDir().getAbsolutePath(), "repo.json");
|
||||||
private final List<Listener> listeners = new CopyOnWriteArrayList<>();
|
private final List<Listener> listeners = new CopyOnWriteArrayList<>();
|
||||||
private boolean isLoading = false;
|
private boolean isLoading = false;
|
||||||
|
|
@ -106,6 +109,25 @@ public class RepoLoader {
|
||||||
Map<String, OnlineModule> modules = new HashMap<>();
|
Map<String, OnlineModule> modules = new HashMap<>();
|
||||||
OnlineModule[] repoModules = gson.fromJson(bodyString, OnlineModule[].class);
|
OnlineModule[] repoModules = gson.fromJson(bodyString, OnlineModule[].class);
|
||||||
Arrays.stream(repoModules).forEach(onlineModule -> modules.put(onlineModule.getName(), onlineModule));
|
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;
|
||||||
|
int verCode;
|
||||||
|
String verName;
|
||||||
|
try {
|
||||||
|
verCode = Integer.parseInt(splits[0]);
|
||||||
|
verName = splits[1];
|
||||||
|
} catch (NumberFormatException ignored) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
String pkgName = module.getName();
|
||||||
|
latestVersion.put(pkgName, new Pair<>(verCode, verName));
|
||||||
|
}
|
||||||
|
|
||||||
onlineModules = modules;
|
onlineModules = modules;
|
||||||
Files.write(repoFile, bodyString.getBytes(StandardCharsets.UTF_8));
|
Files.write(repoFile, bodyString.getBytes(StandardCharsets.UTF_8));
|
||||||
for (Listener listener : listeners) {
|
for (Listener listener : listeners) {
|
||||||
|
|
@ -129,6 +151,10 @@ public class RepoLoader {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Pair<Integer, String> getModuleLatestVersion(String packageName) {
|
||||||
|
return latestVersion.get(packageName);
|
||||||
|
}
|
||||||
|
|
||||||
public void loadRemoteReleases(String packageName) {
|
public void loadRemoteReleases(String packageName) {
|
||||||
App.getOkHttpClient().newCall(new Request.Builder()
|
App.getOkHttpClient().newCall(new Request.Builder()
|
||||||
.url(String.format(repoUrl + "module/%s.json", packageName))
|
.url(String.format(repoUrl + "module/%s.json", packageName))
|
||||||
|
|
|
||||||
|
|
@ -81,7 +81,7 @@ public class AppListFragment extends BaseFragment {
|
||||||
binding.recyclerView.setHasFixedSize(true);
|
binding.recyclerView.setHasFixedSize(true);
|
||||||
binding.recyclerView.setLayoutManager(new LinearLayoutManager(requireActivity()));
|
binding.recyclerView.setLayoutManager(new LinearLayoutManager(requireActivity()));
|
||||||
RecyclerViewKt.fixEdgeEffect(binding.recyclerView, false, true);
|
RecyclerViewKt.fixEdgeEffect(binding.recyclerView, false, true);
|
||||||
binding.swipeRefreshLayout.setOnRefreshListener(() -> scopeAdapter.refresh(true));
|
binding.swipeRefreshLayout.setOnRefreshListener(() -> scopeAdapter.refresh());
|
||||||
|
|
||||||
searchListener = scopeAdapter.getSearchListener();
|
searchListener = scopeAdapter.getSearchListener();
|
||||||
|
|
||||||
|
|
@ -150,7 +150,14 @@ public class AppListFragment extends BaseFragment {
|
||||||
@Override
|
@Override
|
||||||
public void onResume() {
|
public void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
scopeAdapter.refresh(false);
|
scopeAdapter.refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroy() {
|
||||||
|
scopeAdapter.onDestroy();
|
||||||
|
|
||||||
|
super.onDestroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@
|
||||||
|
|
||||||
package org.lsposed.manager.ui.fragment;
|
package org.lsposed.manager.ui.fragment;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
import androidx.appcompat.widget.Toolbar;
|
import androidx.appcompat.widget.Toolbar;
|
||||||
|
|
@ -66,4 +67,11 @@ public class BaseFragment extends Fragment {
|
||||||
public Future<?> runAsync(Runnable runnable) {
|
public Future<?> runAsync(Runnable runnable) {
|
||||||
return App.getExecutorService().submit(runnable);
|
return App.getExecutorService().submit(runnable);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void runOnUiThread(Runnable runnable) {
|
||||||
|
Activity activity = getActivity();
|
||||||
|
if (activity != null && !activity.isFinishing()) {
|
||||||
|
activity.runOnUiThread(runnable);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -280,13 +280,19 @@ public class LogsFragment extends BaseFragment {
|
||||||
protected void onPostExecute(List<String> logs) {
|
protected void onPostExecute(List<String> logs) {
|
||||||
adapter.setLogs(logs);
|
adapter.setLogs(logs);
|
||||||
|
|
||||||
handler.removeCallbacks(mRunnable);//It loaded so fast that no need to show progress
|
handler.removeCallbacks(mRunnable);
|
||||||
if (mProgressDialog.isShowing()) {
|
if (mProgressDialog.isShowing()) {
|
||||||
mProgressDialog.dismiss();
|
mProgressDialog.dismiss();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroy() {
|
||||||
|
handler.removeCallbacksAndMessages(null);
|
||||||
|
super.onDestroy();
|
||||||
|
}
|
||||||
|
|
||||||
private class LogsAdapter extends RecyclerView.Adapter<LogsAdapter.ViewHolder> {
|
private class LogsAdapter extends RecyclerView.Adapter<LogsAdapter.ViewHolder> {
|
||||||
ArrayList<String> logs = new ArrayList<>();
|
ArrayList<String> logs = new ArrayList<>();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -32,15 +32,12 @@ import android.graphics.drawable.Drawable;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Handler;
|
|
||||||
import android.os.HandlerThread;
|
|
||||||
import android.text.Spannable;
|
import android.text.Spannable;
|
||||||
import android.text.SpannableStringBuilder;
|
import android.text.SpannableStringBuilder;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.text.style.ForegroundColorSpan;
|
import android.text.style.ForegroundColorSpan;
|
||||||
import android.text.style.StyleSpan;
|
import android.text.style.StyleSpan;
|
||||||
import android.text.style.TypefaceSpan;
|
import android.text.style.TypefaceSpan;
|
||||||
import android.util.Pair;
|
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
|
|
@ -59,7 +56,6 @@ import androidx.appcompat.widget.SearchView;
|
||||||
import androidx.constraintlayout.widget.ConstraintLayout;
|
import androidx.constraintlayout.widget.ConstraintLayout;
|
||||||
import androidx.core.content.ContextCompat;
|
import androidx.core.content.ContextCompat;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import androidx.lifecycle.Lifecycle;
|
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
import androidx.viewpager2.adapter.FragmentStateAdapter;
|
import androidx.viewpager2.adapter.FragmentStateAdapter;
|
||||||
|
|
@ -89,8 +85,6 @@ import org.lsposed.manager.util.ModuleUtil;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
@ -101,8 +95,7 @@ import rikka.insets.WindowInsetsHelperKt;
|
||||||
import rikka.recyclerview.RecyclerViewKt;
|
import rikka.recyclerview.RecyclerViewKt;
|
||||||
import rikka.widget.borderview.BorderRecyclerView;
|
import rikka.widget.borderview.BorderRecyclerView;
|
||||||
|
|
||||||
public class ModulesFragment extends BaseFragment implements ModuleUtil.ModuleListener, RepoLoader.Listener {
|
public class ModulesFragment extends BaseFragment implements ModuleUtil.ModuleListener {
|
||||||
private static final Handler workHandler;
|
|
||||||
private static final PackageManager pm = App.getInstance().getPackageManager();
|
private static final PackageManager pm = App.getInstance().getPackageManager();
|
||||||
private static final ModuleUtil moduleUtil = ModuleUtil.getInstance();
|
private static final ModuleUtil moduleUtil = ModuleUtil.getInstance();
|
||||||
private static final RepoLoader repoLoader = RepoLoader.getInstance();
|
private static final RepoLoader repoLoader = RepoLoader.getInstance();
|
||||||
|
|
@ -113,16 +106,8 @@ public class ModulesFragment extends BaseFragment implements ModuleUtil.ModuleLi
|
||||||
private final ArrayList<ModuleAdapter> adapters = new ArrayList<>();
|
private final ArrayList<ModuleAdapter> adapters = new ArrayList<>();
|
||||||
private final ArrayList<String> tabTitles = new ArrayList<>();
|
private final ArrayList<String> tabTitles = new ArrayList<>();
|
||||||
|
|
||||||
private final Map<String, Pair<Integer, String>> latestVersion = new ConcurrentHashMap<>();
|
|
||||||
|
|
||||||
private ModuleUtil.InstalledModule selectedModule;
|
private ModuleUtil.InstalledModule selectedModule;
|
||||||
|
|
||||||
static {
|
|
||||||
HandlerThread workThread = new HandlerThread("ModulesActivity WorkHandler");
|
|
||||||
workThread.start();
|
|
||||||
workHandler = new Handler(workThread.getLooper());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
@ -142,18 +127,9 @@ public class ModulesFragment extends BaseFragment implements ModuleUtil.ModuleLi
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAttach(@NonNull Context context) {
|
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||||
super.onAttach(context);
|
super.onViewCreated(view, savedInstanceState);
|
||||||
moduleUtil.addListener(this);
|
moduleUtil.addListener(this);
|
||||||
repoLoader.addListener(this);
|
|
||||||
repoLoaded();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDetach() {
|
|
||||||
moduleUtil.removeListener(this);
|
|
||||||
repoLoader.removeListener(this);
|
|
||||||
super.onDetach();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
|
|
@ -281,16 +257,16 @@ public class ModulesFragment extends BaseFragment implements ModuleUtil.ModuleLi
|
||||||
.setTitle(getString(R.string.install_to_user, user.name))
|
.setTitle(getString(R.string.install_to_user, user.name))
|
||||||
.setMessage(getString(R.string.install_to_user_message, module.getAppName(), user.name))
|
.setMessage(getString(R.string.install_to_user_message, module.getAppName(), user.name))
|
||||||
.setPositiveButton(android.R.string.ok, (dialog, which) ->
|
.setPositiveButton(android.R.string.ok, (dialog, which) ->
|
||||||
workHandler.post(() -> {
|
runAsync(() -> {
|
||||||
var success = ConfigManager.installExistingPackageAsUser(module.packageName, user.id);
|
var success = ConfigManager.installExistingPackageAsUser(module.packageName, user.id);
|
||||||
requireActivity().runOnUiThread(() -> {
|
String text = success ?
|
||||||
String text = success ? getString(R.string.module_installed, module.getAppName(), user.name) : getString(R.string.module_install_failed);
|
getString(R.string.module_installed, module.getAppName(), user.name) :
|
||||||
if (getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.RESUMED)) {
|
getString(R.string.module_install_failed);
|
||||||
Snackbar.make(binding.snackbar, text, Snackbar.LENGTH_SHORT).show();
|
if (binding != null && isResumed()) {
|
||||||
|
Snackbar.make(binding.snackbar, text, Snackbar.LENGTH_LONG).show();
|
||||||
} else {
|
} else {
|
||||||
Toast.makeText(requireActivity(), text, Toast.LENGTH_SHORT).show();
|
Toast.makeText(App.getInstance(), text, Toast.LENGTH_LONG).show();
|
||||||
}
|
}
|
||||||
});
|
|
||||||
if (success)
|
if (success)
|
||||||
moduleUtil.reloadSingleModule(module.packageName, user.id);
|
moduleUtil.reloadSingleModule(module.packageName, user.id);
|
||||||
}))
|
}))
|
||||||
|
|
@ -330,16 +306,14 @@ public class ModulesFragment extends BaseFragment implements ModuleUtil.ModuleLi
|
||||||
.setTitle(selectedModule.getAppName())
|
.setTitle(selectedModule.getAppName())
|
||||||
.setMessage(R.string.module_uninstall_message)
|
.setMessage(R.string.module_uninstall_message)
|
||||||
.setPositiveButton(android.R.string.ok, (dialog, which) ->
|
.setPositiveButton(android.R.string.ok, (dialog, which) ->
|
||||||
workHandler.post(() -> {
|
runAsync(() -> {
|
||||||
boolean success = ConfigManager.uninstallPackage(selectedModule.packageName, selectedModule.userId);
|
boolean success = ConfigManager.uninstallPackage(selectedModule.packageName, selectedModule.userId);
|
||||||
requireActivity().runOnUiThread(() -> {
|
|
||||||
String text = success ? getString(R.string.module_uninstalled, selectedModule.getAppName()) : getString(R.string.module_uninstall_failed);
|
String text = success ? getString(R.string.module_uninstalled, selectedModule.getAppName()) : getString(R.string.module_uninstall_failed);
|
||||||
if (getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.RESUMED)) {
|
if (binding != null && isResumed()) {
|
||||||
Snackbar.make(binding.snackbar, text, Snackbar.LENGTH_SHORT).show();
|
Snackbar.make(binding.snackbar, text, Snackbar.LENGTH_LONG).show();
|
||||||
} else {
|
} else {
|
||||||
Toast.makeText(requireActivity(), text, Toast.LENGTH_SHORT).show();
|
Toast.makeText(App.getInstance(), text, Toast.LENGTH_LONG).show();
|
||||||
}
|
}
|
||||||
});
|
|
||||||
if (success)
|
if (success)
|
||||||
moduleUtil.reloadSingleModule(selectedModule.packageName, selectedModule.userId);
|
moduleUtil.reloadSingleModule(selectedModule.packageName, selectedModule.userId);
|
||||||
}))
|
}))
|
||||||
|
|
@ -357,31 +331,10 @@ public class ModulesFragment extends BaseFragment implements ModuleUtil.ModuleLi
|
||||||
public void onDestroyView() {
|
public void onDestroyView() {
|
||||||
super.onDestroyView();
|
super.onDestroyView();
|
||||||
|
|
||||||
|
moduleUtil.removeListener(this);
|
||||||
binding = null;
|
binding = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
synchronized public void repoLoaded() {
|
|
||||||
latestVersion.clear();
|
|
||||||
for (var module : repoLoader.getOnlineModules()) {
|
|
||||||
var release = module.getLatestRelease();
|
|
||||||
if (release == null || release.isEmpty()) continue;
|
|
||||||
var splits = release.split("-", 2);
|
|
||||||
if (splits.length < 2) continue;
|
|
||||||
int verCode;
|
|
||||||
String verName;
|
|
||||||
try {
|
|
||||||
verCode = Integer.parseInt(splits[0]);
|
|
||||||
verName = splits[1];
|
|
||||||
} catch (NumberFormatException ignored) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
String pkgName = module.getName();
|
|
||||||
latestVersion.put(pkgName, new Pair<>(verCode, verName));
|
|
||||||
}
|
|
||||||
requireActivity().runOnUiThread(() -> adapters.forEach(ModuleAdapter::notifyDataSetChanged));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class ModuleListFragment extends Fragment {
|
public static class ModuleListFragment extends Fragment {
|
||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -529,9 +482,8 @@ public class ModulesFragment extends BaseFragment implements ModuleUtil.ModuleLi
|
||||||
}
|
}
|
||||||
sb.setSpan(foregroundColorSpan, sb.length() - warningText.length(), sb.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
|
sb.setSpan(foregroundColorSpan, sb.length() - warningText.length(), sb.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
|
||||||
}
|
}
|
||||||
|
if (repoLoader.isRepoLoaded()) {
|
||||||
if (latestVersion.containsKey(item.packageName)) {
|
var ver = repoLoader.getModuleLatestVersion(item.packageName);
|
||||||
var ver = latestVersion.get(item.packageName);
|
|
||||||
if (ver != null && ver.first > item.versionCode) {
|
if (ver != null && ver.first > item.versionCode) {
|
||||||
sb.append("\n");
|
sb.append("\n");
|
||||||
String recommended = getString(R.string.update_available, ver.second);
|
String recommended = getString(R.string.update_available, ver.second);
|
||||||
|
|
@ -630,7 +582,7 @@ public class ModulesFragment extends BaseFragment implements ModuleUtil.ModuleLi
|
||||||
|
|
||||||
public void refresh(boolean force) {
|
public void refresh(boolean force) {
|
||||||
if (force) moduleUtil.reloadInstalledModules();
|
if (force) moduleUtil.reloadInstalledModules();
|
||||||
requireActivity().runOnUiThread(reloadModules);
|
runOnUiThread(reloadModules);
|
||||||
}
|
}
|
||||||
|
|
||||||
private final Runnable reloadModules = new Runnable() {
|
private final Runnable reloadModules = new Runnable() {
|
||||||
|
|
@ -651,7 +603,7 @@ public class ModulesFragment extends BaseFragment implements ModuleUtil.ModuleLi
|
||||||
searchList.clear();
|
searchList.clear();
|
||||||
searchList.addAll(tmpList);
|
searchList.addAll(tmpList);
|
||||||
String queryStr = searchView != null ? searchView.getQuery().toString() : "";
|
String queryStr = searchView != null ? searchView.getQuery().toString() : "";
|
||||||
requireActivity().runOnUiThread(() -> getFilter().filter(queryStr));
|
runOnUiThread(() -> getFilter().filter(queryStr));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,11 +19,17 @@
|
||||||
|
|
||||||
package org.lsposed.manager.ui.fragment;
|
package org.lsposed.manager.ui.fragment;
|
||||||
|
|
||||||
|
import android.graphics.Typeface;
|
||||||
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
|
import android.text.Spannable;
|
||||||
import android.text.SpannableStringBuilder;
|
import android.text.SpannableStringBuilder;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
import android.text.style.ForegroundColorSpan;
|
||||||
|
import android.text.style.StyleSpan;
|
||||||
|
import android.text.style.TypefaceSpan;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
|
|
@ -50,6 +56,7 @@ import org.lsposed.manager.databinding.FragmentRepoBinding;
|
||||||
import org.lsposed.manager.databinding.ItemOnlinemoduleBinding;
|
import org.lsposed.manager.databinding.ItemOnlinemoduleBinding;
|
||||||
import org.lsposed.manager.repo.RepoLoader;
|
import org.lsposed.manager.repo.RepoLoader;
|
||||||
import org.lsposed.manager.repo.model.OnlineModule;
|
import org.lsposed.manager.repo.model.OnlineModule;
|
||||||
|
import org.lsposed.manager.util.ModuleUtil;
|
||||||
|
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
@ -59,6 +66,7 @@ import java.util.Comparator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import rikka.core.res.ResourcesKt;
|
||||||
import rikka.core.util.LabelComparator;
|
import rikka.core.util.LabelComparator;
|
||||||
import rikka.recyclerview.RecyclerViewKt;
|
import rikka.recyclerview.RecyclerViewKt;
|
||||||
|
|
||||||
|
|
@ -66,7 +74,7 @@ public class RepoFragment extends BaseFragment implements RepoLoader.Listener {
|
||||||
protected FragmentRepoBinding binding;
|
protected FragmentRepoBinding binding;
|
||||||
protected SearchView searchView;
|
protected SearchView searchView;
|
||||||
private SearchView.OnQueryTextListener mSearchListener;
|
private SearchView.OnQueryTextListener mSearchListener;
|
||||||
private Handler mHandler = new Handler(Looper.getMainLooper());
|
private final Handler mHandler = new Handler(Looper.getMainLooper());
|
||||||
private boolean preLoadWebview = true;
|
private boolean preLoadWebview = true;
|
||||||
|
|
||||||
private final RepoLoader repoLoader = RepoLoader.getInstance();
|
private final RepoLoader repoLoader = RepoLoader.getInstance();
|
||||||
|
|
@ -121,9 +129,12 @@ public class RepoFragment extends BaseFragment implements RepoLoader.Listener {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDestroy() {
|
public void onDestroyView() {
|
||||||
super.onDestroy();
|
super.onDestroyView();
|
||||||
|
|
||||||
|
mHandler.removeCallbacksAndMessages(null);
|
||||||
repoLoader.removeListener(this);
|
repoLoader.removeListener(this);
|
||||||
|
binding = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -138,15 +149,9 @@ public class RepoFragment extends BaseFragment implements RepoLoader.Listener {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDetach() {
|
|
||||||
mHandler.removeCallbacksAndMessages(null);
|
|
||||||
super.onDetach();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void repoLoaded() {
|
public void repoLoaded() {
|
||||||
requireActivity().runOnUiThread(() -> {
|
runOnUiThread(() -> {
|
||||||
binding.progress.hide();
|
binding.progress.hide();
|
||||||
adapter.setData(repoLoader.getOnlineModules());
|
adapter.setData(repoLoader.getOnlineModules());
|
||||||
});
|
});
|
||||||
|
|
@ -201,6 +206,24 @@ public class RepoFragment extends BaseFragment implements RepoLoader.Listener {
|
||||||
sb.append("\n");
|
sb.append("\n");
|
||||||
sb.append(summary);
|
sb.append(summary);
|
||||||
}
|
}
|
||||||
|
ModuleUtil.InstalledModule installedModule = ModuleUtil.getInstance().getModule(module.getName());
|
||||||
|
if (installedModule != null) {
|
||||||
|
var ver = repoLoader.getModuleLatestVersion(installedModule.packageName);
|
||||||
|
if (ver != null && ver.first > installedModule.versionCode) {
|
||||||
|
sb.append("\n");
|
||||||
|
String recommended = getString(R.string.update_available, ver.second);
|
||||||
|
sb.append(recommended);
|
||||||
|
final ForegroundColorSpan foregroundColorSpan = new ForegroundColorSpan(ResourcesKt.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);
|
||||||
|
}
|
||||||
|
}
|
||||||
holder.appDescription.setText(sb);
|
holder.appDescription.setText(sb);
|
||||||
holder.itemView.setOnClickListener(v -> {
|
holder.itemView.setOnClickListener(v -> {
|
||||||
searchView.clearFocus();
|
searchView.clearFocus();
|
||||||
|
|
|
||||||
|
|
@ -204,8 +204,8 @@ public class RepoItemFragment extends BaseFragment implements RepoLoader.Listene
|
||||||
public void moduleReleasesLoaded(OnlineModule module) {
|
public void moduleReleasesLoaded(OnlineModule module) {
|
||||||
this.module = module;
|
this.module = module;
|
||||||
if (releaseAdapter != null) {
|
if (releaseAdapter != null) {
|
||||||
requireActivity().runOnUiThread(() -> releaseAdapter.loadItems());
|
runOnUiThread(() -> releaseAdapter.loadItems());
|
||||||
if (module.getReleases().size() == 1) {
|
if (isResumed() && module.getReleases().size() == 1) {
|
||||||
Snackbar.make(binding.snackbar, R.string.module_release_no_more, Snackbar.LENGTH_SHORT).show();
|
Snackbar.make(binding.snackbar, R.string.module_release_no_more, Snackbar.LENGTH_SHORT).show();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -214,23 +214,18 @@ public class RepoItemFragment extends BaseFragment implements RepoLoader.Listene
|
||||||
@Override
|
@Override
|
||||||
public void onThrowable(Throwable t) {
|
public void onThrowable(Throwable t) {
|
||||||
if (releaseAdapter != null) {
|
if (releaseAdapter != null) {
|
||||||
requireActivity().runOnUiThread(() -> releaseAdapter.loadItems());
|
runOnUiThread(() -> releaseAdapter.loadItems());
|
||||||
}
|
if (isResumed()) {
|
||||||
if (getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.RESUMED)) {
|
|
||||||
Snackbar.make(binding.snackbar, getString(R.string.repo_load_failed, t.getLocalizedMessage()), Snackbar.LENGTH_SHORT).show();
|
Snackbar.make(binding.snackbar, getString(R.string.repo_load_failed, t.getLocalizedMessage()), Snackbar.LENGTH_SHORT).show();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDestroy() {
|
|
||||||
super.onDestroy();
|
|
||||||
RepoLoader.getInstance().removeListener(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDestroyView() {
|
public void onDestroyView() {
|
||||||
super.onDestroyView();
|
super.onDestroyView();
|
||||||
|
|
||||||
|
RepoLoader.getInstance().removeListener(this);
|
||||||
binding = null;
|
binding = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -82,7 +82,7 @@
|
||||||
android:clipToPadding="false"
|
android:clipToPadding="false"
|
||||||
android:fadeScrollbars="true"
|
android:fadeScrollbars="true"
|
||||||
android:paddingTop="104dp"
|
android:paddingTop="104dp"
|
||||||
android:scrollbarStyle="outsideOverlay"
|
android:scrollbarStyle="insideOverlay"
|
||||||
android:scrollbars="vertical"
|
android:scrollbars="vertical"
|
||||||
app:borderTopVisibility="whenTop"
|
app:borderTopVisibility="whenTop"
|
||||||
app:borderTopDrawable="@null"
|
app:borderTopDrawable="@null"
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ buildscript {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath("com.android.tools.build:gradle:7.1.0-alpha09")
|
classpath("com.android.tools.build:gradle:7.1.0-alpha10")
|
||||||
classpath("org.eclipse.jgit:org.eclipse.jgit:5.12.0.202106070339-r")
|
classpath("org.eclipse.jgit:org.eclipse.jgit:5.12.0.202106070339-r")
|
||||||
classpath("androidx.navigation:navigation-safe-args-gradle-plugin:2.4.0-alpha07")
|
classpath("androidx.navigation:navigation-safe-args-gradle-plugin:2.4.0-alpha07")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue