mirror of
https://github.com/f-droid/fdroidclient.git
synced 2026-05-24 00:14:43 -04:00
Merge branch 'repo-details-compose' into 'master'
Refactor RepoDetailsActivity, move logic into ViewModel See merge request fdroid/fdroidclient!1489
This commit is contained in:
42
app/src/main/java/org/fdroid/fdroid/Utils.kt
Normal file
42
app/src/main/java/org/fdroid/fdroid/Utils.kt
Normal file
@@ -0,0 +1,42 @@
|
||||
package org.fdroid.fdroid
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.util.Log
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.view.DisplayCompat
|
||||
import com.google.zxing.BarcodeFormat
|
||||
import com.google.zxing.WriterException
|
||||
import com.google.zxing.encode.Contents
|
||||
import com.google.zxing.encode.QRCodeEncoder
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.fdroid.fdroid.Utils.debugLog
|
||||
import kotlin.math.min
|
||||
|
||||
private const val TAG = "Utils"
|
||||
|
||||
/**
|
||||
* Same as the Java function Utils.generateQrBitmap, but using coroutines instead of Single and Disposable.
|
||||
*/
|
||||
suspend fun generateQrBitmapKt(
|
||||
activity: AppCompatActivity,
|
||||
qrData: String,
|
||||
): Bitmap = withContext(Dispatchers.Default) {
|
||||
val displayMode = DisplayCompat.getMode(activity, activity.windowManager.getDefaultDisplay())
|
||||
val qrCodeDimension = min(displayMode.physicalWidth, displayMode.physicalHeight)
|
||||
debugLog(TAG, "generating QRCode Bitmap of " + qrCodeDimension + "x" + qrCodeDimension)
|
||||
|
||||
val encoder = QRCodeEncoder(
|
||||
qrData,
|
||||
null,
|
||||
Contents.Type.TEXT,
|
||||
BarcodeFormat.QR_CODE.toString(),
|
||||
qrCodeDimension,
|
||||
)
|
||||
return@withContext try {
|
||||
encoder.encodeAsBitmap()
|
||||
} catch (e: WriterException) {
|
||||
Log.e(TAG, "Could not encode QR as bitmap", e)
|
||||
Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,6 @@ import android.os.Bundle;
|
||||
import android.os.Parcelable;
|
||||
import android.text.TextUtils;
|
||||
import android.text.format.DateUtils;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
@@ -24,12 +23,13 @@ import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.core.app.NavUtils;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.lifecycle.ViewModelStoreOwner;
|
||||
import androidx.lifecycle.viewmodel.MutableCreationExtras;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
@@ -38,27 +38,18 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import com.google.android.material.materialswitch.MaterialSwitch;
|
||||
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.R;
|
||||
import org.fdroid.fdroid.Utils;
|
||||
import org.fdroid.fdroid.compat.LocaleCompat;
|
||||
import org.fdroid.fdroid.data.App;
|
||||
import org.fdroid.fdroid.data.DBHelper;
|
||||
import org.fdroid.fdroid.views.apps.AppListActivity;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
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";
|
||||
@@ -104,17 +95,10 @@ public class RepoDetailsActivity extends AppCompatActivity {
|
||||
private Repository repo;
|
||||
private long repoId;
|
||||
private View repoView;
|
||||
private String shareUrl;
|
||||
|
||||
private MirrorAdapter adapterToNotify;
|
||||
|
||||
private RepoDetailsViewModel model;
|
||||
// FIXME access to this could be moved into ViewModel
|
||||
private RepositoryDao repositoryDao;
|
||||
// FIXME access to this could be moved into ViewModel
|
||||
private AppDao appDao;
|
||||
@Nullable
|
||||
private Disposable disposable;
|
||||
|
||||
/**
|
||||
* Help function to make switching between two view states easier.
|
||||
@@ -132,11 +116,22 @@ public class RepoDetailsActivity extends AppCompatActivity {
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
FDroidApp fdroidApp = (FDroidApp) getApplication();
|
||||
fdroidApp.setSecureWindow(this);
|
||||
|
||||
fdroidApp.applyPureBlackBackgroundInDarkTheme(this);
|
||||
model = new ViewModelProvider(this).get(RepoDetailsViewModel.class);
|
||||
repositoryDao = DBHelper.getDb(this).getRepositoryDao();
|
||||
appDao = DBHelper.getDb(this).getAppDao();
|
||||
|
||||
repoId = getIntent().getLongExtra(ARG_REPO_ID, 0);
|
||||
repo = FDroidApp.getRepoManager(this).getRepository(repoId);
|
||||
if (repo == null) {
|
||||
// repo must have been deleted just now (maybe slow UI?)
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
ViewModelStoreOwner owner = this;
|
||||
ViewModelProvider.Factory factory = RepoDetailsViewModel.Companion.getFactory();
|
||||
MutableCreationExtras extras = new MutableCreationExtras();
|
||||
extras.set(RepoDetailsViewModel.Companion.getAPP_KEY(), getApplication());
|
||||
extras.set(RepoDetailsViewModel.Companion.getREPO_KEY(), repo);
|
||||
model = ViewModelProvider.create(owner, factory, extras).get(RepoDetailsViewModel.class);
|
||||
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
@@ -148,15 +143,6 @@ public class RepoDetailsActivity extends AppCompatActivity {
|
||||
|
||||
repoView = findViewById(R.id.repo_view);
|
||||
|
||||
repoId = getIntent().getLongExtra(ARG_REPO_ID, 0);
|
||||
model.initRepo(repoId);
|
||||
repo = FDroidApp.getRepoManager(this).getRepository(repoId);
|
||||
if (repo == null) {
|
||||
// repo must have been deleted just now (maybe slow UI?)
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
TextView inputUrl = findViewById(R.id.input_repo_url);
|
||||
inputUrl.setText(repo.getAddress());
|
||||
|
||||
@@ -171,28 +157,6 @@ public class RepoDetailsActivity extends AppCompatActivity {
|
||||
userMirrorAdapter.setUserMirrors(repo.getUserMirrors());
|
||||
userMirrorListView.setAdapter(userMirrorAdapter);
|
||||
|
||||
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.getAddress());
|
||||
try {
|
||||
if (repo.getFingerprint() != null) {
|
||||
uri = uri.buildUpon().appendQueryParameter("fingerprint", repo.getFingerprint()).build();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Invalid repo fingerprint: " + repo.getAddress());
|
||||
}
|
||||
String qrUriString = uri.toString();
|
||||
disposable = Utils.generateQrBitmap(this, qrUriString)
|
||||
.subscribe(bitmap -> {
|
||||
final ImageView qrCode = findViewById(R.id.qr_code);
|
||||
if (qrCode != null) {
|
||||
qrCode.setImageBitmap(bitmap);
|
||||
}
|
||||
});
|
||||
|
||||
// update UI when repo in DB changes
|
||||
model.getRepoLiveData().observe(this, repo -> {
|
||||
if (repo == null) {
|
||||
@@ -204,23 +168,37 @@ public class RepoDetailsActivity extends AppCompatActivity {
|
||||
updateRepoView();
|
||||
});
|
||||
|
||||
TextView numApps = repoView.findViewById(R.id.text_num_apps);
|
||||
model.getNumberOfAppsLiveData().observe(this, number -> {
|
||||
String countStr = String.format(LocaleCompat.getDefault(), "%d", number);
|
||||
numApps.setText(countStr);
|
||||
});
|
||||
|
||||
MaterialSwitch archiveRepoSwitch = findViewById(R.id.archiveRepo);
|
||||
model.getLiveData().observe(this, s -> {
|
||||
Boolean enabled = s.getArchiveEnabled();
|
||||
if (enabled == null) {
|
||||
archiveRepoSwitch.setEnabled(false);
|
||||
} else {
|
||||
archiveRepoSwitch.setEnabled(true);
|
||||
archiveRepoSwitch.setChecked(enabled);
|
||||
switch (s.getArchiveState()) {
|
||||
case ENABLED:
|
||||
archiveRepoSwitch.setEnabled(true);
|
||||
archiveRepoSwitch.setChecked(true);
|
||||
break;
|
||||
case DISABLED:
|
||||
archiveRepoSwitch.setEnabled(true);
|
||||
archiveRepoSwitch.setChecked(false);
|
||||
break;
|
||||
case UNKNOWN:
|
||||
archiveRepoSwitch.setEnabled(false);
|
||||
break;
|
||||
}
|
||||
});
|
||||
archiveRepoSwitch.setOnClickListener(v -> model.setArchiveRepoEnabled(repo, archiveRepoSwitch.isChecked()));
|
||||
}
|
||||
archiveRepoSwitch.setOnClickListener(v -> model.setArchiveRepoEnabled(archiveRepoSwitch.isChecked()));
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
if (disposable != null) disposable.dispose();
|
||||
super.onDestroy();
|
||||
ImageView qrCode = findViewById(R.id.qr_code);
|
||||
model.getQrCodeLiveData().observe(this, bitmap -> {
|
||||
if (qrCode != null) {
|
||||
qrCode.setImageBitmap(bitmap);
|
||||
}
|
||||
});
|
||||
model.generateQrCode(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -279,7 +257,7 @@ public class RepoDetailsActivity extends AppCompatActivity {
|
||||
} else if (itemId == R.id.action_share) {
|
||||
intent = new Intent(Intent.ACTION_SEND);
|
||||
intent.setType("text/plain");
|
||||
intent.putExtra(Intent.EXTRA_TEXT, shareUrl);
|
||||
intent.putExtra(Intent.EXTRA_TEXT, repo.getShareUri());
|
||||
startActivity(Intent.createChooser(intent,
|
||||
getResources().getString(R.string.share_repository)));
|
||||
}
|
||||
@@ -287,31 +265,6 @@ public class RepoDetailsActivity extends AppCompatActivity {
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPrepareOptionsMenu(Menu menu) {
|
||||
prepareShareMenuItems(menu);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void prepareShareMenuItems(Menu menu) {
|
||||
if (!TextUtils.isEmpty(repo.getAddress())) {
|
||||
if (!TextUtils.isEmpty(repo.getCertificate())) {
|
||||
try {
|
||||
shareUrl = Uri.parse(repo.getAddress()).buildUpon()
|
||||
.appendQueryParameter("fingerprint", repo.getFingerprint()).toString();
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Invalid repo fingerprint: " + repo.getAddress());
|
||||
shareUrl = repo.getAddress();
|
||||
}
|
||||
} else {
|
||||
shareUrl = repo.getAddress();
|
||||
}
|
||||
menu.findItem(R.id.action_share).setVisible(true);
|
||||
} else {
|
||||
menu.findItem(R.id.action_share).setVisible(false);
|
||||
}
|
||||
}
|
||||
|
||||
private void setupDescription(View parent, Repository repo) {
|
||||
|
||||
TextView descriptionLabel = parent.findViewById(R.id.label_description);
|
||||
@@ -405,20 +358,11 @@ public class RepoDetailsActivity extends AppCompatActivity {
|
||||
setMultipleViewVisibility(repoView, HIDE_IF_EXISTS, View.GONE);
|
||||
|
||||
TextView name = repoView.findViewById(R.id.text_repo_name);
|
||||
TextView numApps = repoView.findViewById(R.id.text_num_apps);
|
||||
TextView numAppsButton = repoView.findViewById(R.id.button_view_apps);
|
||||
TextView lastUpdated = repoView.findViewById(R.id.text_last_update);
|
||||
TextView lastDownloaded = repoView.findViewById(R.id.text_last_update_downloaded);
|
||||
|
||||
name.setText(repo.getName(App.getLocales()));
|
||||
// load number of apps in repo
|
||||
disposable = Single.fromCallable(() -> appDao.getNumberOfAppsInRepository(repoId))
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(appCount -> {
|
||||
String countStr = String.format(LocaleCompat.getDefault(), "%d", appCount);
|
||||
numApps.setText(countStr);
|
||||
});
|
||||
if (repo.getEnabled()) {
|
||||
numAppsButton.setOnClickListener(view -> {
|
||||
Intent i = new Intent(this, AppListActivity.class);
|
||||
@@ -457,10 +401,7 @@ public class RepoDetailsActivity extends AppCompatActivity {
|
||||
.setTitle(R.string.repo_confirm_delete_title)
|
||||
.setMessage(R.string.repo_confirm_delete_body)
|
||||
.setPositiveButton(R.string.delete, (dialog, which) -> {
|
||||
runOffUiThread(() -> {
|
||||
repositoryDao.deleteRepository(repoId);
|
||||
return true;
|
||||
});
|
||||
model.deleteRepository();
|
||||
finish();
|
||||
}).setNegativeButton(android.R.string.cancel, (dialog, which) -> {
|
||||
// Do nothing...
|
||||
@@ -490,10 +431,7 @@ public class RepoDetailsActivity extends AppCompatActivity {
|
||||
final String password = passwordInput.getText().toString();
|
||||
|
||||
if (!TextUtils.isEmpty(name)) {
|
||||
runOffUiThread(() -> {
|
||||
repositoryDao.updateUsernameAndPassword(repo.getRepoId(), name, password);
|
||||
return true;
|
||||
});
|
||||
model.updateUsernameAndPassword(name, password);
|
||||
updateRepoView();
|
||||
dialog.dismiss();
|
||||
} else {
|
||||
@@ -577,10 +515,7 @@ public class RepoDetailsActivity extends AppCompatActivity {
|
||||
adapterToNotify.notifyDataSetChanged();
|
||||
}
|
||||
ArrayList<String> toDisableMirrors = new ArrayList<>(disabledMirrors);
|
||||
runOffUiThread(() -> {
|
||||
repositoryDao.updateDisabledMirrors(repo.getRepoId(), toDisableMirrors);
|
||||
return true;
|
||||
});
|
||||
model.updateDisabledMirrors(toDisableMirrors);
|
||||
});
|
||||
|
||||
View repoUnverified = holder.view.findViewById(R.id.repo_unverified);
|
||||
@@ -598,11 +533,4 @@ public class RepoDetailsActivity extends AppCompatActivity {
|
||||
return mirrors.size();
|
||||
}
|
||||
}
|
||||
|
||||
private void runOffUiThread(Callable<?> r) {
|
||||
disposable = Single.fromCallable(r)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,70 +1,103 @@
|
||||
package org.fdroid.fdroid.views.repos
|
||||
|
||||
import android.app.Application
|
||||
import android.graphics.Bitmap
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import android.widget.Toast.LENGTH_SHORT
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.asLiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.lifecycle.viewmodel.CreationExtras
|
||||
import androidx.lifecycle.viewmodel.initializer
|
||||
import androidx.lifecycle.viewmodel.viewModelFactory
|
||||
import info.guardianproject.netcipher.NetCipher
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.fdroid.database.Repository
|
||||
import org.fdroid.fdroid.FDroidApp
|
||||
import org.fdroid.fdroid.R
|
||||
import org.fdroid.fdroid.data.DBHelper
|
||||
import org.fdroid.fdroid.generateQrBitmapKt
|
||||
import org.fdroid.fdroid.work.RepoUpdateWorker
|
||||
|
||||
data class RepoDetailsState(
|
||||
val repo: Repository?,
|
||||
val archiveEnabled: Boolean? = null,
|
||||
val repo: Repository,
|
||||
val archiveState: ArchiveState,
|
||||
)
|
||||
|
||||
class RepoDetailsViewModel(app: Application) : AndroidViewModel(app) {
|
||||
enum class ArchiveState {
|
||||
ENABLED,
|
||||
DISABLED,
|
||||
UNKNOWN,
|
||||
}
|
||||
|
||||
private val repoManager = FDroidApp.getRepoManager(app)
|
||||
private val _state = MutableStateFlow<RepoDetailsState?>(null)
|
||||
val state = _state.asStateFlow()
|
||||
val liveData = _state.asLiveData()
|
||||
class RepoDetailsViewModel(
|
||||
app: Application,
|
||||
initialRepo: Repository,
|
||||
) : AndroidViewModel(app) {
|
||||
|
||||
val repoLiveData = combine(_state, repoManager.repositoriesState) { s, reposState ->
|
||||
if (s?.repo == null) {
|
||||
null
|
||||
} else {
|
||||
reposState.find { repo -> repo.repoId == s.repo.repoId }
|
||||
}
|
||||
}.distinctUntilChanged().asLiveData()
|
||||
|
||||
fun initRepo(repoId: Long) {
|
||||
val repo = repoManager.getRepository(repoId)
|
||||
if (repo == null) {
|
||||
_state.value = RepoDetailsState(null)
|
||||
} else {
|
||||
_state.value = RepoDetailsState(
|
||||
repo = repo,
|
||||
archiveEnabled = repo.isArchiveEnabled(),
|
||||
)
|
||||
companion object {
|
||||
// TODO: Use androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.Companion.APPLICATION_KEY
|
||||
// That seems to require setting up dependency injection.
|
||||
val APP_KEY = object : CreationExtras.Key<Application> {}
|
||||
val REPO_KEY = object : CreationExtras.Key<Repository> {}
|
||||
val Factory = viewModelFactory {
|
||||
initializer {
|
||||
val app = this[APP_KEY] as Application
|
||||
val repo = this[REPO_KEY] as Repository
|
||||
RepoDetailsViewModel(app, repo)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun setArchiveRepoEnabled(repo: Repository, enabled: Boolean) {
|
||||
// archiveEnabled = null means we don't know current state, it's in progress
|
||||
_state.value = _state.value?.copy(archiveEnabled = null)
|
||||
private val repoManager = FDroidApp.getRepoManager(app)
|
||||
private val repositoryDao = DBHelper.getDb(app).getRepositoryDao()
|
||||
private val appDao = DBHelper.getDb(app).getAppDao()
|
||||
|
||||
private val _state = MutableStateFlow(
|
||||
RepoDetailsState(initialRepo, initialRepo.archiveState())
|
||||
)
|
||||
val state = _state.asStateFlow()
|
||||
val liveData = _state.asLiveData()
|
||||
|
||||
val repoFlow = combine(_state, repoManager.repositoriesState) { s, reposState ->
|
||||
reposState.find { repo -> repo.repoId == s.repo.repoId }
|
||||
}.distinctUntilChanged()
|
||||
val repoLiveData = repoFlow.asLiveData()
|
||||
|
||||
val numberAppsFlow: Flow<Int> = repoFlow.map { repo ->
|
||||
if (repo != null) {
|
||||
appDao.getNumberOfAppsInRepository(repo.repoId)
|
||||
} else 0
|
||||
}.flowOn(Dispatchers.IO).distinctUntilChanged()
|
||||
val numberOfAppsLiveData = numberAppsFlow.asLiveData()
|
||||
|
||||
val qrCodeLiveData = MutableLiveData<Bitmap?>(null)
|
||||
|
||||
fun setArchiveRepoEnabled(enabled: Boolean) {
|
||||
val repo = _state.value.repo
|
||||
_state.value = _state.value.copy(archiveState = ArchiveState.UNKNOWN)
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
val repoId = repoManager.setArchiveRepoEnabled(repo, enabled, NetCipher.getProxy())
|
||||
_state.value = _state.value?.copy(archiveEnabled = enabled)
|
||||
_state.value = _state.value.copy(archiveState = enabled.toArchiveState())
|
||||
if (enabled && repoId != null) withContext(Dispatchers.Main) {
|
||||
RepoUpdateWorker.updateNow(getApplication(), repoId)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(this.javaClass.simpleName, "Error toggling archive repo: ", e)
|
||||
_state.value = _state.value?.copy(archiveEnabled = repo.isArchiveEnabled())
|
||||
_state.value = _state.value.copy(archiveState = repo.archiveState())
|
||||
withContext(Dispatchers.Main) {
|
||||
Toast.makeText(getApplication(), R.string.repo_archive_failed, LENGTH_SHORT)
|
||||
.show()
|
||||
@@ -73,10 +106,55 @@ class RepoDetailsViewModel(app: Application) : AndroidViewModel(app) {
|
||||
}
|
||||
}
|
||||
|
||||
private fun Repository.isArchiveEnabled(): Boolean {
|
||||
return repoManager.getRepositories().find { r ->
|
||||
r.isArchiveRepo && r.certificate == certificate
|
||||
}?.enabled ?: false
|
||||
fun deleteRepository() {
|
||||
val repoId = _state.value.repo.repoId
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
repositoryDao.deleteRepository(repoId)
|
||||
}
|
||||
}
|
||||
|
||||
fun updateUsernameAndPassword(username: String, password: String) {
|
||||
val repoId = _state.value.repo.repoId
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
repositoryDao.updateUsernameAndPassword(repoId, username, password)
|
||||
}
|
||||
}
|
||||
|
||||
fun updateDisabledMirrors(toDisable: List<String>) {
|
||||
val repoId = _state.value.repo.repoId
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
repositoryDao.updateDisabledMirrors(repoId, toDisable)
|
||||
}
|
||||
}
|
||||
|
||||
private fun Repository.archiveState(): ArchiveState {
|
||||
val isEnabled = repoManager.getRepositories().find { r ->
|
||||
r.isArchiveRepo && r.certificate == certificate
|
||||
}?.enabled
|
||||
return when (isEnabled) {
|
||||
true -> ArchiveState.ENABLED
|
||||
false -> ArchiveState.DISABLED
|
||||
null -> ArchiveState.UNKNOWN
|
||||
}
|
||||
}
|
||||
|
||||
private fun Boolean.toArchiveState(): ArchiveState {
|
||||
return if (this) ArchiveState.ENABLED else ArchiveState.DISABLED
|
||||
}
|
||||
|
||||
// TODO: initialise this once on ViewModel creation, and don't take an Activity, do fixed size
|
||||
fun generateQrCode(activity: AppCompatActivity) {
|
||||
val repo = _state.value.repo
|
||||
if (repo.address.startsWith("content://") || repo.address.startsWith("file://")) {
|
||||
// no need to show a QR Code, it is not shareable
|
||||
qrCodeLiveData.value = null
|
||||
return
|
||||
}
|
||||
viewModelScope.launch(Dispatchers.Default) {
|
||||
val bitmap = generateQrBitmapKt(activity, repo.shareUri)
|
||||
withContext(Dispatchers.Main) {
|
||||
qrCodeLiveData.value = bitmap
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
package org.fdroid.database
|
||||
|
||||
import android.net.Uri
|
||||
import android.util.Log
|
||||
import androidx.annotation.WorkerThread
|
||||
import androidx.core.os.LocaleListCompat
|
||||
import androidx.room.Embedded
|
||||
import androidx.room.Entity
|
||||
@@ -19,6 +22,8 @@ import org.fdroid.index.v2.MirrorV2
|
||||
import org.fdroid.index.v2.ReleaseChannelV2
|
||||
import org.fdroid.index.v2.RepoV2
|
||||
|
||||
private const val TAG = "Repository"
|
||||
|
||||
@Entity(tableName = CoreRepository.TABLE)
|
||||
internal data class CoreRepository(
|
||||
@PrimaryKey(autoGenerate = true) val repoId: Long = 0,
|
||||
@@ -214,6 +219,20 @@ public data class Repository internal constructor(
|
||||
add(0, org.fdroid.download.Mirror(address))
|
||||
}
|
||||
}
|
||||
|
||||
val shareUri: String
|
||||
@WorkerThread
|
||||
get() {
|
||||
var uri = Uri.parse(address)
|
||||
fingerprint?.let {
|
||||
try {
|
||||
uri = uri.buildUpon().appendQueryParameter("fingerprint", it).build()
|
||||
} catch (e: UnsupportedOperationException) {
|
||||
Log.e(TAG, "Failed to append fingerprint to URI: $e")
|
||||
}
|
||||
}
|
||||
return uri.toString()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user