[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:
tehcneko 2021-08-26 15:39:16 +08:00 committed by GitHub
parent 48c642e778
commit 5cf522b656
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 159 additions and 148 deletions

View File

@ -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;
}
};

View File

@ -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))

View File

@ -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

View File

@ -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);
}
}
}

View File

@ -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<>();

View File

@ -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));
}
};

View File

@ -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();

View File

@ -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;
}

View File

@ -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"

View File

@ -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")
}