mirror of
https://github.com/f-droid/fdroidclient.git
synced 2026-04-20 06:47:06 -04:00
[app] Move managing repos to new DB
This commit is contained in:
committed by
Hans-Christoph Steiner
parent
0a7debeb30
commit
adaf2a97ef
@@ -32,13 +32,13 @@ import android.widget.Toast;
|
||||
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.fdroid.database.Repository;
|
||||
import org.fdroid.fdroid.AddRepoIntentService;
|
||||
import org.fdroid.fdroid.FDroidApp;
|
||||
import org.fdroid.fdroid.IndexUpdater;
|
||||
import org.fdroid.fdroid.IndexV1Updater;
|
||||
import org.fdroid.fdroid.R;
|
||||
import org.fdroid.fdroid.Utils;
|
||||
import org.fdroid.fdroid.data.Repo;
|
||||
import org.fdroid.fdroid.data.RepoProvider;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
@@ -196,11 +196,11 @@ public class TreeUriScannerIntentService extends IntentService {
|
||||
destFile.delete();
|
||||
|
||||
Log.i(TAG, "Found a valid, signed index-v1.json");
|
||||
for (Repo repo : RepoProvider.Helper.all(context)) {
|
||||
if (fingerprint.equals(repo.fingerprint)) {
|
||||
Log.i(TAG, repo.address + " has the SAME fingerprint: " + fingerprint);
|
||||
for (Repository repo : FDroidApp.repos) {
|
||||
if (fingerprint.equals(repo.getFingerprint())) {
|
||||
Log.i(TAG, repo.getAddress() + " has the SAME fingerprint: " + fingerprint);
|
||||
} else {
|
||||
Log.i(TAG, repo.address + " different fingerprint");
|
||||
Log.i(TAG, repo.getAddress() + " different fingerprint");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ import org.fdroid.fdroid.Preferences;
|
||||
import org.fdroid.fdroid.R;
|
||||
import org.fdroid.fdroid.UpdateService;
|
||||
import org.fdroid.fdroid.Utils;
|
||||
import org.fdroid.fdroid.data.DBHelper;
|
||||
import org.fdroid.fdroid.panic.HidingManager;
|
||||
import org.fdroid.fdroid.views.apps.AppListActivity;
|
||||
import org.fdroid.fdroid.views.categories.CategoryAdapter;
|
||||
@@ -46,7 +47,7 @@ class CategoriesViewBinder implements Observer<List<Category>> {
|
||||
|
||||
CategoriesViewBinder(final AppCompatActivity activity, FrameLayout parent) {
|
||||
this.activity = activity;
|
||||
FDroidDatabase db = FDroidDatabaseHolder.getDb(activity);
|
||||
FDroidDatabase db = DBHelper.getDb(activity);
|
||||
Transformations.distinctUntilChanged(db.getRepositoryDao().getLiveCategories()).observe(activity, this);
|
||||
|
||||
View categoriesView = activity.getLayoutInflater().inflate(R.layout.main_tab_categories, parent, true);
|
||||
|
||||
@@ -8,8 +8,8 @@ import android.net.Uri;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import org.fdroid.fdroid.data.Repo;
|
||||
import org.fdroid.fdroid.data.RepoProvider;
|
||||
import org.fdroid.database.Repository;
|
||||
import org.fdroid.download.Mirror;
|
||||
import org.fdroid.fdroid.views.ManageReposActivity;
|
||||
import org.fdroid.fdroid.views.main.MainActivity;
|
||||
|
||||
@@ -76,14 +76,14 @@ public class AddRepoIntentService extends IntentService {
|
||||
}
|
||||
|
||||
String fingerprint = uri.getQueryParameter("fingerprint");
|
||||
for (Repo repo : RepoProvider.Helper.all(this)) {
|
||||
if (repo.inuse && TextUtils.equals(fingerprint, repo.fingerprint)) {
|
||||
if (TextUtils.equals(urlString, repo.address)) {
|
||||
for (Repository repo : FDroidApp.repos) {
|
||||
if (repo.getEnabled() && TextUtils.equals(fingerprint, repo.getFingerprint())) {
|
||||
if (TextUtils.equals(urlString, repo.getAddress())) {
|
||||
Utils.debugLog(TAG, urlString + " already added as a repo");
|
||||
return;
|
||||
} else {
|
||||
for (String mirrorUrl : repo.getMirrorList()) {
|
||||
if (urlString.startsWith(mirrorUrl)) {
|
||||
for (Mirror mirror : repo.getMirrors()) {
|
||||
if (urlString.startsWith(mirror.getBaseUrl())) {
|
||||
Utils.debugLog(TAG, urlString + " already added as a mirror");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ import android.os.Parcelable;
|
||||
|
||||
import org.fdroid.fdroid.data.Apk;
|
||||
import org.fdroid.fdroid.data.App;
|
||||
import org.fdroid.fdroid.data.Repo;
|
||||
import org.fdroid.fdroid.installer.ErrorDialogActivity;
|
||||
import org.fdroid.fdroid.installer.InstallManagerService;
|
||||
import org.fdroid.fdroid.net.DownloaderService;
|
||||
@@ -208,12 +207,12 @@ public final class AppUpdateStatusManager {
|
||||
localBroadcastManager = LocalBroadcastManager.getInstance(context.getApplicationContext());
|
||||
}
|
||||
|
||||
public void removeAllByRepo(Repo repo) {
|
||||
public void removeAllByRepo(long repoId) {
|
||||
boolean hasRemovedSome = false;
|
||||
Iterator<AppUpdateStatus> it = getAll().iterator();
|
||||
while (it.hasNext()) {
|
||||
AppUpdateStatus status = it.next();
|
||||
if (status.apk.repoId == repo.getId()) {
|
||||
if (status.apk.repoId == repoId) {
|
||||
it.remove();
|
||||
hasRemovedSome = true;
|
||||
}
|
||||
|
||||
@@ -53,7 +53,6 @@ import org.acra.config.DialogConfigurationBuilder;
|
||||
import org.acra.config.MailSenderConfigurationBuilder;
|
||||
import org.apache.commons.net.util.SubnetUtils;
|
||||
import org.fdroid.database.FDroidDatabase;
|
||||
import org.fdroid.database.FDroidDatabaseHolder;
|
||||
import org.fdroid.database.Repository;
|
||||
import org.fdroid.fdroid.Preferences.ChangeListener;
|
||||
import org.fdroid.fdroid.Preferences.Theme;
|
||||
@@ -337,7 +336,7 @@ public class FDroidApp extends Application implements androidx.work.Configuratio
|
||||
|
||||
// keep a static copy of the repositories around and in-sync
|
||||
// not how one would normally do this, but it is a common pattern in this codebase
|
||||
FDroidDatabase db = FDroidDatabaseHolder.getDb(this);
|
||||
FDroidDatabase db = DBHelper.getDb(this);
|
||||
db.getRepositoryDao().getLiveRepositories().observeForever(repositories -> repos = repositories);
|
||||
|
||||
PRNGFixes.apply();
|
||||
|
||||
@@ -57,10 +57,10 @@ import org.fdroid.index.v1.IndexUpdaterKt;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.app.JobIntentService;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
import androidx.core.content.ContextCompat;
|
||||
@@ -77,6 +77,8 @@ public class UpdateService extends JobIntentService {
|
||||
public static final String LOCAL_ACTION_STATUS = "status";
|
||||
|
||||
public static final String EXTRA_MESSAGE = "msg";
|
||||
public static final String EXTRA_REPO_ID = "repoId";
|
||||
public static final String EXTRA_REPO_FINGERPRINT = "fingerprint";
|
||||
public static final String EXTRA_REPO_ERRORS = "repoErrors";
|
||||
public static final String EXTRA_STATUS_CODE = "status";
|
||||
public static final String EXTRA_MANUAL_UPDATE = "manualUpdate";
|
||||
@@ -112,8 +114,13 @@ public class UpdateService extends JobIntentService {
|
||||
}
|
||||
|
||||
public static void updateRepoNow(Context context, String address) {
|
||||
updateNewRepoNow(context, address, null);
|
||||
}
|
||||
|
||||
public static void updateNewRepoNow(Context context, String address, @Nullable String fingerprint) {
|
||||
Intent intent = new Intent(context, UpdateService.class);
|
||||
intent.putExtra(EXTRA_MANUAL_UPDATE, true);
|
||||
intent.putExtra(EXTRA_REPO_FINGERPRINT, fingerprint);
|
||||
if (!TextUtils.isEmpty(address)) {
|
||||
intent.setData(Uri.parse(address));
|
||||
}
|
||||
@@ -416,6 +423,8 @@ public class UpdateService extends JobIntentService {
|
||||
final long startTime = System.currentTimeMillis();
|
||||
boolean manualUpdate = intent.getBooleanExtra(EXTRA_MANUAL_UPDATE, false);
|
||||
boolean forcedUpdate = intent.getBooleanExtra(EXTRA_FORCED_UPDATE, false);
|
||||
long repoId = intent.getLongExtra(EXTRA_REPO_ID, -1);
|
||||
String fingerprint = intent.getStringExtra(EXTRA_REPO_FINGERPRINT);
|
||||
String address = intent.getDataString();
|
||||
|
||||
try {
|
||||
@@ -461,11 +470,10 @@ public class UpdateService extends JobIntentService {
|
||||
int errorRepos = 0;
|
||||
ArrayList<CharSequence> repoErrors = new ArrayList<>();
|
||||
boolean changes = false;
|
||||
boolean singleRepoUpdate = !TextUtils.isEmpty(address);
|
||||
boolean singleRepoUpdate = !TextUtils.isEmpty(address) || repoId > 0;
|
||||
for (final Repository repo : repos) {
|
||||
if (!repo.getEnabled()) continue;
|
||||
if (!singleRepoUpdate && repo.isSwap()) continue;
|
||||
if (singleRepoUpdate && !repo.getAddress().equals(address)) {
|
||||
if (singleRepoUpdate && !repo.getAddress().equals(address) && repo.getRepoId() != repoId) {
|
||||
unchangedRepos++;
|
||||
continue;
|
||||
}
|
||||
@@ -479,9 +487,14 @@ public class UpdateService extends JobIntentService {
|
||||
// TODO try new v2 index first
|
||||
final org.fdroid.index.v1.IndexV1Updater updater = new org.fdroid.index.v1.IndexV1Updater(
|
||||
getApplicationContext(), DownloaderFactory.INSTANCE, compatChecker);
|
||||
final long repoId = repo.getRepoId();
|
||||
final String certificate = Objects.requireNonNull(repo.getCertificate());
|
||||
IndexUpdateResult result = updater.update(repoId, certificate, listener);
|
||||
final long currentRepoId = repo.getRepoId();
|
||||
final IndexUpdateResult result;
|
||||
if (repo.getCertificate() == null) {
|
||||
// This is a new repo without a certificate
|
||||
result = updater.updateNewRepo(currentRepoId, fingerprint, listener);
|
||||
} else {
|
||||
result = updater.update(currentRepoId, repo.getCertificate(), listener);
|
||||
}
|
||||
if (result == IndexUpdateResult.UNCHANGED) {
|
||||
unchangedRepos++;
|
||||
} else if (result == IndexUpdateResult.PROCESSED) {
|
||||
|
||||
@@ -60,7 +60,6 @@ import org.fdroid.download.DownloadRequest;
|
||||
import org.fdroid.download.Mirror;
|
||||
import org.fdroid.fdroid.compat.FileCompat;
|
||||
import org.fdroid.fdroid.data.App;
|
||||
import org.fdroid.fdroid.data.Repo;
|
||||
import org.fdroid.fdroid.data.SanitizedFile;
|
||||
import org.fdroid.fdroid.data.Schema;
|
||||
import org.fdroid.fdroid.net.TreeUriDownloader;
|
||||
@@ -343,24 +342,26 @@ public final class Utils {
|
||||
for (int i = 2; i < fingerprint.length(); i = i + 2) {
|
||||
displayFP.append(" ").append(fingerprint.substring(i, i + 2));
|
||||
}
|
||||
return displayFP.toString();
|
||||
return displayFP.toString().toUpperCase(Locale.US);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static Uri getLocalRepoUri(Repo repo) {
|
||||
if (TextUtils.isEmpty(repo.address)) {
|
||||
public static Uri getLocalRepoUri(Repository repo) {
|
||||
if (TextUtils.isEmpty(repo.getAddress())) {
|
||||
return Uri.parse("http://wifi-not-enabled");
|
||||
}
|
||||
Uri uri = Uri.parse(repo.address);
|
||||
Uri uri = Uri.parse(repo.getAddress());
|
||||
Uri.Builder b = uri.buildUpon();
|
||||
if (!TextUtils.isEmpty(repo.fingerprint)) {
|
||||
b.appendQueryParameter("fingerprint", repo.fingerprint);
|
||||
if (!TextUtils.isEmpty(repo.getCertificate())) {
|
||||
String fingerprint = DigestUtils.sha256Hex(repo.getCertificate());
|
||||
b.appendQueryParameter("fingerprint", fingerprint);
|
||||
}
|
||||
String scheme = Preferences.get().isLocalRepoHttpsEnabled() ? "https" : "http";
|
||||
b.scheme(scheme);
|
||||
return b.build();
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public static Uri getSharingUri(Repo repo) {
|
||||
if (TextUtils.isEmpty(repo.address)) {
|
||||
return Uri.parse("http://wifi-not-enabled");
|
||||
@@ -378,6 +379,23 @@ public final class Utils {
|
||||
return b.build();
|
||||
}
|
||||
|
||||
public static Uri getSharingUri(Repository repo) {
|
||||
if (TextUtils.isEmpty(repo.getAddress())) {
|
||||
return Uri.parse("http://wifi-not-enabled");
|
||||
}
|
||||
Uri localRepoUri = getLocalRepoUri(repo);
|
||||
Uri.Builder b = localRepoUri.buildUpon();
|
||||
b.scheme(localRepoUri.getScheme().replaceFirst("http", "fdroidrepo"));
|
||||
b.appendQueryParameter("swap", "1");
|
||||
if (!TextUtils.isEmpty(FDroidApp.bssid)) {
|
||||
b.appendQueryParameter("bssid", FDroidApp.bssid);
|
||||
if (!TextUtils.isEmpty(FDroidApp.ssid)) {
|
||||
b.appendQueryParameter("ssid", FDroidApp.ssid);
|
||||
}
|
||||
}
|
||||
return b.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a standard {@link PackageManager} {@link Uri} for pointing to an app.
|
||||
*/
|
||||
|
||||
@@ -31,6 +31,12 @@ import android.database.sqlite.SQLiteOpenHelper;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.annotation.WorkerThread;
|
||||
|
||||
import org.fdroid.database.FDroidDatabase;
|
||||
import org.fdroid.database.FDroidDatabaseHolder;
|
||||
import org.fdroid.database.InitialRepository;
|
||||
import org.fdroid.fdroid.Preferences;
|
||||
import org.fdroid.fdroid.R;
|
||||
import org.fdroid.fdroid.Utils;
|
||||
@@ -71,6 +77,28 @@ public class DBHelper extends SQLiteOpenHelper {
|
||||
private static DBHelper instance;
|
||||
private static final String DATABASE_NAME = "fdroid";
|
||||
|
||||
public static FDroidDatabase getDb(Context context) {
|
||||
return FDroidDatabaseHolder.getDb(context, "fdroid_db", db -> prePopulateDb(context, db));
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
@VisibleForTesting
|
||||
static void prePopulateDb(Context context, FDroidDatabase db) {
|
||||
List<String> initialRepos = DBHelper.loadInitialRepos(context);
|
||||
for (int i = 0; i < initialRepos.size(); i += REPO_XML_ITEM_COUNT) {
|
||||
InitialRepository repo = new InitialRepository(
|
||||
initialRepos.get(i), // name
|
||||
initialRepos.get(i + 1), // address
|
||||
initialRepos.get(i + 2), // description
|
||||
initialRepos.get(i + 7), // certificate
|
||||
Integer.parseInt(initialRepos.get(i + 3)), // version
|
||||
initialRepos.get(i + 4).equals("1"), // enabled
|
||||
Integer.parseInt(initialRepos.get(i + 5)) // weight
|
||||
);
|
||||
db.getRepositoryDao().insert(repo);
|
||||
}
|
||||
}
|
||||
|
||||
private static final String CREATE_TABLE_PACKAGE = "CREATE TABLE " + PackageTable.NAME
|
||||
+ " ( "
|
||||
+ PackageTable.Cols.PACKAGE_NAME + " text not null, "
|
||||
|
||||
@@ -251,7 +251,7 @@ public class RepoProvider extends FDroidProvider {
|
||||
int appCount = resolver.delete(appUri, null, null);
|
||||
Utils.debugLog(TAG, "Removed " + appCount + " apps from repo " + repo.address + ".");
|
||||
|
||||
AppUpdateStatusManager.getInstance(context).removeAllByRepo(repo);
|
||||
AppUpdateStatusManager.getInstance(context).removeAllByRepo(repo.id);
|
||||
|
||||
AppProvider.Helper.recalculatePreferredMetadata(context);
|
||||
}
|
||||
|
||||
@@ -50,7 +50,6 @@ import com.google.android.material.appbar.MaterialToolbar;
|
||||
import org.fdroid.database.AppPrefs;
|
||||
import org.fdroid.database.AppVersion;
|
||||
import org.fdroid.database.FDroidDatabase;
|
||||
import org.fdroid.database.FDroidDatabaseHolder;
|
||||
import org.fdroid.download.DownloadRequest;
|
||||
import org.fdroid.fdroid.AppUpdateStatusManager;
|
||||
import org.fdroid.fdroid.CompatibilityChecker;
|
||||
@@ -159,7 +158,7 @@ public class AppDetailsActivity extends AppCompatActivity
|
||||
}
|
||||
);
|
||||
checker = new CompatibilityChecker(this);
|
||||
db = FDroidDatabaseHolder.getDb(getApplicationContext());
|
||||
db = DBHelper.getDb(getApplicationContext());
|
||||
db.getAppDao().getApp(packageName).observe(this, this::onAppChanged);
|
||||
db.getVersionDao().getAppVersions(packageName).observe(this, this::onVersionsChanged);
|
||||
db.getAppPrefsDao().getAppPrefs(packageName).observe(this, this::onAppPrefsChanged);
|
||||
|
||||
@@ -22,12 +22,10 @@ package org.fdroid.fdroid.views;
|
||||
import android.content.ClipData;
|
||||
import android.content.ClipboardManager;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.net.wifi.WifiInfo;
|
||||
import android.net.wifi.WifiManager;
|
||||
@@ -43,10 +41,8 @@ import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
@@ -54,17 +50,21 @@ import com.google.android.material.appbar.MaterialToolbar;
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import com.google.android.material.textfield.TextInputLayout;
|
||||
|
||||
import org.fdroid.database.Repository;
|
||||
import org.fdroid.database.RepositoryDao;
|
||||
import org.fdroid.download.Mirror;
|
||||
import org.fdroid.fdroid.AddRepoIntentService;
|
||||
import org.fdroid.fdroid.AppUpdateStatusManager;
|
||||
import org.fdroid.fdroid.FDroidApp;
|
||||
import org.fdroid.fdroid.IndexUpdater;
|
||||
import org.fdroid.fdroid.Preferences;
|
||||
import org.fdroid.fdroid.R;
|
||||
import org.fdroid.fdroid.UpdateService;
|
||||
import org.fdroid.fdroid.Utils;
|
||||
import org.fdroid.fdroid.data.App;
|
||||
import org.fdroid.fdroid.data.DBHelper;
|
||||
import org.fdroid.fdroid.data.NewRepoConfig;
|
||||
import org.fdroid.fdroid.data.Repo;
|
||||
import org.fdroid.fdroid.data.RepoProvider;
|
||||
import org.fdroid.fdroid.data.Schema.RepoTable;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
@@ -72,28 +72,29 @@ import java.net.HttpURLConnection;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.util.Arrays;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.core.app.NavUtils;
|
||||
import androidx.core.app.TaskStackBuilder;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.loader.app.LoaderManager;
|
||||
import androidx.loader.content.CursorLoader;
|
||||
import androidx.loader.content.Loader;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.rxjava3.core.Single;
|
||||
import io.reactivex.rxjava3.disposables.CompositeDisposable;
|
||||
import io.reactivex.rxjava3.disposables.Disposable;
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers;
|
||||
|
||||
public class ManageReposActivity extends AppCompatActivity
|
||||
implements LoaderManager.LoaderCallbacks<Cursor>, RepoAdapter.EnabledListener {
|
||||
public class ManageReposActivity extends AppCompatActivity implements RepoAdapter.RepoItemListener {
|
||||
private static final String TAG = "ManageReposActivity";
|
||||
|
||||
public static final String EXTRA_FINISH_AFTER_ADDING_REPO = "finishAfterAddingRepo";
|
||||
@@ -102,10 +103,11 @@ public class ManageReposActivity extends AppCompatActivity
|
||||
|
||||
private enum AddRepoState {
|
||||
DOESNT_EXIST, EXISTS_FINGERPRINT_MISMATCH, EXISTS_ADD_MIRROR, EXISTS_ALREADY_MIRROR,
|
||||
EXISTS_DISABLED, EXISTS_ENABLED, EXISTS_UPGRADABLE_TO_SIGNED, INVALID_URL,
|
||||
IS_SWAP
|
||||
EXISTS_DISABLED, EXISTS_ENABLED, EXISTS_UPGRADABLE_TO_SIGNED, INVALID_URL
|
||||
}
|
||||
|
||||
private RepositoryDao repositoryDao;
|
||||
|
||||
/**
|
||||
* True if activity started with an intent such as from QR code. False if
|
||||
* opened from, e.g. the main menu.
|
||||
@@ -118,6 +120,7 @@ public class ManageReposActivity extends AppCompatActivity
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
FDroidApp fdroidApp = (FDroidApp) getApplication();
|
||||
fdroidApp.applyPureBlackBackgroundInDarkTheme(this);
|
||||
repositoryDao = DBHelper.getDb(this).getRepositoryDao();
|
||||
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
@@ -149,17 +152,10 @@ public class ManageReposActivity extends AppCompatActivity
|
||||
}
|
||||
});
|
||||
|
||||
final ListView repoList = (ListView) findViewById(R.id.list);
|
||||
repoAdapter = new RepoAdapter(this);
|
||||
repoAdapter.setEnabledListener(this);
|
||||
final RecyclerView repoList = (RecyclerView) findViewById(R.id.list);
|
||||
RepoAdapter repoAdapter = new RepoAdapter(this);
|
||||
repoList.setAdapter(repoAdapter);
|
||||
repoList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||
Repo repo = new Repo((Cursor) repoList.getItemAtPosition(position));
|
||||
editRepo(repo);
|
||||
}
|
||||
});
|
||||
repositoryDao.getLiveRepositories().observe(this, repoAdapter::updateItems);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -182,9 +178,6 @@ public class ManageReposActivity extends AppCompatActivity
|
||||
|
||||
/* let's see if someone is trying to send us a new repo */
|
||||
addRepoFromIntent(getIntent());
|
||||
|
||||
// Starts a new or restarts an existing Loader in this manager
|
||||
getSupportLoaderManager().restartLoader(0, null, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -287,8 +280,8 @@ public class ManageReposActivity extends AppCompatActivity
|
||||
private class AddRepo {
|
||||
|
||||
private final Context context;
|
||||
private final HashMap<String, Repo> urlRepoMap = new HashMap<>();
|
||||
private final HashMap<String, Repo> fingerprintRepoMap = new HashMap<>();
|
||||
private final HashMap<String, Repository> urlRepoMap = new HashMap<>();
|
||||
private final HashMap<String, Repository> fingerprintRepoMap = new HashMap<>();
|
||||
private final AlertDialog addRepoDialog;
|
||||
private final TextView overwriteMessage;
|
||||
private final ColorStateList defaultTextColour;
|
||||
@@ -306,14 +299,17 @@ public class ManageReposActivity extends AppCompatActivity
|
||||
|
||||
context = ManageReposActivity.this;
|
||||
|
||||
for (Repo repo : RepoProvider.Helper.all(context)) {
|
||||
urlRepoMap.put(repo.address, repo);
|
||||
for (String url : repo.getMirrorList()) {
|
||||
urlRepoMap.put(url, repo);
|
||||
for (Repository repo : FDroidApp.repos) {
|
||||
urlRepoMap.put(repo.getAddress(), repo);
|
||||
for (Mirror mirror : repo.getAllMirrors()) {
|
||||
urlRepoMap.put(mirror.getBaseUrl(), repo);
|
||||
}
|
||||
if (!TextUtils.isEmpty(repo.fingerprint)
|
||||
&& TextUtils.equals(getRepoType(newAddress), getRepoType(repo.address))) {
|
||||
fingerprintRepoMap.put(repo.fingerprint, repo);
|
||||
if (!TextUtils.isEmpty(repo.getCertificate())
|
||||
&& TextUtils.equals(getRepoType(newAddress), getRepoType(repo.getAddress()))) {
|
||||
String fingerprint = repo.getFingerprint();
|
||||
if (fingerprint != null) {
|
||||
fingerprintRepoMap.put(fingerprint.toLowerCase(Locale.ENGLISH), repo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -376,30 +372,23 @@ public class ManageReposActivity extends AppCompatActivity
|
||||
|
||||
String fp = fingerprintEditText.getText().toString();
|
||||
// remove any whitespace from fingerprint
|
||||
fp = fp.replaceAll("\\s", "");
|
||||
fp = fp.replaceAll("\\s", "").toLowerCase(Locale.ENGLISH);
|
||||
if (TextUtils.isEmpty(fp)) fp = null;
|
||||
|
||||
switch (addRepoState) {
|
||||
case DOESNT_EXIST:
|
||||
prepareToCreateNewRepo(url, fp, username, password);
|
||||
break;
|
||||
|
||||
case IS_SWAP:
|
||||
Utils.debugLog(TAG, "Removing existing swap repo " + url
|
||||
+ " before adding new repo.");
|
||||
Repo repo = RepoProvider.Helper.findByAddress(context, url);
|
||||
RepoProvider.Helper.remove(context, repo.getId());
|
||||
prepareToCreateNewRepo(url, fp, username, password);
|
||||
break;
|
||||
|
||||
case EXISTS_DISABLED:
|
||||
case EXISTS_UPGRADABLE_TO_SIGNED:
|
||||
case EXISTS_ADD_MIRROR:
|
||||
updateAndEnableExistingRepo(url, fp);
|
||||
finishedAddingRepo();
|
||||
finishedAddingRepo(url, fp);
|
||||
break;
|
||||
|
||||
default:
|
||||
finishedAddingRepo();
|
||||
finishedAddingRepo(url, fp);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -477,9 +466,10 @@ public class ManageReposActivity extends AppCompatActivity
|
||||
// Don't bother dealing with this exception yet, as this is called every time
|
||||
// a letter is added to the repo URL text input. We don't want to display a message
|
||||
// to the user until they try to save the repo.
|
||||
return;
|
||||
}
|
||||
|
||||
Repo repo = fingerprintRepoMap.get(fingerprint);
|
||||
Repository repo = fingerprintRepoMap.get(fingerprint.toLowerCase(Locale.ENGLISH));
|
||||
if (repo == null) {
|
||||
repo = urlRepoMap.get(uri);
|
||||
}
|
||||
@@ -487,18 +477,17 @@ public class ManageReposActivity extends AppCompatActivity
|
||||
if (repo == null) {
|
||||
repoDoesntExist();
|
||||
} else {
|
||||
if (repo.isSwap) {
|
||||
repoIsSwap(repo);
|
||||
} else if (repo.fingerprint == null && fingerprint.length() > 0) {
|
||||
if (repo.getFingerprint() == null && fingerprint.length() > 0) {
|
||||
upgradingToSigned(repo);
|
||||
} else if (repo.fingerprint != null && !repo.fingerprint.equalsIgnoreCase(fingerprint)) {
|
||||
} else if (repo.getFingerprint() != null && !repo.getFingerprint().equalsIgnoreCase(fingerprint)) {
|
||||
repoFingerprintDoesntMatch(repo);
|
||||
} else {
|
||||
if (repo.getMirrorList().contains(uri) && !TextUtils.equals(repo.address, uri) && repo.inuse) {
|
||||
Repository mirrorRepo = urlRepoMap.get(uri);
|
||||
if (repo.equals(mirrorRepo) && !TextUtils.equals(repo.getAddress(), uri) && repo.getEnabled()) {
|
||||
repoExistsAlreadyMirror(repo);
|
||||
} else if (!TextUtils.equals(repo.address, uri) && repo.inuse) {
|
||||
} else if (!TextUtils.equals(repo.getAddress(), uri) && repo.getEnabled()) {
|
||||
repoExistsAddMirror(repo);
|
||||
} else if (repo.inuse) {
|
||||
} else if (repo.getEnabled()) {
|
||||
repoExistsAndEnabled(repo);
|
||||
} else {
|
||||
repoExistsAndDisabled(repo);
|
||||
@@ -511,15 +500,11 @@ public class ManageReposActivity extends AppCompatActivity
|
||||
updateUi(null, AddRepoState.DOESNT_EXIST, 0, false, R.string.repo_add_add, true);
|
||||
}
|
||||
|
||||
private void repoIsSwap(Repo repo) {
|
||||
updateUi(repo, AddRepoState.IS_SWAP, 0, false, R.string.repo_add_add, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Same address with different fingerprint, this could be malicious, so display a message
|
||||
* force the user to manually delete the repo before adding this one.
|
||||
*/
|
||||
private void repoFingerprintDoesntMatch(Repo repo) {
|
||||
private void repoFingerprintDoesntMatch(Repository repo) {
|
||||
updateUi(repo, AddRepoState.EXISTS_FINGERPRINT_MISMATCH,
|
||||
R.string.repo_delete_to_overwrite,
|
||||
true, R.string.overwrite, false);
|
||||
@@ -530,32 +515,32 @@ public class ManageReposActivity extends AppCompatActivity
|
||||
R.string.repo_add_add, false);
|
||||
}
|
||||
|
||||
private void repoExistsAndDisabled(Repo repo) {
|
||||
private void repoExistsAndDisabled(Repository repo) {
|
||||
updateUi(repo, AddRepoState.EXISTS_DISABLED,
|
||||
R.string.repo_exists_enable, false, R.string.enable, true);
|
||||
}
|
||||
|
||||
private void repoExistsAndEnabled(Repo repo) {
|
||||
private void repoExistsAndEnabled(Repository repo) {
|
||||
updateUi(repo, AddRepoState.EXISTS_ENABLED, R.string.repo_exists_and_enabled, false,
|
||||
R.string.ok, true);
|
||||
}
|
||||
|
||||
private void repoExistsAddMirror(Repo repo) {
|
||||
private void repoExistsAddMirror(Repository repo) {
|
||||
updateUi(repo, AddRepoState.EXISTS_ADD_MIRROR, R.string.repo_exists_add_mirror, false,
|
||||
R.string.repo_add_mirror, true);
|
||||
}
|
||||
|
||||
private void repoExistsAlreadyMirror(Repo repo) {
|
||||
private void repoExistsAlreadyMirror(Repository repo) {
|
||||
updateUi(repo, AddRepoState.EXISTS_ALREADY_MIRROR, 0, false, R.string.ok, true);
|
||||
}
|
||||
|
||||
private void upgradingToSigned(Repo repo) {
|
||||
private void upgradingToSigned(Repository repo) {
|
||||
updateUi(repo, AddRepoState.EXISTS_UPGRADABLE_TO_SIGNED, R.string.repo_exists_add_fingerprint,
|
||||
false, R.string.add_key, true);
|
||||
}
|
||||
|
||||
private void updateUi(Repo repo, AddRepoState state, int messageRes, boolean redMessage, int addTextRes,
|
||||
boolean addEnabled) {
|
||||
private void updateUi(@Nullable Repository repo, AddRepoState state, int messageRes, boolean redMessage,
|
||||
int addTextRes, boolean addEnabled) {
|
||||
if (addRepoState != state) {
|
||||
addRepoState = state;
|
||||
|
||||
@@ -563,7 +548,7 @@ public class ManageReposActivity extends AppCompatActivity
|
||||
if (repo == null) {
|
||||
name = '"' + getString(R.string.unknown) + '"';
|
||||
} else {
|
||||
name = repo.name;
|
||||
name = repo.getName(App.getLocales());
|
||||
}
|
||||
|
||||
if (messageRes > 0) {
|
||||
@@ -582,10 +567,11 @@ public class ManageReposActivity extends AppCompatActivity
|
||||
addButton.setText(addTextRes);
|
||||
addButton.setEnabled(addEnabled);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 15 && addRepoState == AddRepoState.EXISTS_ALREADY_MIRROR) {
|
||||
if (addRepoState == AddRepoState.EXISTS_ALREADY_MIRROR) {
|
||||
addButton.callOnClick();
|
||||
editRepo(repo);
|
||||
String msg = getString(R.string.repo_exists_and_enabled, repo.address);
|
||||
if (repo != null) editRepo(repo);
|
||||
Objects.requireNonNull(repo); // should be non-null in this addRepoState
|
||||
String msg = getString(R.string.repo_exists_and_enabled, repo.getAddress());
|
||||
Toast.makeText(context, msg, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
@@ -594,7 +580,7 @@ public class ManageReposActivity extends AppCompatActivity
|
||||
/**
|
||||
* Adds a new repo to the database.
|
||||
*/
|
||||
private void prepareToCreateNewRepo(final String originalAddress, final String fingerprint,
|
||||
private void prepareToCreateNewRepo(final String originalAddress, @Nullable final String fingerprint,
|
||||
final String username, final String password) {
|
||||
final View addRepoForm = addRepoDialog.findViewById(R.id.add_repo_form);
|
||||
addRepoForm.setVisibility(View.GONE);
|
||||
@@ -702,7 +688,7 @@ public class ManageReposActivity extends AppCompatActivity
|
||||
textSearching.setText("");
|
||||
skip.setText(R.string.cancel);
|
||||
skip.setOnClickListener(null);
|
||||
validateRepoDetails(newAddress, fingerprint);
|
||||
validateRepoDetails(newAddress, fingerprint == null ? "" : fingerprint);
|
||||
} else {
|
||||
// create repo without username/password
|
||||
createNewRepo(newAddress, fingerprint);
|
||||
@@ -725,77 +711,73 @@ public class ManageReposActivity extends AppCompatActivity
|
||||
/**
|
||||
* Create a repository without a username or password.
|
||||
*/
|
||||
private void createNewRepo(String address, String fingerprint) {
|
||||
private void createNewRepo(String address, @Nullable String fingerprint) {
|
||||
createNewRepo(address, fingerprint, null, null);
|
||||
}
|
||||
|
||||
private void createNewRepo(String address, String fingerprint,
|
||||
private void createNewRepo(String address, @Nullable String fingerprint,
|
||||
final String username, final String password) {
|
||||
try {
|
||||
address = AddRepoIntentService.normalizeUrl(address);
|
||||
} catch (URISyntaxException e) {
|
||||
// Leave address as it was.
|
||||
}
|
||||
ContentValues values = new ContentValues(4);
|
||||
values.put(RepoTable.Cols.ADDRESS, address);
|
||||
if (!TextUtils.isEmpty(fingerprint)) {
|
||||
values.put(RepoTable.Cols.FINGERPRINT, fingerprint.toUpperCase(Locale.ENGLISH));
|
||||
}
|
||||
if (address == null) return;
|
||||
|
||||
if (!TextUtils.isEmpty(username) && !TextUtils.isEmpty(password)) {
|
||||
values.put(RepoTable.Cols.USERNAME, username);
|
||||
values.put(RepoTable.Cols.PASSWORD, password);
|
||||
}
|
||||
final String repoAddress = address;
|
||||
|
||||
RepoProvider.Helper.insert(context, values);
|
||||
finishedAddingRepo();
|
||||
Toast.makeText(context, getString(R.string.repo_added, address), Toast.LENGTH_SHORT).show();
|
||||
Disposable disposable = Single.fromCallable(
|
||||
() -> repositoryDao.insertEmptyRepo(repoAddress, username, password)
|
||||
)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(repoId -> {
|
||||
finishedAddingRepo(repoAddress, fingerprint);
|
||||
Toast.makeText(context, getString(R.string.repo_added, repoAddress), Toast.LENGTH_SHORT)
|
||||
.show();
|
||||
});
|
||||
compositeDisposable.add(disposable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Seeing as this repo already exists, we will force it to be enabled again.
|
||||
*/
|
||||
private void updateAndEnableExistingRepo(String url, String fingerprint) {
|
||||
private void updateAndEnableExistingRepo(String url, @Nullable String fingerprint) {
|
||||
if (fingerprint != null) {
|
||||
fingerprint = fingerprint.trim();
|
||||
if (TextUtils.isEmpty(fingerprint)) {
|
||||
fingerprint = null;
|
||||
} else {
|
||||
fingerprint = fingerprint.toUpperCase(Locale.ENGLISH);
|
||||
fingerprint = fingerprint.toLowerCase(Locale.ENGLISH);
|
||||
}
|
||||
}
|
||||
|
||||
Utils.debugLog(TAG, "Enabling existing repo: " + url);
|
||||
Repo repo = fingerprintRepoMap.get(fingerprint);
|
||||
Repository repo = fingerprintRepoMap.get(fingerprint);
|
||||
if (repo == null) {
|
||||
repo = RepoProvider.Helper.findByAddress(context, url);
|
||||
repo = urlRepoMap.get(url);
|
||||
}
|
||||
|
||||
ContentValues values = new ContentValues(2);
|
||||
values.put(RepoTable.Cols.IN_USE, 1);
|
||||
values.put(RepoTable.Cols.FINGERPRINT, fingerprint);
|
||||
if (!TextUtils.equals(url, repo.address)) {
|
||||
boolean addUserMirror = true;
|
||||
for (String mirror : repo.getMirrorList()) {
|
||||
if (TextUtils.equals(mirror, url)) {
|
||||
addUserMirror = false;
|
||||
}
|
||||
}
|
||||
if (addUserMirror) {
|
||||
if (repo.userMirrors == null) {
|
||||
repo.userMirrors = new String[]{url};
|
||||
} else {
|
||||
int last = repo.userMirrors.length;
|
||||
repo.userMirrors = Arrays.copyOf(repo.userMirrors, last + 1);
|
||||
repo.userMirrors[last] = url;
|
||||
}
|
||||
values.put(RepoTable.Cols.USER_MIRRORS, Utils.serializeCommaSeparatedString(repo.userMirrors));
|
||||
// return if this repo is gone
|
||||
if (repo == null) return;
|
||||
// return if a repo with that exact same address already exists
|
||||
if (TextUtils.equals(url, repo.getAddress())) return;
|
||||
// return if this address is already a mirror
|
||||
for (Mirror mirror : repo.getAllMirrors()) {
|
||||
if (TextUtils.equals(mirror.getBaseUrl(), url)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
RepoProvider.Helper.update(context, repo, values);
|
||||
ArrayList<String> userMirrors = new ArrayList<>(repo.getUserMirrors());
|
||||
userMirrors.add(url);
|
||||
|
||||
final long repoId = repo.getRepoId();
|
||||
runOffUiThread(() -> {
|
||||
repositoryDao.updateUserMirrors(repoId, userMirrors);
|
||||
return true;
|
||||
});
|
||||
// TODO does this change get reflected?
|
||||
notifyDataSetChanged();
|
||||
finishedAddingRepo();
|
||||
finishedAddingRepo(url, fingerprint);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -803,8 +785,9 @@ public class ManageReposActivity extends AppCompatActivity
|
||||
* will set a result and finish. Otherwise, we'll updateViews the list of repos
|
||||
* to reflect the newly created repo.
|
||||
*/
|
||||
private void finishedAddingRepo() {
|
||||
UpdateService.updateNow(ManageReposActivity.this);
|
||||
private void finishedAddingRepo(String address, @Nullable String fingerprint) {
|
||||
String f = fingerprint == null ? null : fingerprint.toLowerCase(Locale.ENGLISH);
|
||||
UpdateService.updateNewRepoNow(ManageReposActivity.this, address, f);
|
||||
if (addRepoDialog.isShowing()) {
|
||||
addRepoDialog.dismiss();
|
||||
}
|
||||
@@ -849,30 +832,9 @@ public class ManageReposActivity extends AppCompatActivity
|
||||
}
|
||||
}
|
||||
|
||||
private RepoAdapter repoAdapter;
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Loader<Cursor> onCreateLoader(int i, Bundle bundle) {
|
||||
Uri uri = RepoProvider.allExceptSwapUri();
|
||||
final String[] projection = {
|
||||
RepoTable.Cols._ID,
|
||||
RepoTable.Cols.NAME,
|
||||
RepoTable.Cols.SIGNING_CERT,
|
||||
RepoTable.Cols.FINGERPRINT,
|
||||
RepoTable.Cols.IN_USE,
|
||||
};
|
||||
return new CursorLoader(this, uri, projection, null, null, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(@NonNull Loader<Cursor> cursorLoader, Cursor cursor) {
|
||||
repoAdapter.swapCursor(cursor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(@NonNull Loader<Cursor> cursorLoader) {
|
||||
repoAdapter.swapCursor(null);
|
||||
public void onClicked(Repository repo) {
|
||||
editRepo(repo);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -891,17 +853,19 @@ public class ManageReposActivity extends AppCompatActivity
|
||||
* update the repos if you toggled on on.
|
||||
*/
|
||||
@Override
|
||||
public void onSetEnabled(Repo repo, boolean isEnabled) {
|
||||
if (repo.inuse != isEnabled) {
|
||||
ContentValues values = new ContentValues(1);
|
||||
values.put(RepoTable.Cols.IN_USE, isEnabled ? 1 : 0);
|
||||
RepoProvider.Helper.update(this, repo, values);
|
||||
public void onSetEnabled(Repository repo, boolean isEnabled) {
|
||||
if (repo.getEnabled() != isEnabled) {
|
||||
runOffUiThread(() -> {
|
||||
repositoryDao.setRepositoryEnabled(repo.getRepoId(), isEnabled);
|
||||
return true;
|
||||
});
|
||||
|
||||
if (isEnabled) {
|
||||
UpdateService.updateNow(this);
|
||||
UpdateService.updateRepoNow(this, repo.getAddress());
|
||||
} else {
|
||||
RepoProvider.Helper.purgeApps(this, repo);
|
||||
String notification = getString(R.string.repo_disabled_notification, repo.name);
|
||||
AppUpdateStatusManager.getInstance(this).removeAllByRepo(repo.getRepoId());
|
||||
// RepoProvider.Helper.purgeApps(this, repo);
|
||||
String notification = getString(R.string.repo_disabled_notification, repo.getName(App.getLocales()));
|
||||
Toast.makeText(this, notification, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
@@ -909,9 +873,9 @@ public class ManageReposActivity extends AppCompatActivity
|
||||
|
||||
public static final int SHOW_REPO_DETAILS = 1;
|
||||
|
||||
public void editRepo(Repo repo) {
|
||||
public void editRepo(Repository repo) {
|
||||
Intent intent = new Intent(this, RepoDetailsActivity.class);
|
||||
intent.putExtra(RepoDetailsActivity.ARG_REPO_ID, repo.getId());
|
||||
intent.putExtra(RepoDetailsActivity.ARG_REPO_ID, repo.getRepoId());
|
||||
startActivityForResult(intent, SHOW_REPO_DETAILS);
|
||||
}
|
||||
|
||||
@@ -922,7 +886,15 @@ public class ManageReposActivity extends AppCompatActivity
|
||||
* repo, and wanting the switch to be changed to on).
|
||||
*/
|
||||
private void notifyDataSetChanged() {
|
||||
getSupportLoaderManager().restartLoader(0, null, this);
|
||||
// TODO still needed?
|
||||
}
|
||||
|
||||
private <T> void runOffUiThread(Callable<T> r) {
|
||||
Disposable disposable = Single.fromCallable(r)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe();
|
||||
compositeDisposable.add(disposable);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,92 +1,105 @@
|
||||
package org.fdroid.fdroid.views;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.CompoundButton;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import org.fdroid.database.Repository;
|
||||
import org.fdroid.fdroid.R;
|
||||
import org.fdroid.fdroid.compat.CursorAdapterCompat;
|
||||
import org.fdroid.fdroid.data.Repo;
|
||||
import org.fdroid.fdroid.data.App;
|
||||
|
||||
import androidx.cursoradapter.widget.CursorAdapter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class RepoAdapter extends CursorAdapter {
|
||||
public class RepoAdapter extends RecyclerView.Adapter<RepoAdapter.RepoViewHolder> {
|
||||
|
||||
public interface EnabledListener {
|
||||
void onSetEnabled(Repo repo, boolean isEnabled);
|
||||
public interface RepoItemListener {
|
||||
void onClicked(Repository repo);
|
||||
|
||||
void onSetEnabled(Repository repo, boolean isEnabled);
|
||||
}
|
||||
|
||||
private final LayoutInflater inflater;
|
||||
private final List<Repository> items = new ArrayList<>();
|
||||
private final RepoItemListener repoItemListener;
|
||||
|
||||
private EnabledListener enabledListener;
|
||||
|
||||
RepoAdapter(Context context) {
|
||||
super(context, null, CursorAdapterCompat.FLAG_AUTO_REQUERY);
|
||||
inflater = LayoutInflater.from(context);
|
||||
RepoAdapter(RepoItemListener repoItemListener) {
|
||||
this.repoItemListener = repoItemListener;
|
||||
}
|
||||
|
||||
public void setEnabledListener(EnabledListener listener) {
|
||||
enabledListener = listener;
|
||||
@SuppressLint("NotifyDataSetChanged") // we could do better, but not really worth it at this point
|
||||
public void updateItems(List<Repository> items) {
|
||||
this.items.clear();
|
||||
this.items.addAll(items);
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public RepoViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
|
||||
View v = inflater.inflate(R.layout.repo_item, parent, false);
|
||||
return new RepoViewHolder(v);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasStableIds() {
|
||||
return true;
|
||||
public void onBindViewHolder(@NonNull RepoViewHolder holder, int position) {
|
||||
holder.bind(items.get(position));
|
||||
}
|
||||
|
||||
@Override
|
||||
public View newView(Context context, Cursor cursor, ViewGroup parent) {
|
||||
View view = inflater.inflate(R.layout.repo_item, parent, false);
|
||||
setupView(cursor, view, (CompoundButton) view.findViewById(R.id.repo_switch));
|
||||
return view;
|
||||
public int getItemCount() {
|
||||
return items.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindView(View view, Context context, Cursor cursor) {
|
||||
CompoundButton switchView = (CompoundButton) view.findViewById(R.id.repo_switch);
|
||||
class RepoViewHolder extends RecyclerView.ViewHolder {
|
||||
private final View rootView;
|
||||
private final CompoundButton switchView;
|
||||
private final TextView nameView;
|
||||
private final View unsignedView;
|
||||
private final View unverifiedView;
|
||||
|
||||
// Remove old listener (because we are reusing this view, we don't want
|
||||
// to invoke the listener for the last repo to use it - particularly
|
||||
// because we are potentially about to change the checked status
|
||||
// which would in turn invoke this listener....
|
||||
switchView.setOnCheckedChangeListener(null);
|
||||
setupView(cursor, view, switchView);
|
||||
}
|
||||
RepoViewHolder(@NonNull View view) {
|
||||
super(view);
|
||||
rootView = view;
|
||||
switchView = view.findViewById(R.id.repo_switch);
|
||||
nameView = view.findViewById(R.id.repo_name);
|
||||
unsignedView = view.findViewById(R.id.repo_unsigned);
|
||||
unverifiedView = view.findViewById(R.id.repo_unverified);
|
||||
}
|
||||
|
||||
private void setupView(Cursor cursor, View view, CompoundButton switchView) {
|
||||
final Repo repo = new Repo(cursor);
|
||||
private void bind(Repository repo) {
|
||||
rootView.setOnClickListener(v -> repoItemListener.onClicked(repo));
|
||||
// Remove old listener (because we are reusing this view, we don't want
|
||||
// to invoke the listener for the last repo to use it - particularly
|
||||
// because we are potentially about to change the checked status
|
||||
// which would in turn invoke this listener....
|
||||
switchView.setOnCheckedChangeListener(null);
|
||||
switchView.setChecked(repo.getEnabled());
|
||||
|
||||
switchView.setChecked(repo.inuse);
|
||||
|
||||
// Add this listener *after* setting the checked status, so we don't
|
||||
// invoke the listener while setting up the view...
|
||||
switchView.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
|
||||
@Override
|
||||
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
|
||||
if (enabledListener != null) {
|
||||
enabledListener.onSetEnabled(repo, isChecked);
|
||||
// Add this listener *after* setting the checked status, so we don't
|
||||
// invoke the listener while setting up the view...
|
||||
switchView.setOnCheckedChangeListener((buttonView, isChecked) -> {
|
||||
if (repoItemListener != null) {
|
||||
repoItemListener.onSetEnabled(repo, isChecked);
|
||||
}
|
||||
});
|
||||
nameView.setText(repo.getName(App.getLocales()));
|
||||
if (repo.getCertificate() != null) {
|
||||
unsignedView.setVisibility(View.GONE);
|
||||
unverifiedView.setVisibility(View.GONE);
|
||||
} else if (repo.getCertificate() == null) { // FIXME: Do we still need that unsignedView at all?
|
||||
unsignedView.setVisibility(View.GONE);
|
||||
unverifiedView.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
unsignedView.setVisibility(View.VISIBLE);
|
||||
unverifiedView.setVisibility(View.GONE);
|
||||
}
|
||||
});
|
||||
|
||||
TextView nameView = (TextView) view.findViewById(R.id.repo_name);
|
||||
nameView.setText(repo.getName());
|
||||
|
||||
View unsignedView = view.findViewById(R.id.repo_unsigned);
|
||||
View unverifiedView = view.findViewById(R.id.repo_unverified);
|
||||
if (repo.isSigned()) {
|
||||
unsignedView.setVisibility(View.GONE);
|
||||
unverifiedView.setVisibility(View.GONE);
|
||||
} else if (repo.isSignedButUnverified()) {
|
||||
unsignedView.setVisibility(View.GONE);
|
||||
unverifiedView.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
unsignedView.setVisibility(View.VISIBLE);
|
||||
unverifiedView.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package org.fdroid.fdroid.views;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
@@ -30,19 +29,24 @@ import android.widget.Toast;
|
||||
import com.google.android.material.appbar.MaterialToolbar;
|
||||
import com.google.android.material.textfield.TextInputLayout;
|
||||
|
||||
import org.fdroid.database.AppDao;
|
||||
import org.fdroid.database.Repository;
|
||||
import org.fdroid.database.RepositoryDao;
|
||||
import org.fdroid.download.Mirror;
|
||||
import org.fdroid.fdroid.FDroidApp;
|
||||
import org.fdroid.fdroid.NfcHelper;
|
||||
import org.fdroid.fdroid.NfcNotEnabledActivity;
|
||||
import org.fdroid.fdroid.R;
|
||||
import org.fdroid.fdroid.UpdateService;
|
||||
import org.fdroid.fdroid.Utils;
|
||||
import org.fdroid.fdroid.data.Repo;
|
||||
import org.fdroid.fdroid.data.RepoProvider;
|
||||
import org.fdroid.fdroid.data.Schema.RepoTable;
|
||||
import org.fdroid.fdroid.data.App;
|
||||
import org.fdroid.fdroid.data.DBHelper;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
@@ -53,7 +57,11 @@ import androidx.core.content.ContextCompat;
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.rxjava3.core.Single;
|
||||
import io.reactivex.rxjava3.disposables.Disposable;
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers;
|
||||
|
||||
public class RepoDetailsActivity extends AppCompatActivity {
|
||||
private static final String TAG = "RepoDetailsActivity";
|
||||
@@ -86,13 +94,15 @@ public class RepoDetailsActivity extends AppCompatActivity {
|
||||
private static final int[] HIDE_IF_EXISTS = {
|
||||
R.id.text_not_yet_updated,
|
||||
};
|
||||
private Repo repo;
|
||||
private Repository repo;
|
||||
private long repoId;
|
||||
private View repoView;
|
||||
private String shareUrl;
|
||||
|
||||
private MirrorAdapter adapterToNotify;
|
||||
|
||||
private RepositoryDao repositoryDao;
|
||||
private AppDao appDao;
|
||||
@Nullable
|
||||
private Disposable disposable;
|
||||
|
||||
@@ -112,6 +122,8 @@ public class RepoDetailsActivity extends AppCompatActivity {
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
FDroidApp fdroidApp = (FDroidApp) getApplication();
|
||||
fdroidApp.applyPureBlackBackgroundInDarkTheme(this);
|
||||
repositoryDao = DBHelper.getDb(this).getRepositoryDao();
|
||||
appDao = DBHelper.getDb(this).getAppDao();
|
||||
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
@@ -124,27 +136,31 @@ public class RepoDetailsActivity extends AppCompatActivity {
|
||||
repoView = findViewById(R.id.repo_view);
|
||||
|
||||
repoId = getIntent().getLongExtra(ARG_REPO_ID, 0);
|
||||
repo = RepoProvider.Helper.findById(this, repoId);
|
||||
repo = FDroidApp.getRepo(repoId);
|
||||
|
||||
TextView inputUrl = findViewById(R.id.input_repo_url);
|
||||
inputUrl.setText(repo.address);
|
||||
inputUrl.setText(repo.getAddress());
|
||||
|
||||
RecyclerView officialMirrorListView = findViewById(R.id.official_mirror_list);
|
||||
officialMirrorListView.setLayoutManager(new LinearLayoutManager(this));
|
||||
adapterToNotify = new MirrorAdapter(repo, repo.mirrors);
|
||||
adapterToNotify = new MirrorAdapter(repo, repo.getAllMirrors(false));
|
||||
officialMirrorListView.setAdapter(adapterToNotify);
|
||||
|
||||
RecyclerView userMirrorListView = findViewById(R.id.user_mirror_list);
|
||||
userMirrorListView.setLayoutManager(new LinearLayoutManager(this));
|
||||
userMirrorListView.setAdapter(new MirrorAdapter(repo, repo.userMirrors));
|
||||
MirrorAdapter userMirrorAdapter = new MirrorAdapter(repo, repo.getUserMirrors().size());
|
||||
userMirrorAdapter.setUserMirrors(repo.getUserMirrors());
|
||||
userMirrorListView.setAdapter(userMirrorAdapter);
|
||||
|
||||
if (repo.address.startsWith("content://") || repo.address.startsWith("file://")) {
|
||||
if (repo.getAddress().startsWith("content://") || repo.getAddress().startsWith("file://")) {
|
||||
// no need to show a QR Code, it is not shareable
|
||||
return;
|
||||
}
|
||||
|
||||
Uri uri = Uri.parse(repo.address);
|
||||
uri = uri.buildUpon().appendQueryParameter("fingerprint", repo.fingerprint).build();
|
||||
Uri uri = Uri.parse(repo.getAddress());
|
||||
if (repo.getFingerprint() != null) {
|
||||
uri = uri.buildUpon().appendQueryParameter("fingerprint", repo.getFingerprint()).build();
|
||||
}
|
||||
String qrUriString = uri.toString();
|
||||
disposable = Utils.generateQrBitmap(this, qrUriString)
|
||||
.subscribe(bitmap -> {
|
||||
@@ -183,7 +199,7 @@ public class RepoDetailsActivity extends AppCompatActivity {
|
||||
* have been updated. The safest way to deal with this is to reload the
|
||||
* repo object directly from the database.
|
||||
*/
|
||||
repo = RepoProvider.Helper.findById(this, repoId);
|
||||
repo = FDroidApp.getRepo(repoId);
|
||||
updateRepoView();
|
||||
|
||||
LocalBroadcastManager.getInstance(this).registerReceiver(broadcastReceiver,
|
||||
@@ -290,12 +306,12 @@ public class RepoDetailsActivity extends AppCompatActivity {
|
||||
}
|
||||
|
||||
private void prepareShareMenuItems(Menu menu) {
|
||||
if (!TextUtils.isEmpty(repo.address)) {
|
||||
if (!TextUtils.isEmpty(repo.fingerprint)) {
|
||||
shareUrl = Uri.parse(repo.address).buildUpon()
|
||||
.appendQueryParameter("fingerprint", repo.fingerprint).toString();
|
||||
if (!TextUtils.isEmpty(repo.getAddress())) {
|
||||
if (!TextUtils.isEmpty(repo.getCertificate())) {
|
||||
shareUrl = Uri.parse(repo.getAddress()).buildUpon()
|
||||
.appendQueryParameter("fingerprint", repo.getFingerprint()).toString();
|
||||
} else {
|
||||
shareUrl = repo.address;
|
||||
shareUrl = repo.getAddress();
|
||||
}
|
||||
menu.findItem(R.id.action_share).setVisible(true);
|
||||
} else {
|
||||
@@ -303,51 +319,52 @@ public class RepoDetailsActivity extends AppCompatActivity {
|
||||
}
|
||||
}
|
||||
|
||||
private void setupDescription(View parent, Repo repo) {
|
||||
private void setupDescription(View parent, Repository repo) {
|
||||
|
||||
TextView descriptionLabel = (TextView) parent.findViewById(R.id.label_description);
|
||||
TextView description = (TextView) parent.findViewById(R.id.text_description);
|
||||
|
||||
if (TextUtils.isEmpty(repo.description)) {
|
||||
String desc = repo.getDescription(App.getLocales());
|
||||
if (desc == null || TextUtils.isEmpty(desc)) {
|
||||
descriptionLabel.setVisibility(View.GONE);
|
||||
description.setVisibility(View.GONE);
|
||||
description.setText("");
|
||||
} else {
|
||||
descriptionLabel.setVisibility(View.VISIBLE);
|
||||
description.setVisibility(View.VISIBLE);
|
||||
description.setText(repo.description.replaceAll("\n", " "));
|
||||
description.setText(desc.replaceAll("\n", " "));
|
||||
}
|
||||
}
|
||||
|
||||
private void setupRepoFingerprint(View parent, Repo repo) {
|
||||
private void setupRepoFingerprint(View parent, Repository repo) {
|
||||
TextView repoFingerprintView = (TextView) parent.findViewById(R.id.text_repo_fingerprint);
|
||||
TextView repoFingerprintDescView = (TextView) parent.findViewById(R.id.text_repo_fingerprint_description);
|
||||
|
||||
String repoFingerprint;
|
||||
|
||||
// TODO show the current state of the signature check, not just whether there is a key or not
|
||||
if (TextUtils.isEmpty(repo.fingerprint) && TextUtils.isEmpty(repo.signingCertificate)) {
|
||||
if (TextUtils.isEmpty(repo.getCertificate())) {
|
||||
repoFingerprint = getResources().getString(R.string.unsigned);
|
||||
repoFingerprintView.setTextColor(ContextCompat.getColor(this, R.color.unsigned));
|
||||
repoFingerprintDescView.setVisibility(View.VISIBLE);
|
||||
repoFingerprintDescView.setText(getResources().getString(R.string.unsigned_description));
|
||||
} else {
|
||||
// this is based on repo.fingerprint always existing, which it should
|
||||
repoFingerprint = Utils.formatFingerprint(this, repo.fingerprint);
|
||||
repoFingerprint = Utils.formatFingerprint(this, repo.getFingerprint());
|
||||
repoFingerprintDescView.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
repoFingerprintView.setText(repoFingerprint);
|
||||
}
|
||||
|
||||
private void setupCredentials(View parent, Repo repo) {
|
||||
private void setupCredentials(View parent, Repository repo) {
|
||||
|
||||
TextView usernameLabel = parent.findViewById(R.id.label_username);
|
||||
TextView username = parent.findViewById(R.id.text_username);
|
||||
Button changePassword = parent.findViewById(R.id.button_edit_credentials);
|
||||
changePassword.setOnClickListener(this::showChangePasswordDialog);
|
||||
|
||||
if (TextUtils.isEmpty(repo.username)) {
|
||||
if (TextUtils.isEmpty(repo.getUsername())) {
|
||||
usernameLabel.setVisibility(View.GONE);
|
||||
username.setVisibility(View.GONE);
|
||||
username.setText("");
|
||||
@@ -355,7 +372,7 @@ public class RepoDetailsActivity extends AppCompatActivity {
|
||||
} else {
|
||||
usernameLabel.setVisibility(View.VISIBLE);
|
||||
username.setVisibility(View.VISIBLE);
|
||||
username.setText(repo.username);
|
||||
username.setText(repo.getUsername());
|
||||
changePassword.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
@@ -363,8 +380,7 @@ public class RepoDetailsActivity extends AppCompatActivity {
|
||||
private void updateRepoView() {
|
||||
TextView officialMirrorsLabel = repoView.findViewById(R.id.label_official_mirrors);
|
||||
RecyclerView officialMirrorList = repoView.findViewById(R.id.official_mirror_list);
|
||||
if ((repo.mirrors != null && repo.mirrors.length > 1)
|
||||
|| (repo.userMirrors != null && repo.userMirrors.length > 0)) {
|
||||
if (repo.getAllMirrors().size() > 1) {
|
||||
// don't show this if there is only the canonical URL available, and no other mirrors
|
||||
officialMirrorsLabel.setVisibility(View.VISIBLE);
|
||||
officialMirrorList.setVisibility(View.VISIBLE);
|
||||
@@ -375,7 +391,7 @@ public class RepoDetailsActivity extends AppCompatActivity {
|
||||
|
||||
TextView userMirrorsLabel = repoView.findViewById(R.id.label_user_mirrors);
|
||||
RecyclerView userMirrorList = repoView.findViewById(R.id.user_mirror_list);
|
||||
if (repo.userMirrors != null && repo.userMirrors.length > 0) {
|
||||
if (repo.getUserMirrors().size() > 0) {
|
||||
userMirrorsLabel.setVisibility(View.VISIBLE);
|
||||
userMirrorList.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
@@ -383,7 +399,7 @@ public class RepoDetailsActivity extends AppCompatActivity {
|
||||
userMirrorList.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
if (repo.hasBeenUpdated()) {
|
||||
if (repo.getLastETag() != null) {
|
||||
updateViewForExistingRepo(repoView);
|
||||
} else {
|
||||
setMultipleViewVisibility(repoView, HIDE_IF_EXISTS, View.VISIBLE);
|
||||
@@ -399,10 +415,11 @@ public class RepoDetailsActivity extends AppCompatActivity {
|
||||
TextView numApps = repoView.findViewById(R.id.text_num_apps);
|
||||
TextView lastUpdated = repoView.findViewById(R.id.text_last_update);
|
||||
|
||||
name.setText(repo.name);
|
||||
|
||||
int appCount = RepoProvider.Helper.countAppsForRepo(this, repoId);
|
||||
numApps.setText(String.format(Locale.getDefault(), "%d", appCount));
|
||||
name.setText(repo.getName(App.getLocales()));
|
||||
disposable = Single.fromCallable(() -> appDao.getNumberOfAppsInRepository(repoId))
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(appCount -> numApps.setText(String.format(Locale.getDefault(), "%d", appCount)));
|
||||
|
||||
setupDescription(repoView, repo);
|
||||
setupRepoFingerprint(repoView, repo);
|
||||
@@ -410,14 +427,13 @@ public class RepoDetailsActivity extends AppCompatActivity {
|
||||
|
||||
// Repos that existed before this feature was supported will have an
|
||||
// "Unknown" last update until next time they update...
|
||||
if (repo.lastUpdated == null) {
|
||||
if (repo.getLastUpdated() == null) {
|
||||
lastUpdated.setText(R.string.unknown);
|
||||
} else {
|
||||
int format = DateUtils.isToday(repo.lastUpdated.getTime()) ?
|
||||
int format = DateUtils.isToday(repo.getLastUpdated()) ?
|
||||
DateUtils.FORMAT_SHOW_TIME :
|
||||
DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_SHOW_DATE;
|
||||
lastUpdated.setText(DateUtils.formatDateTime(this,
|
||||
repo.lastUpdated.getTime(), format));
|
||||
lastUpdated.setText(DateUtils.formatDateTime(this, repo.getLastUpdated(), format));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -428,7 +444,10 @@ public class RepoDetailsActivity extends AppCompatActivity {
|
||||
.setPositiveButton(R.string.delete, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
RepoProvider.Helper.remove(getApplicationContext(), repoId);
|
||||
runOffUiThread(() -> {
|
||||
repositoryDao.deleteRepository(repoId);
|
||||
return true;
|
||||
});
|
||||
finish();
|
||||
}
|
||||
}).setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
|
||||
@@ -448,7 +467,7 @@ public class RepoDetailsActivity extends AppCompatActivity {
|
||||
final EditText nameInput = nameInputLayout.getEditText();
|
||||
final EditText passwordInput = passwordInputLayout.getEditText();
|
||||
|
||||
nameInput.setText(repo.username);
|
||||
nameInput.setText(repo.getUsername());
|
||||
passwordInput.requestFocus();
|
||||
|
||||
credentialsDialog.setTitle(R.string.repo_edit_credentials);
|
||||
@@ -471,19 +490,13 @@ public class RepoDetailsActivity extends AppCompatActivity {
|
||||
final String password = passwordInput.getText().toString();
|
||||
|
||||
if (!TextUtils.isEmpty(name)) {
|
||||
|
||||
final ContentValues values = new ContentValues(2);
|
||||
values.put(RepoTable.Cols.USERNAME, name);
|
||||
values.put(RepoTable.Cols.PASSWORD, password);
|
||||
|
||||
RepoProvider.Helper.update(RepoDetailsActivity.this, repo, values);
|
||||
|
||||
runOffUiThread(() -> {
|
||||
repositoryDao.updateUsernameAndPassword(repo.getRepoId(), name, password);
|
||||
return true;
|
||||
});
|
||||
updateRepoView();
|
||||
|
||||
dialog.dismiss();
|
||||
|
||||
} else {
|
||||
|
||||
Toast.makeText(RepoDetailsActivity.this, R.string.repo_error_empty_username,
|
||||
Toast.LENGTH_LONG).show();
|
||||
}
|
||||
@@ -494,8 +507,9 @@ public class RepoDetailsActivity extends AppCompatActivity {
|
||||
}
|
||||
|
||||
private class MirrorAdapter extends RecyclerView.Adapter<MirrorAdapter.MirrorViewHolder> {
|
||||
private final Repo repo;
|
||||
private final String[] mirrors;
|
||||
private final Repository repo;
|
||||
private final List<Mirror> mirrors;
|
||||
private final HashSet<String> disabledMirrors;
|
||||
|
||||
class MirrorViewHolder extends RecyclerView.ViewHolder {
|
||||
View view;
|
||||
@@ -506,9 +520,22 @@ public class RepoDetailsActivity extends AppCompatActivity {
|
||||
}
|
||||
}
|
||||
|
||||
MirrorAdapter(Repo repo, String[] mirrors) {
|
||||
MirrorAdapter(Repository repo, List<Mirror> mirrors) {
|
||||
this.repo = repo;
|
||||
this.mirrors = mirrors;
|
||||
disabledMirrors = new HashSet<>(repo.getDisabledMirrors());
|
||||
}
|
||||
|
||||
MirrorAdapter(Repository repo, int userMirrorSize) {
|
||||
this.repo = repo;
|
||||
this.mirrors = new ArrayList<>(userMirrorSize);
|
||||
disabledMirrors = new HashSet<>(repo.getDisabledMirrors());
|
||||
}
|
||||
|
||||
void setUserMirrors(List<String> userMirrors) {
|
||||
for (String url : userMirrors) {
|
||||
this.mirrors.add(new Mirror(url));
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@@ -521,16 +548,15 @@ public class RepoDetailsActivity extends AppCompatActivity {
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull MirrorViewHolder holder, final int position) {
|
||||
TextView repoNameTextView = holder.view.findViewById(R.id.repo_name);
|
||||
repoNameTextView.setText(mirrors[position]);
|
||||
Mirror mirror = mirrors.get(position);
|
||||
repoNameTextView.setText(mirror.getBaseUrl());
|
||||
|
||||
final String itemMirror = mirrors[position];
|
||||
final String itemMirror = mirror.getBaseUrl();
|
||||
boolean enabled = true;
|
||||
if (repo.disabledMirrors != null) {
|
||||
for (String disabled : repo.disabledMirrors) {
|
||||
if (TextUtils.equals(itemMirror, disabled)) {
|
||||
enabled = false;
|
||||
break;
|
||||
}
|
||||
for (String disabled : repo.getDisabledMirrors()) {
|
||||
if (TextUtils.equals(itemMirror, disabled)) {
|
||||
enabled = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
CompoundButton switchView = holder.view.findViewById(R.id.repo_switch);
|
||||
@@ -538,36 +564,23 @@ public class RepoDetailsActivity extends AppCompatActivity {
|
||||
switchView.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
|
||||
@Override
|
||||
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
|
||||
HashSet<String> disabledMirrors;
|
||||
if (repo.disabledMirrors == null) {
|
||||
disabledMirrors = new HashSet<>(1);
|
||||
} else {
|
||||
disabledMirrors = new HashSet<>(Arrays.asList(repo.disabledMirrors));
|
||||
}
|
||||
|
||||
if (isChecked) {
|
||||
disabledMirrors.remove(itemMirror);
|
||||
} else {
|
||||
disabledMirrors.add(itemMirror);
|
||||
}
|
||||
|
||||
int totalMirrors = (repo.mirrors == null ? 0 : repo.mirrors.length)
|
||||
+ (repo.userMirrors == null ? 0 : repo.userMirrors.length);
|
||||
List<Mirror> mirrors = repo.getAllMirrors(true);
|
||||
int totalMirrors = mirrors.size();
|
||||
if (disabledMirrors.size() == totalMirrors) {
|
||||
// if all mirrors are disabled, re-enable canonical repo as mirror
|
||||
disabledMirrors.remove(repo.address);
|
||||
disabledMirrors.remove(repo.getAddress());
|
||||
adapterToNotify.notifyItemChanged(0);
|
||||
}
|
||||
|
||||
if (disabledMirrors.size() == 0) {
|
||||
repo.disabledMirrors = null;
|
||||
} else {
|
||||
repo.disabledMirrors = disabledMirrors.toArray(new String[disabledMirrors.size()]);
|
||||
}
|
||||
final ContentValues values = new ContentValues(1);
|
||||
values.put(RepoTable.Cols.DISABLED_MIRRORS,
|
||||
Utils.serializeCommaSeparatedString(repo.disabledMirrors));
|
||||
RepoProvider.Helper.update(RepoDetailsActivity.this, repo, values);
|
||||
runOffUiThread(() -> {
|
||||
repositoryDao.updateDisabledMirrors(repo.getRepoId(), new ArrayList<>(disabledMirrors));
|
||||
return true;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -583,7 +596,14 @@ public class RepoDetailsActivity extends AppCompatActivity {
|
||||
if (mirrors == null) {
|
||||
return 0;
|
||||
}
|
||||
return mirrors.length;
|
||||
return mirrors.size();
|
||||
}
|
||||
}
|
||||
|
||||
private void runOffUiThread(Callable<?> r) {
|
||||
disposable = Single.fromCallable(r)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,8 +24,8 @@ import com.bumptech.glide.Glide;
|
||||
|
||||
import org.fdroid.database.AppOverviewItem;
|
||||
import org.fdroid.database.FDroidDatabase;
|
||||
import org.fdroid.database.FDroidDatabaseHolder;
|
||||
import org.fdroid.fdroid.R;
|
||||
import org.fdroid.fdroid.data.DBHelper;
|
||||
import org.fdroid.fdroid.views.apps.AppListActivity;
|
||||
import org.fdroid.fdroid.views.apps.FeatureImage;
|
||||
|
||||
@@ -57,7 +57,7 @@ public class CategoryController extends RecyclerView.ViewHolder {
|
||||
super(itemView);
|
||||
|
||||
this.activity = activity;
|
||||
db = FDroidDatabaseHolder.getDb(activity);
|
||||
db = DBHelper.getDb(activity);
|
||||
|
||||
appCardsAdapter = new AppPreviewAdapter(activity);
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/listPreferredItemHeight"
|
||||
android:orientation="horizontal"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingLeft="?attr/listPreferredItemPaddingLeft"
|
||||
android:paddingStart="?attr/listPreferredItemPaddingLeft"
|
||||
|
||||
@@ -23,11 +23,12 @@
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<ListView
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:scrollbars="vertical"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
tools:listitem="@layout/repo_item" />
|
||||
|
||||
</LinearLayout>
|
||||
Reference in New Issue
Block a user