[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.Handler;
|
||||
import android.os.HandlerThread;
|
||||
import android.os.Message;
|
||||
import android.text.Spannable;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.TextUtils;
|
||||
import android.text.style.ForegroundColorSpan;
|
||||
import android.text.style.StyleSpan;
|
||||
import android.text.style.TypefaceSpan;
|
||||
import android.util.Log;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
|
|
@ -91,12 +89,13 @@ import rikka.core.res.ResourcesKt;
|
|||
import rikka.widget.switchbar.SwitchBar;
|
||||
|
||||
@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 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;
|
||||
|
||||
|
|
@ -143,9 +142,8 @@ public class ScopeAdapter extends RecyclerView.Adapter<ScopeAdapter.ViewHolder>
|
|||
this.activity = fragment.requireActivity();
|
||||
this.module = module;
|
||||
moduleUtil = ModuleUtil.getInstance();
|
||||
HandlerThread handlerThread = new HandlerThread("appList");
|
||||
handlerThread.start();
|
||||
loadAppListHandler = new Handler(handlerThread.getLooper(), this);
|
||||
loadAppListHandler = new Handler(handlerThread.getLooper());
|
||||
preferences = App.getPreferences();
|
||||
pm = activity.getPackageManager();
|
||||
}
|
||||
|
|
@ -269,7 +267,7 @@ public class ScopeAdapter extends RecyclerView.Adapter<ScopeAdapter.ViewHolder>
|
|||
} else if (!AppHelper.onOptionsItemSelected(item, preferences)) {
|
||||
return false;
|
||||
}
|
||||
refresh(false);
|
||||
refresh();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -444,52 +442,27 @@ public class ScopeAdapter extends RecyclerView.Adapter<ScopeAdapter.ViewHolder>
|
|||
return showList.size();
|
||||
}
|
||||
|
||||
public void refresh(boolean force) {
|
||||
public void onDestroy() {
|
||||
loadAppListHandler.removeCallbacksAndMessages(null);
|
||||
handlerThread.quit();
|
||||
}
|
||||
|
||||
public void refresh() {
|
||||
synchronized (this) {
|
||||
if (refreshing) {
|
||||
return;
|
||||
}
|
||||
refreshing = true;
|
||||
}
|
||||
loadAppListHandler.removeMessages(0);
|
||||
if (!force) {
|
||||
fragment.binding.progress.setIndeterminate(true);
|
||||
}
|
||||
loadAppListHandler.removeCallbacksAndMessages(null);
|
||||
boolean force = fragment.binding.swipeRefreshLayout.isRefreshing();
|
||||
if (!force) fragment.binding.progress.setIndeterminate(true);
|
||||
enabled = moduleUtil.isModuleEnabled(module.packageName);
|
||||
fragment.binding.masterSwitch.setOnCheckedChangeListener(null);
|
||||
fragment.binding.masterSwitch.setChecked(enabled);
|
||||
fragment.binding.masterSwitch.setOnCheckedChangeListener(switchBarOnCheckedChangeListener);
|
||||
loadAppListHandler.sendMessage(Message.obtain(loadAppListHandler, 0, 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);
|
||||
loadAppListHandler.post(() -> {
|
||||
List<PackageInfo> appList = AppHelper.getAppList(force);
|
||||
checkedList.clear();
|
||||
recommendedList.clear();
|
||||
var tmpList = new ArrayList<AppInfo>();
|
||||
|
|
@ -543,12 +516,33 @@ public class ScopeAdapter extends RecyclerView.Adapter<ScopeAdapter.ViewHolder>
|
|||
refreshing = false;
|
||||
}
|
||||
activity.runOnUiThread(dataReadyRunnable);
|
||||
dataReadyRunnable.wait();
|
||||
try {
|
||||
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() {
|
||||
@Override
|
||||
public boolean onQueryTextSubmit(String query) {
|
||||
refresh(false);
|
||||
refresh();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onQueryTextChange(String newText) {
|
||||
refresh(false);
|
||||
refresh();
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@
|
|||
package org.lsposed.manager.repo;
|
||||
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
|
|
@ -39,6 +40,7 @@ import java.util.Collection;
|
|||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
import okhttp3.Call;
|
||||
|
|
@ -50,6 +52,7 @@ import okhttp3.ResponseBody;
|
|||
public class RepoLoader {
|
||||
private static RepoLoader instance = null;
|
||||
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 List<Listener> listeners = new CopyOnWriteArrayList<>();
|
||||
private boolean isLoading = false;
|
||||
|
|
@ -106,6 +109,25 @@ public class RepoLoader {
|
|||
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;
|
||||
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;
|
||||
Files.write(repoFile, bodyString.getBytes(StandardCharsets.UTF_8));
|
||||
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) {
|
||||
App.getOkHttpClient().newCall(new Request.Builder()
|
||||
.url(String.format(repoUrl + "module/%s.json", packageName))
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ public class AppListFragment extends BaseFragment {
|
|||
binding.recyclerView.setHasFixedSize(true);
|
||||
binding.recyclerView.setLayoutManager(new LinearLayoutManager(requireActivity()));
|
||||
RecyclerViewKt.fixEdgeEffect(binding.recyclerView, false, true);
|
||||
binding.swipeRefreshLayout.setOnRefreshListener(() -> scopeAdapter.refresh(true));
|
||||
binding.swipeRefreshLayout.setOnRefreshListener(() -> scopeAdapter.refresh());
|
||||
|
||||
searchListener = scopeAdapter.getSearchListener();
|
||||
|
||||
|
|
@ -150,7 +150,14 @@ public class AppListFragment extends BaseFragment {
|
|||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
scopeAdapter.refresh(false);
|
||||
scopeAdapter.refresh();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
scopeAdapter.onDestroy();
|
||||
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
|
||||
package org.lsposed.manager.ui.fragment;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
|
|
@ -66,4 +67,11 @@ public class BaseFragment extends Fragment {
|
|||
public Future<?> runAsync(Runnable 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) {
|
||||
adapter.setLogs(logs);
|
||||
|
||||
handler.removeCallbacks(mRunnable);//It loaded so fast that no need to show progress
|
||||
handler.removeCallbacks(mRunnable);
|
||||
if (mProgressDialog.isShowing()) {
|
||||
mProgressDialog.dismiss();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
handler.removeCallbacksAndMessages(null);
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
private class LogsAdapter extends RecyclerView.Adapter<LogsAdapter.ViewHolder> {
|
||||
ArrayList<String> logs = new ArrayList<>();
|
||||
|
||||
|
|
|
|||
|
|
@ -32,15 +32,12 @@ import android.graphics.drawable.Drawable;
|
|||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.HandlerThread;
|
||||
import android.text.Spannable;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.TextUtils;
|
||||
import android.text.style.ForegroundColorSpan;
|
||||
import android.text.style.StyleSpan;
|
||||
import android.text.style.TypefaceSpan;
|
||||
import android.util.Pair;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
|
|
@ -59,7 +56,6 @@ import androidx.appcompat.widget.SearchView;
|
|||
import androidx.constraintlayout.widget.ConstraintLayout;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.lifecycle.Lifecycle;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.viewpager2.adapter.FragmentStateAdapter;
|
||||
|
|
@ -89,8 +85,6 @@ import org.lsposed.manager.util.ModuleUtil;
|
|||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
|
@ -101,8 +95,7 @@ import rikka.insets.WindowInsetsHelperKt;
|
|||
import rikka.recyclerview.RecyclerViewKt;
|
||||
import rikka.widget.borderview.BorderRecyclerView;
|
||||
|
||||
public class ModulesFragment extends BaseFragment implements ModuleUtil.ModuleListener, RepoLoader.Listener {
|
||||
private static final Handler workHandler;
|
||||
public class ModulesFragment extends BaseFragment implements ModuleUtil.ModuleListener {
|
||||
private static final PackageManager pm = App.getInstance().getPackageManager();
|
||||
private static final ModuleUtil moduleUtil = ModuleUtil.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<String> tabTitles = new ArrayList<>();
|
||||
|
||||
private final Map<String, Pair<Integer, String>> latestVersion = new ConcurrentHashMap<>();
|
||||
|
||||
private ModuleUtil.InstalledModule selectedModule;
|
||||
|
||||
static {
|
||||
HandlerThread workThread = new HandlerThread("ModulesActivity WorkHandler");
|
||||
workThread.start();
|
||||
workHandler = new Handler(workThread.getLooper());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
|
@ -142,18 +127,9 @@ public class ModulesFragment extends BaseFragment implements ModuleUtil.ModuleLi
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(@NonNull Context context) {
|
||||
super.onAttach(context);
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
moduleUtil.addListener(this);
|
||||
repoLoader.addListener(this);
|
||||
repoLoaded();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetach() {
|
||||
moduleUtil.removeListener(this);
|
||||
repoLoader.removeListener(this);
|
||||
super.onDetach();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
|
|
@ -281,16 +257,16 @@ public class ModulesFragment extends BaseFragment implements ModuleUtil.ModuleLi
|
|||
.setTitle(getString(R.string.install_to_user, user.name))
|
||||
.setMessage(getString(R.string.install_to_user_message, module.getAppName(), user.name))
|
||||
.setPositiveButton(android.R.string.ok, (dialog, which) ->
|
||||
workHandler.post(() -> {
|
||||
runAsync(() -> {
|
||||
var success = ConfigManager.installExistingPackageAsUser(module.packageName, user.id);
|
||||
requireActivity().runOnUiThread(() -> {
|
||||
String text = success ? getString(R.string.module_installed, module.getAppName(), user.name) : getString(R.string.module_install_failed);
|
||||
if (getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.RESUMED)) {
|
||||
Snackbar.make(binding.snackbar, text, Snackbar.LENGTH_SHORT).show();
|
||||
} else {
|
||||
Toast.makeText(requireActivity(), text, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
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();
|
||||
}
|
||||
if (success)
|
||||
moduleUtil.reloadSingleModule(module.packageName, user.id);
|
||||
}))
|
||||
|
|
@ -330,16 +306,14 @@ public class ModulesFragment extends BaseFragment implements ModuleUtil.ModuleLi
|
|||
.setTitle(selectedModule.getAppName())
|
||||
.setMessage(R.string.module_uninstall_message)
|
||||
.setPositiveButton(android.R.string.ok, (dialog, which) ->
|
||||
workHandler.post(() -> {
|
||||
runAsync(() -> {
|
||||
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);
|
||||
if (getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.RESUMED)) {
|
||||
Snackbar.make(binding.snackbar, text, Snackbar.LENGTH_SHORT).show();
|
||||
} else {
|
||||
Toast.makeText(requireActivity(), text, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
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();
|
||||
}
|
||||
if (success)
|
||||
moduleUtil.reloadSingleModule(selectedModule.packageName, selectedModule.userId);
|
||||
}))
|
||||
|
|
@ -357,31 +331,10 @@ public class ModulesFragment extends BaseFragment implements ModuleUtil.ModuleLi
|
|||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
|
||||
moduleUtil.removeListener(this);
|
||||
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 {
|
||||
@Nullable
|
||||
@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);
|
||||
}
|
||||
|
||||
if (latestVersion.containsKey(item.packageName)) {
|
||||
var ver = latestVersion.get(item.packageName);
|
||||
if (repoLoader.isRepoLoaded()) {
|
||||
var ver = repoLoader.getModuleLatestVersion(item.packageName);
|
||||
if (ver != null && ver.first > item.versionCode) {
|
||||
sb.append("\n");
|
||||
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) {
|
||||
if (force) moduleUtil.reloadInstalledModules();
|
||||
requireActivity().runOnUiThread(reloadModules);
|
||||
runOnUiThread(reloadModules);
|
||||
}
|
||||
|
||||
private final Runnable reloadModules = new Runnable() {
|
||||
|
|
@ -651,7 +603,7 @@ public class ModulesFragment extends BaseFragment implements ModuleUtil.ModuleLi
|
|||
searchList.clear();
|
||||
searchList.addAll(tmpList);
|
||||
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;
|
||||
|
||||
import android.graphics.Typeface;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.text.Spannable;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.TextUtils;
|
||||
import android.text.style.ForegroundColorSpan;
|
||||
import android.text.style.StyleSpan;
|
||||
import android.text.style.TypefaceSpan;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
|
|
@ -50,6 +56,7 @@ 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.util.ModuleUtil;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
|
|
@ -59,6 +66,7 @@ import java.util.Comparator;
|
|||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import rikka.core.res.ResourcesKt;
|
||||
import rikka.core.util.LabelComparator;
|
||||
import rikka.recyclerview.RecyclerViewKt;
|
||||
|
||||
|
|
@ -66,7 +74,7 @@ public class RepoFragment extends BaseFragment implements RepoLoader.Listener {
|
|||
protected FragmentRepoBinding binding;
|
||||
protected SearchView searchView;
|
||||
private SearchView.OnQueryTextListener mSearchListener;
|
||||
private Handler mHandler = new Handler(Looper.getMainLooper());
|
||||
private final Handler mHandler = new Handler(Looper.getMainLooper());
|
||||
private boolean preLoadWebview = true;
|
||||
|
||||
private final RepoLoader repoLoader = RepoLoader.getInstance();
|
||||
|
|
@ -121,9 +129,12 @@ public class RepoFragment extends BaseFragment implements RepoLoader.Listener {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
|
||||
mHandler.removeCallbacksAndMessages(null);
|
||||
repoLoader.removeListener(this);
|
||||
binding = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -138,15 +149,9 @@ public class RepoFragment extends BaseFragment implements RepoLoader.Listener {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetach() {
|
||||
mHandler.removeCallbacksAndMessages(null);
|
||||
super.onDetach();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void repoLoaded() {
|
||||
requireActivity().runOnUiThread(() -> {
|
||||
runOnUiThread(() -> {
|
||||
binding.progress.hide();
|
||||
adapter.setData(repoLoader.getOnlineModules());
|
||||
});
|
||||
|
|
@ -201,6 +206,24 @@ public class RepoFragment extends BaseFragment implements RepoLoader.Listener {
|
|||
sb.append("\n");
|
||||
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.itemView.setOnClickListener(v -> {
|
||||
searchView.clearFocus();
|
||||
|
|
|
|||
|
|
@ -204,8 +204,8 @@ public class RepoItemFragment extends BaseFragment implements RepoLoader.Listene
|
|||
public void moduleReleasesLoaded(OnlineModule module) {
|
||||
this.module = module;
|
||||
if (releaseAdapter != null) {
|
||||
requireActivity().runOnUiThread(() -> releaseAdapter.loadItems());
|
||||
if (module.getReleases().size() == 1) {
|
||||
runOnUiThread(() -> releaseAdapter.loadItems());
|
||||
if (isResumed() && module.getReleases().size() == 1) {
|
||||
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
|
||||
public void onThrowable(Throwable t) {
|
||||
if (releaseAdapter != null) {
|
||||
requireActivity().runOnUiThread(() -> releaseAdapter.loadItems());
|
||||
runOnUiThread(() -> releaseAdapter.loadItems());
|
||||
if (isResumed()) {
|
||||
Snackbar.make(binding.snackbar, getString(R.string.repo_load_failed, t.getLocalizedMessage()), Snackbar.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
if (getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.RESUMED)) {
|
||||
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
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
|
||||
RepoLoader.getInstance().removeListener(this);
|
||||
binding = null;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@
|
|||
android:clipToPadding="false"
|
||||
android:fadeScrollbars="true"
|
||||
android:paddingTop="104dp"
|
||||
android:scrollbarStyle="outsideOverlay"
|
||||
android:scrollbarStyle="insideOverlay"
|
||||
android:scrollbars="vertical"
|
||||
app:borderTopVisibility="whenTop"
|
||||
app:borderTopDrawable="@null"
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ buildscript {
|
|||
mavenCentral()
|
||||
}
|
||||
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("androidx.navigation:navigation-safe-args-gradle-plugin:2.4.0-alpha07")
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue