mirror of
https://github.com/nextcloud/android.git
synced 2024-12-21 16:24:32 +03:00
Merge remote-tracking branch 'origin/master' into dev
This commit is contained in:
commit
5c86b142d8
17 changed files with 768 additions and 771 deletions
8
.github/workflows/reuse.yml
vendored
8
.github/workflows/reuse.yml
vendored
|
@ -9,12 +9,14 @@
|
||||||
|
|
||||||
name: REUSE Compliance Check
|
name: REUSE Compliance Check
|
||||||
|
|
||||||
on: pull_request
|
on: [pull_request]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
reuse-compliance-check:
|
reuse-compliance-check:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
- name: Checkout
|
||||||
|
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||||
|
|
||||||
- name: REUSE Compliance Check
|
- name: REUSE Compliance Check
|
||||||
uses: fsfe/reuse-action@a46482ca367aef4454a87620aa37c2be4b2f8106 # v3.0.0
|
uses: fsfe/reuse-action@3ae3c6bdf1257ab19397fab11fd3312144692083 # v4.0.0
|
||||||
|
|
|
@ -299,7 +299,7 @@ dependencies {
|
||||||
implementation 'com.googlecode.ez-vcard:ez-vcard:0.12.1'
|
implementation 'com.googlecode.ez-vcard:ez-vcard:0.12.1'
|
||||||
implementation 'org.lukhnos:nnio:0.3.1'
|
implementation 'org.lukhnos:nnio:0.3.1'
|
||||||
implementation 'org.bouncycastle:bcpkix-jdk18on:1.78.1'
|
implementation 'org.bouncycastle:bcpkix-jdk18on:1.78.1'
|
||||||
implementation 'com.google.code.gson:gson:2.10.1'
|
implementation 'com.google.code.gson:gson:2.11.0'
|
||||||
implementation 'com.github.nextcloud-deps:sectioned-recyclerview:0.6.1'
|
implementation 'com.github.nextcloud-deps:sectioned-recyclerview:0.6.1'
|
||||||
implementation 'com.github.chrisbanes:PhotoView:2.3.0'
|
implementation 'com.github.chrisbanes:PhotoView:2.3.0'
|
||||||
implementation 'pl.droidsonroids.gif:android-gif-drawable:1.2.28'
|
implementation 'pl.droidsonroids.gif:android-gif-drawable:1.2.28'
|
||||||
|
@ -310,7 +310,7 @@ dependencies {
|
||||||
}
|
}
|
||||||
implementation 'com.caverock:androidsvg:1.4'
|
implementation 'com.caverock:androidsvg:1.4'
|
||||||
implementation 'androidx.annotation:annotation:1.8.0'
|
implementation 'androidx.annotation:annotation:1.8.0'
|
||||||
implementation 'com.vanniktech:emoji-google:0.18.0'
|
implementation 'com.vanniktech:emoji-google:0.21.0'
|
||||||
|
|
||||||
implementation "com.github.nextcloud-deps.hwsecurity:hwsecurity-fido:$fidoVersion"
|
implementation "com.github.nextcloud-deps.hwsecurity:hwsecurity-fido:$fidoVersion"
|
||||||
implementation "com.github.nextcloud-deps.hwsecurity:hwsecurity-fido2:$fidoVersion"
|
implementation "com.github.nextcloud-deps.hwsecurity:hwsecurity-fido2:$fidoVersion"
|
||||||
|
@ -378,11 +378,11 @@ dependencies {
|
||||||
|
|
||||||
// dependencies for instrumented tests
|
// dependencies for instrumented tests
|
||||||
// JUnit4 Rules
|
// JUnit4 Rules
|
||||||
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
|
androidTestImplementation 'androidx.test.ext:junit:1.2.1'
|
||||||
androidTestImplementation "androidx.test:rules:$androidxTestVersion"
|
androidTestImplementation "androidx.test:rules:$androidxTestVersion"
|
||||||
// Android JUnit Runner
|
// Android JUnit Runner
|
||||||
androidTestImplementation "androidx.test:runner:1.5.2"
|
androidTestImplementation "androidx.test:runner:1.6.1"
|
||||||
androidTestUtil "androidx.test:orchestrator:1.4.2"
|
androidTestUtil "androidx.test:orchestrator:1.5.0"
|
||||||
androidTestImplementation "androidx.test:core-ktx:$androidxTestVersion"
|
androidTestImplementation "androidx.test:core-ktx:$androidxTestVersion"
|
||||||
|
|
||||||
// Espresso
|
// Espresso
|
||||||
|
|
|
@ -182,7 +182,7 @@ class FilesSyncWork(
|
||||||
} else {
|
} else {
|
||||||
// Check every file in synced folder for changes and update
|
// Check every file in synced folder for changes and update
|
||||||
// filesystemDataProvider database (potentially needs a long time)
|
// filesystemDataProvider database (potentially needs a long time)
|
||||||
FilesSyncHelper.insertAllDBEntries(syncedFolder, powerManagementService)
|
FilesSyncHelper.insertAllDBEntriesForSyncedFolder(syncedFolder)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,9 +12,11 @@ import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import com.nextcloud.client.account.User
|
import com.nextcloud.client.account.User
|
||||||
import com.nextcloud.client.account.UserAccountManager
|
import com.nextcloud.client.account.UserAccountManager
|
||||||
|
import com.nextcloud.client.device.BatteryStatus
|
||||||
import com.nextcloud.client.device.PowerManagementService
|
import com.nextcloud.client.device.PowerManagementService
|
||||||
import com.nextcloud.client.jobs.BackgroundJobManager
|
import com.nextcloud.client.jobs.BackgroundJobManager
|
||||||
import com.nextcloud.client.jobs.upload.FileUploadWorker.Companion.currentUploadFileOperation
|
import com.nextcloud.client.jobs.upload.FileUploadWorker.Companion.currentUploadFileOperation
|
||||||
|
import com.nextcloud.client.network.Connectivity
|
||||||
import com.nextcloud.client.network.ConnectivityService
|
import com.nextcloud.client.network.ConnectivityService
|
||||||
import com.owncloud.android.MainApp
|
import com.owncloud.android.MainApp
|
||||||
import com.owncloud.android.datamodel.OCFile
|
import com.owncloud.android.datamodel.OCFile
|
||||||
|
@ -28,9 +30,9 @@ import com.owncloud.android.lib.common.operations.RemoteOperationResult
|
||||||
import com.owncloud.android.lib.common.utils.Log_OC
|
import com.owncloud.android.lib.common.utils.Log_OC
|
||||||
import com.owncloud.android.lib.resources.files.ReadFileRemoteOperation
|
import com.owncloud.android.lib.resources.files.ReadFileRemoteOperation
|
||||||
import com.owncloud.android.lib.resources.files.model.RemoteFile
|
import com.owncloud.android.lib.resources.files.model.RemoteFile
|
||||||
|
import com.owncloud.android.operations.UploadFileOperation
|
||||||
import com.owncloud.android.utils.FileUtil
|
import com.owncloud.android.utils.FileUtil
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.util.Optional
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@Suppress("TooManyFunctions")
|
@Suppress("TooManyFunctions")
|
||||||
|
@ -117,34 +119,44 @@ class FileUploadHelper {
|
||||||
failedUploads: Array<OCUpload>
|
failedUploads: Array<OCUpload>
|
||||||
): Boolean {
|
): Boolean {
|
||||||
var showNotExistMessage = false
|
var showNotExistMessage = false
|
||||||
val (gotNetwork, _, gotWifi) = connectivityService.connectivity
|
val isOnline = checkConnectivity(connectivityService)
|
||||||
|
val connectivity = connectivityService.connectivity
|
||||||
val batteryStatus = powerManagementService.battery
|
val batteryStatus = powerManagementService.battery
|
||||||
val charging = batteryStatus.isCharging || batteryStatus.isFull
|
val accountNames = accountManager.accounts.filter { account ->
|
||||||
val isPowerSaving = powerManagementService.isPowerSavingEnabled
|
accountManager.getUser(account.name).isPresent
|
||||||
var uploadUser = Optional.empty<User>()
|
}.map { account ->
|
||||||
|
account.name
|
||||||
|
}.toHashSet()
|
||||||
|
|
||||||
for (failedUpload in failedUploads) {
|
for (failedUpload in failedUploads) {
|
||||||
val isDeleted = !File(failedUpload.localPath).exists()
|
if (!accountNames.contains(failedUpload.accountName)) {
|
||||||
if (isDeleted) {
|
uploadsStorageManager.removeUpload(failedUpload)
|
||||||
showNotExistMessage = true
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
// 2A. for deleted files, mark as permanently failed
|
val uploadResult =
|
||||||
if (failedUpload.lastResult != UploadResult.FILE_NOT_FOUND) {
|
checkUploadConditions(failedUpload, connectivity, batteryStatus, powerManagementService, isOnline)
|
||||||
failedUpload.lastResult = UploadResult.FILE_NOT_FOUND
|
|
||||||
|
if (uploadResult != UploadResult.UPLOADED) {
|
||||||
|
if (failedUpload.lastResult != uploadResult) {
|
||||||
|
failedUpload.lastResult = uploadResult
|
||||||
uploadsStorageManager.updateUpload(failedUpload)
|
uploadsStorageManager.updateUpload(failedUpload)
|
||||||
}
|
}
|
||||||
} else if (!isPowerSaving && gotNetwork &&
|
if (uploadResult == UploadResult.FILE_NOT_FOUND) {
|
||||||
canUploadBeRetried(failedUpload, gotWifi, charging) && !connectivityService.isInternetWalled
|
showNotExistMessage = true
|
||||||
) {
|
}
|
||||||
// 2B. for existing local files, try restarting it if possible
|
continue
|
||||||
failedUpload.uploadStatus = UploadStatus.UPLOAD_IN_PROGRESS
|
|
||||||
uploadsStorageManager.updateUpload(failedUpload)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
failedUpload.uploadStatus = UploadStatus.UPLOAD_IN_PROGRESS
|
||||||
|
uploadsStorageManager.updateUpload(failedUpload)
|
||||||
}
|
}
|
||||||
|
|
||||||
accountManager.accounts.forEach {
|
accountNames.forEach { accountName ->
|
||||||
val user = accountManager.getUser(it.name)
|
val user = accountManager.getUser(accountName)
|
||||||
if (user.isPresent) backgroundJobManager.startFilesUploadJob(user.get())
|
if (user.isPresent) {
|
||||||
|
backgroundJobManager.startFilesUploadJob(user.get())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return showNotExistMessage
|
return showNotExistMessage
|
||||||
|
@ -216,11 +228,50 @@ class FileUploadHelper {
|
||||||
return upload.uploadStatus == UploadStatus.UPLOAD_IN_PROGRESS
|
return upload.uploadStatus == UploadStatus.UPLOAD_IN_PROGRESS
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun canUploadBeRetried(upload: OCUpload, gotWifi: Boolean, isCharging: Boolean): Boolean {
|
private fun checkConnectivity(connectivityService: ConnectivityService): Boolean {
|
||||||
val file = File(upload.localPath)
|
// check that connection isn't walled off and that the server is reachable
|
||||||
val needsWifi = upload.isUseWifiOnly
|
return connectivityService.getConnectivity().isConnected && !connectivityService.isInternetWalled()
|
||||||
val needsCharging = upload.isWhileChargingOnly
|
}
|
||||||
return file.exists() && (!needsWifi || gotWifi) && (!needsCharging || isCharging)
|
|
||||||
|
/**
|
||||||
|
* Dupe of [UploadFileOperation.checkConditions], needed to check if the upload should even be scheduled
|
||||||
|
* @return [UploadResult.UPLOADED] if the upload should be scheduled, otherwise the reason why it shouldn't
|
||||||
|
*/
|
||||||
|
private fun checkUploadConditions(
|
||||||
|
upload: OCUpload,
|
||||||
|
connectivity: Connectivity,
|
||||||
|
battery: BatteryStatus,
|
||||||
|
powerManagementService: PowerManagementService,
|
||||||
|
hasGeneralConnection: Boolean
|
||||||
|
): UploadResult {
|
||||||
|
var conditions = UploadResult.UPLOADED
|
||||||
|
|
||||||
|
// check that internet is available
|
||||||
|
if (!hasGeneralConnection) {
|
||||||
|
conditions = UploadResult.NETWORK_CONNECTION
|
||||||
|
}
|
||||||
|
|
||||||
|
// check that local file exists; skip the upload otherwise
|
||||||
|
if (!File(upload.localPath).exists()) {
|
||||||
|
conditions = UploadResult.FILE_NOT_FOUND
|
||||||
|
}
|
||||||
|
|
||||||
|
// check that connectivity conditions are met; delay upload otherwise
|
||||||
|
if (upload.isUseWifiOnly && (!connectivity.isWifi || connectivity.isMetered)) {
|
||||||
|
conditions = UploadResult.DELAYED_FOR_WIFI
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if charging conditions are met; delay upload otherwise
|
||||||
|
if (upload.isWhileChargingOnly && !battery.isCharging && !battery.isFull) {
|
||||||
|
conditions = UploadResult.DELAYED_FOR_CHARGING
|
||||||
|
}
|
||||||
|
|
||||||
|
// check that device is not in power save mode; delay upload otherwise
|
||||||
|
if (powerManagementService.isPowerSavingEnabled) {
|
||||||
|
conditions = UploadResult.DELAYED_IN_POWER_SAVE_MODE
|
||||||
|
}
|
||||||
|
|
||||||
|
return conditions
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("ReturnCount")
|
@Suppress("ReturnCount")
|
||||||
|
|
|
@ -130,7 +130,7 @@ class FileUploadWorker(
|
||||||
@Suppress("ReturnCount")
|
@Suppress("ReturnCount")
|
||||||
private fun retrievePagesBySortingUploadsByID(): Result {
|
private fun retrievePagesBySortingUploadsByID(): Result {
|
||||||
val accountName = inputData.getString(ACCOUNT) ?: return Result.failure()
|
val accountName = inputData.getString(ACCOUNT) ?: return Result.failure()
|
||||||
var uploadsPerPage = uploadsStorageManager.getCurrentAndPendingUploadsForAccountPageAscById(-1, accountName)
|
var uploadsPerPage = uploadsStorageManager.getCurrentUploadsForAccountPageAscById(-1, accountName)
|
||||||
val totalUploadSize = uploadsStorageManager.getTotalUploadSize(accountName)
|
val totalUploadSize = uploadsStorageManager.getTotalUploadSize(accountName)
|
||||||
|
|
||||||
Log_OC.d(TAG, "Total upload size: $totalUploadSize")
|
Log_OC.d(TAG, "Total upload size: $totalUploadSize")
|
||||||
|
@ -148,7 +148,7 @@ class FileUploadWorker(
|
||||||
val lastId = uploadsPerPage.last().uploadId
|
val lastId = uploadsPerPage.last().uploadId
|
||||||
uploadFiles(totalUploadSize, uploadsPerPage, accountName)
|
uploadFiles(totalUploadSize, uploadsPerPage, accountName)
|
||||||
uploadsPerPage =
|
uploadsPerPage =
|
||||||
uploadsStorageManager.getCurrentAndPendingUploadsForAccountPageAscById(lastId, accountName)
|
uploadsStorageManager.getCurrentUploadsForAccountPageAscById(lastId, accountName)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isStopped) {
|
if (isStopped) {
|
||||||
|
|
|
@ -471,7 +471,7 @@ public class UploadsStorageManager extends Observable {
|
||||||
return getUploadPage(QUERY_PAGE_SIZE, afterId, true, selection, selectionArgs);
|
return getUploadPage(QUERY_PAGE_SIZE, afterId, true, selection, selectionArgs);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getInProgressUploadsSelection() {
|
private String getInProgressAndDelayedUploadsSelection() {
|
||||||
return "( " + ProviderTableMeta.UPLOADS_STATUS + EQUAL + UploadStatus.UPLOAD_IN_PROGRESS.value +
|
return "( " + ProviderTableMeta.UPLOADS_STATUS + EQUAL + UploadStatus.UPLOAD_IN_PROGRESS.value +
|
||||||
OR + ProviderTableMeta.UPLOADS_LAST_RESULT +
|
OR + ProviderTableMeta.UPLOADS_LAST_RESULT +
|
||||||
EQUAL + UploadResult.DELAYED_FOR_WIFI.getValue() +
|
EQUAL + UploadResult.DELAYED_FOR_WIFI.getValue() +
|
||||||
|
@ -485,7 +485,8 @@ public class UploadsStorageManager extends Observable {
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getTotalUploadSize(@Nullable String... selectionArgs) {
|
public int getTotalUploadSize(@Nullable String... selectionArgs) {
|
||||||
final String selection = getInProgressUploadsSelection();
|
final String selection = ProviderTableMeta.UPLOADS_STATUS + EQUAL + UploadStatus.UPLOAD_IN_PROGRESS.value +
|
||||||
|
AND + ProviderTableMeta.UPLOADS_ACCOUNT_NAME + IS_EQUAL;
|
||||||
int totalSize = 0;
|
int totalSize = 0;
|
||||||
|
|
||||||
Cursor cursor = getDB().query(
|
Cursor cursor = getDB().query(
|
||||||
|
@ -605,17 +606,29 @@ public class UploadsStorageManager extends Observable {
|
||||||
}
|
}
|
||||||
|
|
||||||
public OCUpload[] getCurrentAndPendingUploadsForAccount(final @NonNull String accountName) {
|
public OCUpload[] getCurrentAndPendingUploadsForAccount(final @NonNull String accountName) {
|
||||||
String inProgressUploadsSelection = getInProgressUploadsSelection();
|
String inProgressUploadsSelection = getInProgressAndDelayedUploadsSelection();
|
||||||
return getUploads(inProgressUploadsSelection, accountName);
|
return getUploads(inProgressUploadsSelection, accountName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public OCUpload[] getCurrentUploadsForAccount(final @NonNull String accountName) {
|
||||||
|
return getUploads(ProviderTableMeta.UPLOADS_STATUS + EQUAL + UploadStatus.UPLOAD_IN_PROGRESS.value + AND +
|
||||||
|
ProviderTableMeta.UPLOADS_ACCOUNT_NAME + IS_EQUAL, accountName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<OCUpload> getCurrentUploadsForAccountPageAscById(final long afterId, final @NonNull String accountName) {
|
||||||
|
final String selection = ProviderTableMeta.UPLOADS_STATUS + EQUAL + UploadStatus.UPLOAD_IN_PROGRESS.value +
|
||||||
|
AND + ProviderTableMeta.UPLOADS_ACCOUNT_NAME + IS_EQUAL;
|
||||||
|
return getUploadPage(QUERY_PAGE_SIZE, afterId, false, selection, accountName);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a page of uploads after <code>afterId</code>, where uploads are sorted by ascending upload id.
|
* Gets a page of uploads after <code>afterId</code>, where uploads are sorted by ascending upload id.
|
||||||
* <p>
|
* <p>
|
||||||
* If <code>afterId</code> is -1, returns the first page
|
* If <code>afterId</code> is -1, returns the first page
|
||||||
*/
|
*/
|
||||||
public List<OCUpload> getCurrentAndPendingUploadsForAccountPageAscById(final long afterId, final @NonNull String accountName) {
|
public List<OCUpload> getCurrentAndPendingUploadsForAccountPageAscById(final long afterId, final @NonNull String accountName) {
|
||||||
final String selection = getInProgressUploadsSelection();
|
final String selection = getInProgressAndDelayedUploadsSelection();
|
||||||
return getUploadPage(QUERY_PAGE_SIZE, afterId, false, selection, accountName);
|
return getUploadPage(QUERY_PAGE_SIZE, afterId, false, selection, accountName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -150,7 +150,7 @@ class ConflictsResolveActivity : FileActivity(), OnConflictDecisionMadeListener
|
||||||
outState.putInt(EXTRA_LOCAL_BEHAVIOUR, localBehaviour)
|
outState.putInt(EXTRA_LOCAL_BEHAVIOUR, localBehaviour)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun conflictDecisionMade(decision: Decision) {
|
override fun conflictDecisionMade(decision: Decision?) {
|
||||||
listener?.conflictDecisionMade(decision)
|
listener?.conflictDecisionMade(decision)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -205,10 +205,10 @@ class ConflictsResolveActivity : FileActivity(), OnConflictDecisionMadeListener
|
||||||
if (prev != null) {
|
if (prev != null) {
|
||||||
fragmentTransaction.remove(prev)
|
fragmentTransaction.remove(prev)
|
||||||
}
|
}
|
||||||
if (existingFile != null && storageManager.fileExists(remotePath)) {
|
if (existingFile != null && storageManager.fileExists(remotePath) && newFile != null) {
|
||||||
val dialog = ConflictsResolveDialog.newInstance(
|
val dialog = ConflictsResolveDialog.newInstance(
|
||||||
existingFile,
|
existingFile,
|
||||||
newFile,
|
newFile!!,
|
||||||
userOptional.get()
|
userOptional.get()
|
||||||
)
|
)
|
||||||
dialog.show(fragmentTransaction, "conflictDialog")
|
dialog.show(fragmentTransaction, "conflictDialog")
|
||||||
|
|
|
@ -1,276 +0,0 @@
|
||||||
/*
|
|
||||||
* Nextcloud - Android Client
|
|
||||||
*
|
|
||||||
* SPDX-FileCopyrightText: 2023 Alper Ozturk <alper.ozturk@nextcloud.com>
|
|
||||||
* SPDX-FileCopyrightText: 2020-2022 Tobias Kaminsky <tobias@kaminsky.me>
|
|
||||||
* SPDX-FileCopyrightText: 2015 ownCloud Inc.
|
|
||||||
* SPDX-FileCopyrightText: 2012 Bartosz Przybylski <bart.p.pl@gmail.com>
|
|
||||||
* SPDX-License-Identifier: GPL-2.0-only AND (AGPL-3.0-or-later OR GPL-2.0-only)
|
|
||||||
*/
|
|
||||||
package com.owncloud.android.ui.dialog;
|
|
||||||
|
|
||||||
import android.app.Dialog;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.DialogInterface;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.view.View;
|
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
import com.google.android.material.button.MaterialButton;
|
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
|
||||||
import com.nextcloud.client.account.User;
|
|
||||||
import com.nextcloud.client.di.Injectable;
|
|
||||||
import com.nextcloud.utils.extensions.BundleExtensionsKt;
|
|
||||||
import com.nextcloud.utils.extensions.FileExtensionsKt;
|
|
||||||
import com.owncloud.android.R;
|
|
||||||
import com.owncloud.android.databinding.ConflictResolveDialogBinding;
|
|
||||||
import com.owncloud.android.datamodel.FileDataStorageManager;
|
|
||||||
import com.owncloud.android.datamodel.OCFile;
|
|
||||||
import com.owncloud.android.datamodel.SyncedFolderProvider;
|
|
||||||
import com.owncloud.android.datamodel.ThumbnailsCacheManager;
|
|
||||||
import com.owncloud.android.lib.common.utils.Log_OC;
|
|
||||||
import com.owncloud.android.ui.adapter.LocalFileListAdapter;
|
|
||||||
import com.owncloud.android.utils.DisplayUtils;
|
|
||||||
import com.owncloud.android.utils.theme.ViewThemeUtils;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.appcompat.app.AlertDialog;
|
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
|
||||||
import androidx.fragment.app.DialogFragment;
|
|
||||||
import androidx.fragment.app.Fragment;
|
|
||||||
import androidx.fragment.app.FragmentTransaction;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Dialog which will be displayed to user upon keep-in-sync file conflict.
|
|
||||||
*/
|
|
||||||
public class ConflictsResolveDialog extends DialogFragment implements Injectable {
|
|
||||||
|
|
||||||
private ConflictResolveDialogBinding binding;
|
|
||||||
|
|
||||||
private OCFile existingFile;
|
|
||||||
private File newFile;
|
|
||||||
public OnConflictDecisionMadeListener listener;
|
|
||||||
private User user;
|
|
||||||
private final List<ThumbnailsCacheManager.ThumbnailGenerationTask> asyncTasks = new ArrayList<>();
|
|
||||||
private MaterialButton positiveButton;
|
|
||||||
@Inject ViewThemeUtils viewThemeUtils;
|
|
||||||
@Inject SyncedFolderProvider syncedFolderProvider;
|
|
||||||
|
|
||||||
private static final String TAG = "ConflictsResolveDialog";
|
|
||||||
private static final String KEY_NEW_FILE = "file";
|
|
||||||
private static final String KEY_EXISTING_FILE = "ocfile";
|
|
||||||
private static final String KEY_USER = "user";
|
|
||||||
|
|
||||||
public enum Decision {
|
|
||||||
CANCEL,
|
|
||||||
KEEP_BOTH,
|
|
||||||
KEEP_LOCAL,
|
|
||||||
KEEP_SERVER,
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ConflictsResolveDialog newInstance(OCFile existingFile, OCFile newFile, User user) {
|
|
||||||
ConflictsResolveDialog dialog = new ConflictsResolveDialog();
|
|
||||||
|
|
||||||
Bundle args = new Bundle();
|
|
||||||
args.putParcelable(KEY_EXISTING_FILE, existingFile);
|
|
||||||
|
|
||||||
File file = new File(newFile.getStoragePath());
|
|
||||||
FileExtensionsKt.logFileSize(file, TAG);
|
|
||||||
args.putSerializable(KEY_NEW_FILE, file);
|
|
||||||
args.putParcelable(KEY_USER, user);
|
|
||||||
dialog.setArguments(args);
|
|
||||||
|
|
||||||
return dialog;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAttach(@NonNull Context context) {
|
|
||||||
super.onAttach(context);
|
|
||||||
|
|
||||||
try {
|
|
||||||
listener = (OnConflictDecisionMadeListener) context;
|
|
||||||
} catch (ClassCastException e) {
|
|
||||||
throw new ClassCastException("Activity of this dialog must implement OnConflictDecisionMadeListener");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onStart() {
|
|
||||||
super.onStart();
|
|
||||||
|
|
||||||
AlertDialog alertDialog = (AlertDialog) getDialog();
|
|
||||||
|
|
||||||
if (alertDialog == null) {
|
|
||||||
Toast.makeText(getContext(), "Failed to create conflict dialog", Toast.LENGTH_LONG).show();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
positiveButton = (MaterialButton) alertDialog.getButton(AlertDialog.BUTTON_POSITIVE);
|
|
||||||
MaterialButton negativeButton = (MaterialButton) alertDialog.getButton(AlertDialog.BUTTON_NEGATIVE);
|
|
||||||
|
|
||||||
viewThemeUtils.material.colorMaterialButtonPrimaryTonal(positiveButton);
|
|
||||||
viewThemeUtils.material.colorMaterialButtonPrimaryBorderless(negativeButton);
|
|
||||||
positiveButton.setEnabled(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
|
|
||||||
if (savedInstanceState != null) {
|
|
||||||
existingFile = BundleExtensionsKt.getParcelableArgument(savedInstanceState, KEY_EXISTING_FILE, OCFile.class);
|
|
||||||
newFile = BundleExtensionsKt.getSerializableArgument(savedInstanceState, KEY_NEW_FILE, File.class);
|
|
||||||
user = BundleExtensionsKt.getParcelableArgument(savedInstanceState, KEY_USER, User.class);
|
|
||||||
} else if (getArguments() != null) {
|
|
||||||
existingFile = BundleExtensionsKt.getParcelableArgument(getArguments(), KEY_EXISTING_FILE, OCFile.class);
|
|
||||||
newFile = BundleExtensionsKt.getSerializableArgument(getArguments(), KEY_NEW_FILE, File.class);
|
|
||||||
user = BundleExtensionsKt.getParcelableArgument(getArguments(), KEY_USER, User.class);
|
|
||||||
} else {
|
|
||||||
Toast.makeText(getContext(), "Failed to create conflict dialog", Toast.LENGTH_LONG).show();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSaveInstanceState(@NonNull Bundle outState) {
|
|
||||||
super.onSaveInstanceState(outState);
|
|
||||||
|
|
||||||
FileExtensionsKt.logFileSize(existingFile, TAG);
|
|
||||||
FileExtensionsKt.logFileSize(newFile, TAG);
|
|
||||||
outState.putParcelable(KEY_EXISTING_FILE, existingFile);
|
|
||||||
outState.putSerializable(KEY_NEW_FILE, newFile);
|
|
||||||
outState.putParcelable(KEY_USER, user);
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
|
||||||
// Inflate the layout for the dialog
|
|
||||||
binding = ConflictResolveDialogBinding.inflate(requireActivity().getLayoutInflater());
|
|
||||||
|
|
||||||
viewThemeUtils.platform.themeCheckbox(binding.newCheckbox);
|
|
||||||
viewThemeUtils.platform.themeCheckbox(binding.existingCheckbox);
|
|
||||||
|
|
||||||
// Build the dialog
|
|
||||||
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(requireActivity());
|
|
||||||
builder.setView(binding.getRoot())
|
|
||||||
.setPositiveButton(R.string.common_ok, (dialog, which) -> {
|
|
||||||
if (listener != null) {
|
|
||||||
if (binding.newCheckbox.isChecked() && binding.existingCheckbox.isChecked()) {
|
|
||||||
listener.conflictDecisionMade(Decision.KEEP_BOTH);
|
|
||||||
} else if (binding.newCheckbox.isChecked()) {
|
|
||||||
listener.conflictDecisionMade(Decision.KEEP_LOCAL);
|
|
||||||
} else if (binding.existingCheckbox.isChecked()) {
|
|
||||||
listener.conflictDecisionMade(Decision.KEEP_SERVER);
|
|
||||||
} // else do nothing
|
|
||||||
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.setNegativeButton(R.string.common_cancel, (dialog, which) -> {
|
|
||||||
if (listener != null) {
|
|
||||||
listener.conflictDecisionMade(Decision.CANCEL);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.setTitle(String.format(getString(R.string.conflict_file_headline), existingFile.getFileName()));
|
|
||||||
|
|
||||||
File parentFile = new File(existingFile.getRemotePath()).getParentFile();
|
|
||||||
if (parentFile != null) {
|
|
||||||
binding.in.setText(String.format(getString(R.string.in_folder), parentFile.getAbsolutePath()));
|
|
||||||
} else {
|
|
||||||
binding.in.setVisibility(View.GONE);
|
|
||||||
}
|
|
||||||
|
|
||||||
// set info for new file
|
|
||||||
binding.newSize.setText(DisplayUtils.bytesToHumanReadable(newFile.length()));
|
|
||||||
binding.newTimestamp.setText(DisplayUtils.getRelativeTimestamp(getContext(), newFile.lastModified()));
|
|
||||||
binding.newThumbnail.setTag(newFile.hashCode());
|
|
||||||
LocalFileListAdapter.setThumbnail(newFile,
|
|
||||||
binding.newThumbnail,
|
|
||||||
getContext(),
|
|
||||||
viewThemeUtils);
|
|
||||||
|
|
||||||
// set info for existing file
|
|
||||||
binding.existingSize.setText(DisplayUtils.bytesToHumanReadable(existingFile.getFileLength()));
|
|
||||||
binding.existingTimestamp.setText(DisplayUtils.getRelativeTimestamp(getContext(),
|
|
||||||
existingFile.getModificationTimestamp()));
|
|
||||||
|
|
||||||
binding.existingThumbnail.setTag(existingFile.getFileId());
|
|
||||||
DisplayUtils.setThumbnail(existingFile,
|
|
||||||
binding.existingThumbnail,
|
|
||||||
user,
|
|
||||||
new FileDataStorageManager(user,
|
|
||||||
requireContext().getContentResolver()),
|
|
||||||
asyncTasks,
|
|
||||||
false,
|
|
||||||
getContext(),
|
|
||||||
null,
|
|
||||||
syncedFolderProvider.getPreferences(),
|
|
||||||
viewThemeUtils,
|
|
||||||
syncedFolderProvider);
|
|
||||||
|
|
||||||
View.OnClickListener checkBoxClickListener = v ->
|
|
||||||
positiveButton.setEnabled(binding.newCheckbox.isChecked() || binding.existingCheckbox.isChecked());
|
|
||||||
|
|
||||||
binding.newCheckbox.setOnClickListener(checkBoxClickListener);
|
|
||||||
binding.existingCheckbox.setOnClickListener(checkBoxClickListener);
|
|
||||||
|
|
||||||
binding.newFileContainer.setOnClickListener(v -> {
|
|
||||||
binding.newCheckbox.setChecked(!binding.newCheckbox.isChecked());
|
|
||||||
positiveButton.setEnabled(binding.newCheckbox.isChecked() || binding.existingCheckbox.isChecked());
|
|
||||||
});
|
|
||||||
binding.existingFileContainer.setOnClickListener(v -> {
|
|
||||||
binding.existingCheckbox.setChecked(!binding.existingCheckbox.isChecked());
|
|
||||||
positiveButton.setEnabled(binding.newCheckbox.isChecked() || binding.existingCheckbox.isChecked());
|
|
||||||
});
|
|
||||||
|
|
||||||
viewThemeUtils.dialog.colorMaterialAlertDialogBackground(binding.existingFileContainer.getContext(), builder);
|
|
||||||
|
|
||||||
return builder.create();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void showDialog(AppCompatActivity activity) {
|
|
||||||
Fragment prev = activity.getSupportFragmentManager().findFragmentByTag("dialog");
|
|
||||||
FragmentTransaction ft = activity.getSupportFragmentManager().beginTransaction();
|
|
||||||
if (prev != null) {
|
|
||||||
ft.remove(prev);
|
|
||||||
}
|
|
||||||
ft.addToBackStack(null);
|
|
||||||
|
|
||||||
this.show(ft, "dialog");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCancel(@NonNull DialogInterface dialog) {
|
|
||||||
if (listener != null) {
|
|
||||||
listener.conflictDecisionMade(Decision.CANCEL);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface OnConflictDecisionMadeListener {
|
|
||||||
void conflictDecisionMade(Decision decision);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onStop() {
|
|
||||||
super.onStop();
|
|
||||||
|
|
||||||
for (ThumbnailsCacheManager.ThumbnailGenerationTask task : asyncTasks) {
|
|
||||||
if (task != null) {
|
|
||||||
task.cancel(true);
|
|
||||||
if (task.getGetMethod() != null) {
|
|
||||||
Log_OC.d(this, "cancel: abort get method directly");
|
|
||||||
task.getGetMethod().abort();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
asyncTasks.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -0,0 +1,270 @@
|
||||||
|
/*
|
||||||
|
* Nextcloud - Android Client
|
||||||
|
*
|
||||||
|
* SPDX-FileCopyrightText: 2023 Alper Ozturk <alper.ozturk@nextcloud.com>
|
||||||
|
* SPDX-FileCopyrightText: 2020-2022 Tobias Kaminsky <tobias@kaminsky.me>
|
||||||
|
* SPDX-FileCopyrightText: 2015 ownCloud Inc.
|
||||||
|
* SPDX-FileCopyrightText: 2012 Bartosz Przybylski <bart.p.pl@gmail.com>
|
||||||
|
* SPDX-License-Identifier: GPL-2.0-only AND (AGPL-3.0-or-later OR GPL-2.0-only)
|
||||||
|
*/
|
||||||
|
package com.owncloud.android.ui.dialog
|
||||||
|
|
||||||
|
import android.app.Dialog
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.DialogInterface
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.fragment.app.DialogFragment
|
||||||
|
import com.google.android.material.button.MaterialButton
|
||||||
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
|
import com.nextcloud.client.account.User
|
||||||
|
import com.nextcloud.client.di.Injectable
|
||||||
|
import com.nextcloud.utils.extensions.getParcelableArgument
|
||||||
|
import com.nextcloud.utils.extensions.getSerializableArgument
|
||||||
|
import com.nextcloud.utils.extensions.logFileSize
|
||||||
|
import com.owncloud.android.R
|
||||||
|
import com.owncloud.android.databinding.ConflictResolveDialogBinding
|
||||||
|
import com.owncloud.android.datamodel.FileDataStorageManager
|
||||||
|
import com.owncloud.android.datamodel.OCFile
|
||||||
|
import com.owncloud.android.datamodel.SyncedFolderProvider
|
||||||
|
import com.owncloud.android.datamodel.ThumbnailsCacheManager.ThumbnailGenerationTask
|
||||||
|
import com.owncloud.android.lib.common.utils.Log_OC
|
||||||
|
import com.owncloud.android.ui.adapter.LocalFileListAdapter
|
||||||
|
import com.owncloud.android.utils.DisplayUtils
|
||||||
|
import com.owncloud.android.utils.theme.ViewThemeUtils
|
||||||
|
import java.io.File
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dialog which will be displayed to user upon keep-in-sync file conflict.
|
||||||
|
*/
|
||||||
|
class ConflictsResolveDialog : DialogFragment(), Injectable {
|
||||||
|
private lateinit var binding: ConflictResolveDialogBinding
|
||||||
|
|
||||||
|
private var existingFile: OCFile? = null
|
||||||
|
private var newFile: File? = null
|
||||||
|
var listener: OnConflictDecisionMadeListener? = null
|
||||||
|
private var user: User? = null
|
||||||
|
private val asyncTasks: MutableList<ThumbnailGenerationTask> = ArrayList()
|
||||||
|
private var positiveButton: MaterialButton? = null
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var viewThemeUtils: ViewThemeUtils
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var syncedFolderProvider: SyncedFolderProvider
|
||||||
|
|
||||||
|
enum class Decision {
|
||||||
|
CANCEL,
|
||||||
|
KEEP_BOTH,
|
||||||
|
KEEP_LOCAL,
|
||||||
|
KEEP_SERVER
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onAttach(context: Context) {
|
||||||
|
super.onAttach(context)
|
||||||
|
|
||||||
|
try {
|
||||||
|
listener = context as OnConflictDecisionMadeListener
|
||||||
|
} catch (e: ClassCastException) {
|
||||||
|
throw ClassCastException("Activity of this dialog must implement OnConflictDecisionMadeListener")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStart() {
|
||||||
|
super.onStart()
|
||||||
|
|
||||||
|
val alertDialog = dialog as AlertDialog?
|
||||||
|
|
||||||
|
if (alertDialog == null) {
|
||||||
|
Toast.makeText(context, "Failed to create conflict dialog", Toast.LENGTH_LONG).show()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
positiveButton = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE) as MaterialButton
|
||||||
|
val negativeButton = alertDialog.getButton(AlertDialog.BUTTON_NEGATIVE) as MaterialButton
|
||||||
|
|
||||||
|
positiveButton?.let {
|
||||||
|
viewThemeUtils.material.colorMaterialButtonPrimaryTonal(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
viewThemeUtils.material.colorMaterialButtonPrimaryBorderless(negativeButton)
|
||||||
|
positiveButton?.isEnabled = false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
if (savedInstanceState != null) {
|
||||||
|
existingFile = savedInstanceState.getParcelableArgument(KEY_EXISTING_FILE, OCFile::class.java)
|
||||||
|
newFile = savedInstanceState.getSerializableArgument(KEY_NEW_FILE, File::class.java)
|
||||||
|
user = savedInstanceState.getParcelableArgument(KEY_USER, User::class.java)
|
||||||
|
} else if (arguments != null) {
|
||||||
|
existingFile = arguments.getParcelableArgument(KEY_EXISTING_FILE, OCFile::class.java)
|
||||||
|
newFile = arguments.getSerializableArgument(KEY_NEW_FILE, File::class.java)
|
||||||
|
user = arguments.getParcelableArgument(KEY_USER, User::class.java)
|
||||||
|
} else {
|
||||||
|
Toast.makeText(context, "Failed to create conflict dialog", Toast.LENGTH_LONG).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSaveInstanceState(outState: Bundle) {
|
||||||
|
super.onSaveInstanceState(outState)
|
||||||
|
existingFile.logFileSize(TAG)
|
||||||
|
newFile.logFileSize(TAG)
|
||||||
|
outState.putParcelable(KEY_EXISTING_FILE, existingFile)
|
||||||
|
outState.putSerializable(KEY_NEW_FILE, newFile)
|
||||||
|
outState.putParcelable(KEY_USER, user)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||||
|
binding = ConflictResolveDialogBinding.inflate(requireActivity().layoutInflater)
|
||||||
|
|
||||||
|
viewThemeUtils.platform.themeCheckbox(binding.newCheckbox)
|
||||||
|
viewThemeUtils.platform.themeCheckbox(binding.existingCheckbox)
|
||||||
|
|
||||||
|
val builder = MaterialAlertDialogBuilder(requireActivity())
|
||||||
|
builder.setView(binding.root)
|
||||||
|
.setPositiveButton(R.string.common_ok) { _: DialogInterface?, _: Int ->
|
||||||
|
if (binding.newCheckbox.isChecked && binding.existingCheckbox.isChecked) {
|
||||||
|
listener?.conflictDecisionMade(Decision.KEEP_BOTH)
|
||||||
|
} else if (binding.newCheckbox.isChecked) {
|
||||||
|
listener?.conflictDecisionMade(Decision.KEEP_LOCAL)
|
||||||
|
} else if (binding.existingCheckbox.isChecked) {
|
||||||
|
listener?.conflictDecisionMade(Decision.KEEP_SERVER)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.setNegativeButton(R.string.common_cancel) { _: DialogInterface?, _: Int ->
|
||||||
|
listener?.conflictDecisionMade(Decision.CANCEL)
|
||||||
|
}
|
||||||
|
.setTitle(String.format(getString(R.string.conflict_file_headline), existingFile?.fileName))
|
||||||
|
|
||||||
|
setupUI()
|
||||||
|
setOnClickListeners()
|
||||||
|
|
||||||
|
viewThemeUtils.dialog.colorMaterialAlertDialogBackground(binding.existingFileContainer.context, builder)
|
||||||
|
|
||||||
|
return builder.create()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupUI() {
|
||||||
|
val parentFile = existingFile?.remotePath?.let { File(it).parentFile }
|
||||||
|
if (parentFile != null) {
|
||||||
|
binding.`in`.text = String.format(getString(R.string.in_folder), parentFile.absolutePath)
|
||||||
|
} else {
|
||||||
|
binding.`in`.visibility = View.GONE
|
||||||
|
}
|
||||||
|
|
||||||
|
// set info for new file
|
||||||
|
binding.newSize.text = newFile?.length()?.let { DisplayUtils.bytesToHumanReadable(it) }
|
||||||
|
binding.newTimestamp.text = newFile?.lastModified()?.let { DisplayUtils.getRelativeTimestamp(context, it) }
|
||||||
|
binding.newThumbnail.tag = newFile.hashCode()
|
||||||
|
LocalFileListAdapter.setThumbnail(
|
||||||
|
newFile,
|
||||||
|
binding.newThumbnail,
|
||||||
|
context,
|
||||||
|
viewThemeUtils
|
||||||
|
)
|
||||||
|
|
||||||
|
// set info for existing file
|
||||||
|
binding.existingSize.text = existingFile?.fileLength?.let { DisplayUtils.bytesToHumanReadable(it) }
|
||||||
|
binding.existingTimestamp.text = existingFile?.modificationTimestamp?.let {
|
||||||
|
DisplayUtils.getRelativeTimestamp(
|
||||||
|
context,
|
||||||
|
it
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.existingThumbnail.tag = existingFile?.fileId
|
||||||
|
DisplayUtils.setThumbnail(
|
||||||
|
existingFile,
|
||||||
|
binding.existingThumbnail,
|
||||||
|
user,
|
||||||
|
FileDataStorageManager(
|
||||||
|
user,
|
||||||
|
requireContext().contentResolver
|
||||||
|
),
|
||||||
|
asyncTasks,
|
||||||
|
false,
|
||||||
|
context,
|
||||||
|
null,
|
||||||
|
syncedFolderProvider.preferences,
|
||||||
|
viewThemeUtils,
|
||||||
|
syncedFolderProvider
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setOnClickListeners() {
|
||||||
|
val checkBoxClickListener = View.OnClickListener {
|
||||||
|
positiveButton?.isEnabled = binding.newCheckbox.isChecked || binding.existingCheckbox.isChecked
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.newCheckbox.setOnClickListener(checkBoxClickListener)
|
||||||
|
binding.existingCheckbox.setOnClickListener(checkBoxClickListener)
|
||||||
|
|
||||||
|
binding.newFileContainer.setOnClickListener {
|
||||||
|
binding.newCheckbox.isChecked = !binding.newCheckbox.isChecked
|
||||||
|
positiveButton?.isEnabled = binding.newCheckbox.isChecked || binding.existingCheckbox.isChecked
|
||||||
|
}
|
||||||
|
binding.existingFileContainer.setOnClickListener {
|
||||||
|
binding.existingCheckbox.isChecked = !binding.existingCheckbox.isChecked
|
||||||
|
positiveButton?.isEnabled = binding.newCheckbox.isChecked || binding.existingCheckbox.isChecked
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun showDialog(activity: AppCompatActivity) {
|
||||||
|
val prev = activity.supportFragmentManager.findFragmentByTag("dialog")
|
||||||
|
val ft = activity.supportFragmentManager.beginTransaction()
|
||||||
|
if (prev != null) {
|
||||||
|
ft.remove(prev)
|
||||||
|
}
|
||||||
|
ft.addToBackStack(null)
|
||||||
|
show(ft, "dialog")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCancel(dialog: DialogInterface) {
|
||||||
|
listener?.conflictDecisionMade(Decision.CANCEL)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun interface OnConflictDecisionMadeListener {
|
||||||
|
fun conflictDecisionMade(decision: Decision?)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStop() {
|
||||||
|
super.onStop()
|
||||||
|
|
||||||
|
for (task in asyncTasks) {
|
||||||
|
task.cancel(true)
|
||||||
|
Log_OC.d(this, "cancel: abort get method directly")
|
||||||
|
task.getMethod?.abort()
|
||||||
|
}
|
||||||
|
|
||||||
|
asyncTasks.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val TAG = "ConflictsResolveDialog"
|
||||||
|
private const val KEY_NEW_FILE = "file"
|
||||||
|
private const val KEY_EXISTING_FILE = "ocfile"
|
||||||
|
private const val KEY_USER = "user"
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun newInstance(existingFile: OCFile?, newFile: OCFile, user: User?): ConflictsResolveDialog {
|
||||||
|
val file = File(newFile.storagePath)
|
||||||
|
file.logFileSize(TAG)
|
||||||
|
|
||||||
|
val bundle = Bundle().apply {
|
||||||
|
putParcelable(KEY_EXISTING_FILE, existingFile)
|
||||||
|
putSerializable(KEY_NEW_FILE, file)
|
||||||
|
putParcelable(KEY_USER, user)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ConflictsResolveDialog().apply {
|
||||||
|
arguments = bundle
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,201 +0,0 @@
|
||||||
/*
|
|
||||||
* Nextcloud - Android Client
|
|
||||||
*
|
|
||||||
* SPDX-FileCopyrightText: 2023 Alper Ozturk <alper.ozturk@nextcloud.com>
|
|
||||||
* SPDX-FileCopyrightText: 2018 Andy Scherzinger <info@andy-scherzinger.de>
|
|
||||||
* SPDX-FileCopyrightText: 2018 Jessie Chatham Spencer <jessie@teainspace.com>
|
|
||||||
* SPDX-FileCopyrightText: 2016-2022 Tobias Kaminsky <tobias@kaminsky.me>
|
|
||||||
* SPDX-FileCopyrightText: 2015 ownCloud Inc.
|
|
||||||
* SPDX-FileCopyrightText: 2015 David A. Velasco <dvelasco@solidgear.es>
|
|
||||||
* SPDX-License-Identifier: GPL-2.0-only AND (AGPL-3.0-or-later OR GPL-2.0-only)
|
|
||||||
*/
|
|
||||||
package com.owncloud.android.ui.dialog;
|
|
||||||
|
|
||||||
import android.app.Dialog;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.view.ActionMode;
|
|
||||||
|
|
||||||
import com.google.android.material.button.MaterialButton;
|
|
||||||
import com.nextcloud.client.di.Injectable;
|
|
||||||
import com.owncloud.android.R;
|
|
||||||
import com.owncloud.android.datamodel.OCFile;
|
|
||||||
import com.owncloud.android.ui.activity.ComponentsGetter;
|
|
||||||
import com.owncloud.android.ui.dialog.ConfirmationDialogFragment.ConfirmationDialogFragmentListener;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.appcompat.app.AlertDialog;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Dialog requiring confirmation before removing a collection of given OCFiles.
|
|
||||||
* Triggers the removal according to the user response.
|
|
||||||
*/
|
|
||||||
public class RemoveFilesDialogFragment extends ConfirmationDialogFragment implements
|
|
||||||
ConfirmationDialogFragmentListener, Injectable {
|
|
||||||
|
|
||||||
private static final int SINGLE_SELECTION = 1;
|
|
||||||
private static final String ARG_TARGET_FILES = "TARGET_FILES";
|
|
||||||
|
|
||||||
private Collection<OCFile> mTargetFiles;
|
|
||||||
private ActionMode actionMode;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Public factory method to create new RemoveFilesDialogFragment instances.
|
|
||||||
*
|
|
||||||
* @param files Files to remove.
|
|
||||||
* @param actionMode ActionMode to finish on confirmation
|
|
||||||
* @return Dialog ready to show.
|
|
||||||
*/
|
|
||||||
public static RemoveFilesDialogFragment newInstance(ArrayList<OCFile> files, ActionMode actionMode) {
|
|
||||||
RemoveFilesDialogFragment dialogFragment = newInstance(files);
|
|
||||||
dialogFragment.setActionMode(actionMode);
|
|
||||||
return dialogFragment;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Public factory method to create new RemoveFilesDialogFragment instances.
|
|
||||||
*
|
|
||||||
* @param files Files to remove.
|
|
||||||
* @return Dialog ready to show.
|
|
||||||
*/
|
|
||||||
public static RemoveFilesDialogFragment newInstance(ArrayList<OCFile> files) {
|
|
||||||
RemoveFilesDialogFragment frag = new RemoveFilesDialogFragment();
|
|
||||||
Bundle args = new Bundle();
|
|
||||||
int messageStringId;
|
|
||||||
|
|
||||||
boolean containsFolder = false;
|
|
||||||
boolean containsDown = false;
|
|
||||||
|
|
||||||
for (OCFile file: files) {
|
|
||||||
containsFolder |= file.isFolder();
|
|
||||||
containsDown |= file.isDown();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (files.size() == SINGLE_SELECTION) {
|
|
||||||
// choose message for a single file
|
|
||||||
OCFile file = files.get(0);
|
|
||||||
|
|
||||||
messageStringId = file.isFolder() ?
|
|
||||||
R.string.confirmation_remove_folder_alert :
|
|
||||||
R.string.confirmation_remove_file_alert;
|
|
||||||
|
|
||||||
} else {
|
|
||||||
// choose message for more than one file
|
|
||||||
messageStringId = containsFolder ?
|
|
||||||
R.string.confirmation_remove_folders_alert :
|
|
||||||
R.string.confirmation_remove_files_alert;
|
|
||||||
}
|
|
||||||
|
|
||||||
args.putInt(ARG_MESSAGE_RESOURCE_ID, messageStringId);
|
|
||||||
if (files.size() == SINGLE_SELECTION) {
|
|
||||||
args.putStringArray(ARG_MESSAGE_ARGUMENTS, new String[] { files.get(0).getFileName() } );
|
|
||||||
}
|
|
||||||
|
|
||||||
args.putInt(ARG_POSITIVE_BTN_RES, R.string.file_delete);
|
|
||||||
|
|
||||||
if (containsFolder || containsDown) {
|
|
||||||
args.putInt(ARG_NEGATIVE_BTN_RES, R.string.confirmation_remove_local);
|
|
||||||
}
|
|
||||||
|
|
||||||
args.putInt(ARG_NEUTRAL_BTN_RES, R.string.file_keep);
|
|
||||||
args.putParcelableArrayList(ARG_TARGET_FILES, files);
|
|
||||||
frag.setArguments(args);
|
|
||||||
|
|
||||||
return frag;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convenience factory method to create new RemoveFilesDialogFragment instances for a single file
|
|
||||||
*
|
|
||||||
* @param file File to remove.
|
|
||||||
* @return Dialog ready to show.
|
|
||||||
*/
|
|
||||||
public static RemoveFilesDialogFragment newInstance(OCFile file) {
|
|
||||||
ArrayList<OCFile> list = new ArrayList<>();
|
|
||||||
list.add(file);
|
|
||||||
return newInstance(list);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onStart() {
|
|
||||||
super.onStart();
|
|
||||||
|
|
||||||
AlertDialog alertDialog = (AlertDialog) getDialog();
|
|
||||||
|
|
||||||
if (alertDialog != null) {
|
|
||||||
MaterialButton positiveButton = (MaterialButton) alertDialog.getButton(AlertDialog.BUTTON_POSITIVE);
|
|
||||||
viewThemeUtils.material.colorMaterialButtonPrimaryTonal(positiveButton);
|
|
||||||
|
|
||||||
MaterialButton negativeButton = (MaterialButton) alertDialog.getButton(AlertDialog.BUTTON_NEGATIVE);
|
|
||||||
viewThemeUtils.material.colorMaterialButtonPrimaryBorderless(negativeButton);
|
|
||||||
|
|
||||||
MaterialButton neutralButton = (MaterialButton) alertDialog.getButton(AlertDialog.BUTTON_NEUTRAL);
|
|
||||||
if (neutralButton != null) {
|
|
||||||
viewThemeUtils.material.colorMaterialButtonPrimaryBorderless(neutralButton);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
|
||||||
Dialog dialog = super.onCreateDialog(savedInstanceState);
|
|
||||||
Bundle arguments = getArguments();
|
|
||||||
|
|
||||||
if (arguments == null) {
|
|
||||||
return dialog;
|
|
||||||
}
|
|
||||||
|
|
||||||
mTargetFiles = arguments.getParcelableArrayList(ARG_TARGET_FILES);
|
|
||||||
setOnConfirmationListener(this);
|
|
||||||
return dialog;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Performs the removal of the target file, both locally and in the server and
|
|
||||||
* finishes the supplied ActionMode if one was given.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void onConfirmation(String callerTag) {
|
|
||||||
removeFiles(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Performs the removal of the local copy of the target file
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void onCancel(String callerTag) {
|
|
||||||
removeFiles(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void removeFiles(boolean onlyLocalCopy) {
|
|
||||||
ComponentsGetter cg = (ComponentsGetter) getActivity();
|
|
||||||
if (cg != null) {
|
|
||||||
cg.getFileOperationsHelper().removeFiles(mTargetFiles, onlyLocalCopy, false);
|
|
||||||
}
|
|
||||||
finishActionMode();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onNeutral(String callerTag) {
|
|
||||||
// nothing to do here
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setActionMode(ActionMode actionMode) {
|
|
||||||
this.actionMode = actionMode;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This is used when finishing an actionMode,
|
|
||||||
* for example if we want to exit the selection mode
|
|
||||||
* after deleting the target files.
|
|
||||||
*/
|
|
||||||
private void finishActionMode() {
|
|
||||||
if (actionMode != null) {
|
|
||||||
actionMode.finish();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,175 @@
|
||||||
|
/*
|
||||||
|
* Nextcloud - Android Client
|
||||||
|
*
|
||||||
|
* SPDX-FileCopyrightText: 2023 Alper Ozturk <alper.ozturk@nextcloud.com>
|
||||||
|
* SPDX-FileCopyrightText: 2018 Andy Scherzinger <info@andy-scherzinger.de>
|
||||||
|
* SPDX-FileCopyrightText: 2018 Jessie Chatham Spencer <jessie@teainspace.com>
|
||||||
|
* SPDX-FileCopyrightText: 2016-2022 Tobias Kaminsky <tobias@kaminsky.me>
|
||||||
|
* SPDX-FileCopyrightText: 2015 ownCloud Inc.
|
||||||
|
* SPDX-FileCopyrightText: 2015 David A. Velasco <dvelasco@solidgear.es>
|
||||||
|
* SPDX-License-Identifier: GPL-2.0-only AND (AGPL-3.0-or-later OR GPL-2.0-only)
|
||||||
|
*/
|
||||||
|
package com.owncloud.android.ui.dialog
|
||||||
|
|
||||||
|
import android.app.Dialog
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.ActionMode
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import com.google.android.material.button.MaterialButton
|
||||||
|
import com.nextcloud.client.di.Injectable
|
||||||
|
import com.owncloud.android.R
|
||||||
|
import com.owncloud.android.datamodel.OCFile
|
||||||
|
import com.owncloud.android.ui.activity.ComponentsGetter
|
||||||
|
import com.owncloud.android.ui.dialog.ConfirmationDialogFragment.ConfirmationDialogFragmentListener
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dialog requiring confirmation before removing a collection of given OCFiles.
|
||||||
|
* Triggers the removal according to the user response.
|
||||||
|
*/
|
||||||
|
class RemoveFilesDialogFragment : ConfirmationDialogFragment(), ConfirmationDialogFragmentListener, Injectable {
|
||||||
|
private var mTargetFiles: Collection<OCFile>? = null
|
||||||
|
private var actionMode: ActionMode? = null
|
||||||
|
|
||||||
|
override fun onStart() {
|
||||||
|
super.onStart()
|
||||||
|
|
||||||
|
val alertDialog = dialog as AlertDialog? ?: return
|
||||||
|
|
||||||
|
val positiveButton = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE) as? MaterialButton
|
||||||
|
positiveButton?.let {
|
||||||
|
viewThemeUtils?.material?.colorMaterialButtonPrimaryTonal(positiveButton)
|
||||||
|
}
|
||||||
|
|
||||||
|
val negativeButton = alertDialog.getButton(AlertDialog.BUTTON_NEGATIVE) as? MaterialButton
|
||||||
|
negativeButton?.let {
|
||||||
|
viewThemeUtils?.material?.colorMaterialButtonPrimaryBorderless(negativeButton)
|
||||||
|
}
|
||||||
|
|
||||||
|
val neutralButton = alertDialog.getButton(AlertDialog.BUTTON_NEUTRAL) as? MaterialButton
|
||||||
|
neutralButton?.let {
|
||||||
|
viewThemeUtils?.material?.colorMaterialButtonPrimaryBorderless(neutralButton)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||||
|
val dialog = super.onCreateDialog(savedInstanceState)
|
||||||
|
val arguments = arguments ?: return dialog
|
||||||
|
|
||||||
|
mTargetFiles = arguments.getParcelableArrayList(ARG_TARGET_FILES)
|
||||||
|
setOnConfirmationListener(this)
|
||||||
|
return dialog
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs the removal of the target file, both locally and in the server and
|
||||||
|
* finishes the supplied ActionMode if one was given.
|
||||||
|
*/
|
||||||
|
override fun onConfirmation(callerTag: String?) {
|
||||||
|
removeFiles(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs the removal of the local copy of the target file
|
||||||
|
*/
|
||||||
|
override fun onCancel(callerTag: String?) {
|
||||||
|
removeFiles(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun removeFiles(onlyLocalCopy: Boolean) {
|
||||||
|
val cg = activity as ComponentsGetter?
|
||||||
|
cg?.fileOperationsHelper?.removeFiles(mTargetFiles, onlyLocalCopy, false)
|
||||||
|
finishActionMode()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onNeutral(callerTag: String?) {
|
||||||
|
// nothing to do here
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setActionMode(actionMode: ActionMode?) {
|
||||||
|
this.actionMode = actionMode
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is used when finishing an actionMode,
|
||||||
|
* for example if we want to exit the selection mode
|
||||||
|
* after deleting the target files.
|
||||||
|
*/
|
||||||
|
private fun finishActionMode() {
|
||||||
|
actionMode?.finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val SINGLE_SELECTION = 1
|
||||||
|
private const val ARG_TARGET_FILES = "TARGET_FILES"
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun newInstance(files: ArrayList<OCFile>, actionMode: ActionMode?): RemoveFilesDialogFragment {
|
||||||
|
return newInstance(files).apply {
|
||||||
|
setActionMode(actionMode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun newInstance(files: ArrayList<OCFile>): RemoveFilesDialogFragment {
|
||||||
|
val messageStringId: Int
|
||||||
|
|
||||||
|
var containsFolder = false
|
||||||
|
var containsDown = false
|
||||||
|
|
||||||
|
for (file in files) {
|
||||||
|
containsFolder = containsFolder or file.isFolder
|
||||||
|
containsDown = containsDown or file.isDown
|
||||||
|
}
|
||||||
|
|
||||||
|
if (files.size == SINGLE_SELECTION) {
|
||||||
|
val file = files[0]
|
||||||
|
messageStringId =
|
||||||
|
if (file.isFolder) {
|
||||||
|
R.string.confirmation_remove_folder_alert
|
||||||
|
} else {
|
||||||
|
R.string.confirmation_remove_file_alert
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
messageStringId =
|
||||||
|
if (containsFolder) {
|
||||||
|
R.string.confirmation_remove_folders_alert
|
||||||
|
} else {
|
||||||
|
R.string.confirmation_remove_files_alert
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val bundle = Bundle().apply {
|
||||||
|
putInt(ARG_MESSAGE_RESOURCE_ID, messageStringId)
|
||||||
|
if (files.size == SINGLE_SELECTION) {
|
||||||
|
putStringArray(
|
||||||
|
ARG_MESSAGE_ARGUMENTS,
|
||||||
|
arrayOf(
|
||||||
|
files[0].fileName
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
putInt(ARG_POSITIVE_BTN_RES, R.string.file_delete)
|
||||||
|
|
||||||
|
if (containsFolder || containsDown) {
|
||||||
|
putInt(ARG_NEGATIVE_BTN_RES, R.string.confirmation_remove_local)
|
||||||
|
}
|
||||||
|
|
||||||
|
putInt(ARG_NEUTRAL_BTN_RES, R.string.file_keep)
|
||||||
|
putParcelableArrayList(ARG_TARGET_FILES, files)
|
||||||
|
}
|
||||||
|
|
||||||
|
return RemoveFilesDialogFragment().apply {
|
||||||
|
arguments = bundle
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun newInstance(file: OCFile): RemoveFilesDialogFragment {
|
||||||
|
val list = ArrayList<OCFile>().apply {
|
||||||
|
add(file)
|
||||||
|
}
|
||||||
|
return newInstance(list)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,171 +0,0 @@
|
||||||
/*
|
|
||||||
* Nextcloud - Android Client
|
|
||||||
*
|
|
||||||
* SPDX-FileCopyrightText: 2017-2018 Andy Scherzinger <info@andy-scherzinger.de>
|
|
||||||
* SPDX-FileCopyrightText: 2017-2018 Tobias Kaminsky <tobias@kaminsky.me>
|
|
||||||
* SPDX-FileCopyrightText: 2015 ownCloud Inc.
|
|
||||||
* SPDX-FileCopyrightText: 2015 David A. Velasco <dvelasco@solidgear.es>
|
|
||||||
* SPDX-License-Identifier: GPL-2.0-only AND (AGPL-3.0-or-later OR GPL-2.0-only)
|
|
||||||
*/
|
|
||||||
package com.owncloud.android.ui.dialog;
|
|
||||||
|
|
||||||
import android.app.Dialog;
|
|
||||||
import android.content.ComponentName;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.DialogInterface;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.pm.ActivityInfo;
|
|
||||||
import android.content.pm.PackageManager;
|
|
||||||
import android.content.pm.ResolveInfo;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.ArrayAdapter;
|
|
||||||
import android.widget.ImageView;
|
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
|
||||||
import com.nextcloud.utils.extensions.BundleExtensionsKt;
|
|
||||||
import com.owncloud.android.R;
|
|
||||||
import com.owncloud.android.lib.common.utils.Log_OC;
|
|
||||||
import com.owncloud.android.ui.activity.CopyToClipboardActivity;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Locale;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.appcompat.app.AlertDialog;
|
|
||||||
import androidx.fragment.app.DialogFragment;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Dialog showing a list activities able to resolve a given Intent,
|
|
||||||
* filtering out the activities matching give package names.
|
|
||||||
*/
|
|
||||||
public class ShareLinkToDialog extends DialogFragment {
|
|
||||||
|
|
||||||
private final static String TAG = ShareLinkToDialog.class.getSimpleName();
|
|
||||||
private final static String ARG_INTENT = ShareLinkToDialog.class.getSimpleName() +
|
|
||||||
".ARG_INTENT";
|
|
||||||
private final static String ARG_PACKAGES_TO_EXCLUDE = ShareLinkToDialog.class.getSimpleName() +
|
|
||||||
".ARG_PACKAGES_TO_EXCLUDE";
|
|
||||||
|
|
||||||
private ActivityAdapter mAdapter;
|
|
||||||
private Intent mIntent;
|
|
||||||
|
|
||||||
public static ShareLinkToDialog newInstance(Intent intent, String... packagesToExclude) {
|
|
||||||
ShareLinkToDialog f = new ShareLinkToDialog();
|
|
||||||
Bundle args = new Bundle();
|
|
||||||
args.putParcelable(ARG_INTENT, intent);
|
|
||||||
args.putStringArray(ARG_PACKAGES_TO_EXCLUDE, packagesToExclude);
|
|
||||||
f.setArguments(args);
|
|
||||||
return f;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ShareLinkToDialog() {
|
|
||||||
super();
|
|
||||||
Log_OC.d(TAG, "constructor");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@NonNull
|
|
||||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
|
||||||
mIntent = BundleExtensionsKt.getParcelableArgument(getArguments(), ARG_INTENT, Intent.class);
|
|
||||||
String[] packagesToExclude = getArguments().getStringArray(ARG_PACKAGES_TO_EXCLUDE);
|
|
||||||
List<String> packagesToExcludeList = Arrays.asList(packagesToExclude != null ?
|
|
||||||
packagesToExclude : new String[0]);
|
|
||||||
|
|
||||||
PackageManager pm = getActivity().getPackageManager();
|
|
||||||
List<ResolveInfo> activities = pm.queryIntentActivities(mIntent, PackageManager.MATCH_DEFAULT_ONLY);
|
|
||||||
Iterator<ResolveInfo> it = activities.iterator();
|
|
||||||
ResolveInfo resolveInfo;
|
|
||||||
while (it.hasNext()) {
|
|
||||||
resolveInfo = it.next();
|
|
||||||
if (packagesToExcludeList.contains(resolveInfo.activityInfo.packageName.toLowerCase(Locale.ROOT))) {
|
|
||||||
it.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean sendAction = mIntent.getBooleanExtra(Intent.ACTION_SEND, false);
|
|
||||||
|
|
||||||
if (!sendAction) {
|
|
||||||
// add activity for copy to clipboard
|
|
||||||
Intent copyToClipboardIntent = new Intent(getActivity(), CopyToClipboardActivity.class);
|
|
||||||
List<ResolveInfo> copyToClipboard = pm.queryIntentActivities(copyToClipboardIntent, 0);
|
|
||||||
if (!copyToClipboard.isEmpty()) {
|
|
||||||
activities.add(copyToClipboard.get(0));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Collections.sort(activities, new ResolveInfo.DisplayNameComparator(pm));
|
|
||||||
mAdapter = new ActivityAdapter(getActivity(), pm, activities);
|
|
||||||
|
|
||||||
return createSelector(sendAction);
|
|
||||||
}
|
|
||||||
|
|
||||||
private AlertDialog createSelector(final boolean sendAction) {
|
|
||||||
|
|
||||||
int titleId;
|
|
||||||
if (sendAction) {
|
|
||||||
titleId = R.string.activity_chooser_send_file_title;
|
|
||||||
} else {
|
|
||||||
titleId = R.string.activity_chooser_title;
|
|
||||||
}
|
|
||||||
|
|
||||||
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(getActivity())
|
|
||||||
.setTitle(titleId)
|
|
||||||
.setAdapter(mAdapter, new DialogInterface.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
|
||||||
// Add the information of the chosen activity to the intent to send
|
|
||||||
ResolveInfo chosen = mAdapter.getItem(which);
|
|
||||||
ActivityInfo actInfo = chosen.activityInfo;
|
|
||||||
ComponentName name=new ComponentName(
|
|
||||||
actInfo.applicationInfo.packageName,
|
|
||||||
actInfo.name);
|
|
||||||
mIntent.setComponent(name);
|
|
||||||
|
|
||||||
// Send the file
|
|
||||||
getActivity().startActivity(mIntent);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return builder.create();
|
|
||||||
}
|
|
||||||
|
|
||||||
class ActivityAdapter extends ArrayAdapter<ResolveInfo> {
|
|
||||||
|
|
||||||
private PackageManager mPackageManager;
|
|
||||||
|
|
||||||
ActivityAdapter(Context context, PackageManager pm, List<ResolveInfo> apps) {
|
|
||||||
super(context, R.layout.activity_row, apps);
|
|
||||||
this.mPackageManager = pm;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NonNull View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
|
|
||||||
View view = convertView;
|
|
||||||
|
|
||||||
if (view == null) {
|
|
||||||
view = newView(parent);
|
|
||||||
}
|
|
||||||
bindView(position, view);
|
|
||||||
return view;
|
|
||||||
}
|
|
||||||
|
|
||||||
private View newView(ViewGroup parent) {
|
|
||||||
return((LayoutInflater)getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE)).
|
|
||||||
inflate(R.layout.activity_row, parent, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void bindView(int position, View row) {
|
|
||||||
TextView label = row.findViewById(R.id.title);
|
|
||||||
label.setText(getItem(position).loadLabel(mPackageManager));
|
|
||||||
ImageView icon = row.findViewById(R.id.icon);
|
|
||||||
icon.setImageDrawable(getItem(position).loadIcon(mPackageManager));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,150 @@
|
||||||
|
/*
|
||||||
|
* Nextcloud - Android Client
|
||||||
|
*
|
||||||
|
* SPDX-FileCopyrightText: 2024 Alper Ozturk <alper.ozturk@nextcloud.com>
|
||||||
|
* SPDX-FileCopyrightText: 2017-2018 Andy Scherzinger <info@andy-scherzinger.de>
|
||||||
|
* SPDX-FileCopyrightText: 2017-2018 Tobias Kaminsky <tobias@kaminsky.me>
|
||||||
|
* SPDX-FileCopyrightText: 2015 ownCloud Inc.
|
||||||
|
* SPDX-FileCopyrightText: 2015 David A. Velasco <dvelasco@solidgear.es>
|
||||||
|
* SPDX-License-Identifier: GPL-2.0-only AND (AGPL-3.0-or-later OR GPL-2.0-only)
|
||||||
|
*/
|
||||||
|
package com.owncloud.android.ui.dialog
|
||||||
|
|
||||||
|
import android.app.Dialog
|
||||||
|
import android.content.ComponentName
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import android.content.pm.ResolveInfo
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.ArrayAdapter
|
||||||
|
import android.widget.ImageView
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import androidx.fragment.app.DialogFragment
|
||||||
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
|
import com.nextcloud.utils.extensions.getParcelableArgument
|
||||||
|
import com.owncloud.android.R
|
||||||
|
import com.owncloud.android.lib.common.utils.Log_OC
|
||||||
|
import com.owncloud.android.ui.activity.CopyToClipboardActivity
|
||||||
|
import java.util.Collections
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dialog showing a list activities able to resolve a given Intent,
|
||||||
|
* filtering out the activities matching give package names.
|
||||||
|
*/
|
||||||
|
class ShareLinkToDialog : DialogFragment() {
|
||||||
|
private var mAdapter: ActivityAdapter? = null
|
||||||
|
private var mIntent: Intent? = null
|
||||||
|
|
||||||
|
init {
|
||||||
|
Log_OC.d(TAG, "constructor")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("SpreadOperator")
|
||||||
|
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||||
|
mIntent = arguments.getParcelableArgument(ARG_INTENT, Intent::class.java) ?: throw NullPointerException()
|
||||||
|
val packagesToExclude = arguments?.getStringArray(ARG_PACKAGES_TO_EXCLUDE)
|
||||||
|
val packagesToExcludeList = listOf(*packagesToExclude ?: arrayOfNulls(0))
|
||||||
|
val pm = activity?.packageManager ?: throw NullPointerException()
|
||||||
|
|
||||||
|
val activities = pm.queryIntentActivities(mIntent!!, PackageManager.MATCH_DEFAULT_ONLY)
|
||||||
|
val it = activities.iterator()
|
||||||
|
var resolveInfo: ResolveInfo
|
||||||
|
while (it.hasNext()) {
|
||||||
|
resolveInfo = it.next()
|
||||||
|
if (packagesToExcludeList.contains(resolveInfo.activityInfo.packageName.lowercase())) {
|
||||||
|
it.remove()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val sendAction = mIntent?.getBooleanExtra(Intent.ACTION_SEND, false)
|
||||||
|
|
||||||
|
if (sendAction == false) {
|
||||||
|
// add activity for copy to clipboard
|
||||||
|
val copyToClipboardIntent = Intent(requireActivity(), CopyToClipboardActivity::class.java)
|
||||||
|
val copyToClipboard = pm.queryIntentActivities(copyToClipboardIntent, 0)
|
||||||
|
if (copyToClipboard.isNotEmpty()) {
|
||||||
|
activities.add(copyToClipboard[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Collections.sort(activities, ResolveInfo.DisplayNameComparator(pm))
|
||||||
|
mAdapter = ActivityAdapter(requireActivity(), pm, activities)
|
||||||
|
|
||||||
|
return createSelector(sendAction ?: false)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createSelector(sendAction: Boolean): AlertDialog {
|
||||||
|
val titleId = if (sendAction) {
|
||||||
|
R.string.activity_chooser_send_file_title
|
||||||
|
} else {
|
||||||
|
R.string.activity_chooser_title
|
||||||
|
}
|
||||||
|
|
||||||
|
return MaterialAlertDialogBuilder(requireActivity())
|
||||||
|
.setTitle(titleId)
|
||||||
|
.setAdapter(mAdapter) { _, which ->
|
||||||
|
// Add the information of the chosen activity to the intent to send
|
||||||
|
val chosen = mAdapter?.getItem(which)
|
||||||
|
val actInfo = chosen?.activityInfo ?: return@setAdapter
|
||||||
|
val name = ComponentName(
|
||||||
|
actInfo.applicationInfo.packageName,
|
||||||
|
actInfo.name
|
||||||
|
)
|
||||||
|
mIntent?.setComponent(name)
|
||||||
|
activity?.startActivity(mIntent)
|
||||||
|
}
|
||||||
|
.create()
|
||||||
|
}
|
||||||
|
|
||||||
|
internal inner class ActivityAdapter(
|
||||||
|
context: Context,
|
||||||
|
private val mPackageManager: PackageManager,
|
||||||
|
apps: List<ResolveInfo>
|
||||||
|
) : ArrayAdapter<ResolveInfo?>(context, R.layout.activity_row, apps) {
|
||||||
|
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
|
||||||
|
val view = convertView ?: newView(parent)
|
||||||
|
bindView(position, view)
|
||||||
|
return view
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun newView(parent: ViewGroup): View {
|
||||||
|
return (context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater)
|
||||||
|
.inflate(R.layout.activity_row, parent, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun bindView(position: Int, row: View) {
|
||||||
|
row.findViewById<TextView>(R.id.title).run {
|
||||||
|
text = getItem(position)?.loadLabel(mPackageManager)
|
||||||
|
}
|
||||||
|
|
||||||
|
row.findViewById<ImageView>(R.id.icon).run {
|
||||||
|
setImageDrawable(getItem(position)?.loadIcon(mPackageManager))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val TAG: String = ShareLinkToDialog::class.java.simpleName
|
||||||
|
private val ARG_INTENT = ShareLinkToDialog::class.java.simpleName +
|
||||||
|
".ARG_INTENT"
|
||||||
|
private val ARG_PACKAGES_TO_EXCLUDE = ShareLinkToDialog::class.java.simpleName +
|
||||||
|
".ARG_PACKAGES_TO_EXCLUDE"
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun newInstance(intent: Intent?, vararg packagesToExclude: String?): ShareLinkToDialog {
|
||||||
|
val bundle = Bundle().apply {
|
||||||
|
putParcelable(ARG_INTENT, intent)
|
||||||
|
putStringArray(ARG_PACKAGES_TO_EXCLUDE, packagesToExclude)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ShareLinkToDialog().apply {
|
||||||
|
arguments = bundle
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -108,7 +108,7 @@ public final class FilesSyncHelper {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void insertAllDBEntriesForSyncedFolder(SyncedFolder syncedFolder) {
|
public static void insertAllDBEntriesForSyncedFolder(SyncedFolder syncedFolder) {
|
||||||
final Context context = MainApp.getAppContext();
|
final Context context = MainApp.getAppContext();
|
||||||
final ContentResolver contentResolver = context.getContentResolver();
|
final ContentResolver contentResolver = context.getContentResolver();
|
||||||
|
|
||||||
|
@ -146,18 +146,6 @@ public final class FilesSyncHelper {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void insertAllDBEntries(SyncedFolder syncedFolder,
|
|
||||||
PowerManagementService powerManagementService) {
|
|
||||||
if (syncedFolder.isEnabled() &&
|
|
||||||
!(syncedFolder.isChargingOnly() &&
|
|
||||||
!powerManagementService.getBattery().isCharging() &&
|
|
||||||
!powerManagementService.getBattery().isFull()
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
insertAllDBEntriesForSyncedFolder(syncedFolder);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void insertChangedEntries(SyncedFolder syncedFolder,
|
public static void insertChangedEntries(SyncedFolder syncedFolder,
|
||||||
String[] changedFiles) {
|
String[] changedFiles) {
|
||||||
final ContentResolver contentResolver = MainApp.getAppContext().getContentResolver();
|
final ContentResolver contentResolver = MainApp.getAppContext().getContentResolver();
|
||||||
|
@ -248,66 +236,13 @@ public final class FilesSyncHelper {
|
||||||
final UserAccountManager accountManager,
|
final UserAccountManager accountManager,
|
||||||
final ConnectivityService connectivityService,
|
final ConnectivityService connectivityService,
|
||||||
final PowerManagementService powerManagementService) {
|
final PowerManagementService powerManagementService) {
|
||||||
boolean accountExists;
|
|
||||||
|
|
||||||
boolean whileChargingOnly = true;
|
|
||||||
boolean useWifiOnly = true;
|
|
||||||
|
|
||||||
// Make all in progress downloads failed to restart upload worker
|
|
||||||
uploadsStorageManager.failInProgressUploads(UploadResult.SERVICE_INTERRUPTED);
|
|
||||||
|
|
||||||
OCUpload[] failedUploads = uploadsStorageManager.getFailedUploads();
|
|
||||||
|
|
||||||
for (OCUpload failedUpload : failedUploads) {
|
|
||||||
accountExists = false;
|
|
||||||
if (!failedUpload.isWhileChargingOnly()) {
|
|
||||||
whileChargingOnly = false;
|
|
||||||
}
|
|
||||||
if (!failedUpload.isUseWifiOnly()) {
|
|
||||||
useWifiOnly = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// check if accounts still exists
|
|
||||||
for (Account account : accountManager.getAccounts()) {
|
|
||||||
if (account.name.equals(failedUpload.getAccountName())) {
|
|
||||||
accountExists = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!accountExists) {
|
|
||||||
uploadsStorageManager.removeUpload(failedUpload);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
failedUploads = uploadsStorageManager.getFailedUploads();
|
|
||||||
if (failedUploads.length == 0) {
|
|
||||||
//nothing to do
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (whileChargingOnly) {
|
|
||||||
final BatteryStatus batteryStatus = powerManagementService.getBattery();
|
|
||||||
final boolean charging = batteryStatus.isCharging() || batteryStatus.isFull();
|
|
||||||
if (!charging) {
|
|
||||||
//all uploads requires charging
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (useWifiOnly && !connectivityService.getConnectivity().isWifi()) {
|
|
||||||
//all uploads requires wifi
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
new Thread(() -> {
|
new Thread(() -> {
|
||||||
if (connectivityService.getConnectivity().isConnected()) {
|
FileUploadHelper.Companion.instance().retryFailedUploads(
|
||||||
FileUploadHelper.Companion.instance().retryFailedUploads(
|
uploadsStorageManager,
|
||||||
uploadsStorageManager,
|
connectivityService,
|
||||||
connectivityService,
|
accountManager,
|
||||||
accountManager,
|
powerManagementService);
|
||||||
powerManagementService);
|
|
||||||
}
|
|
||||||
}).start();
|
}).start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -146,7 +146,7 @@
|
||||||
<string name="common_ok">Aceptar</string>
|
<string name="common_ok">Aceptar</string>
|
||||||
<string name="common_pending">Pendente</string>
|
<string name="common_pending">Pendente</string>
|
||||||
<string name="common_remove">Eliminar</string>
|
<string name="common_remove">Eliminar</string>
|
||||||
<string name="common_rename">Renomear</string>
|
<string name="common_rename">Cambiar o nome</string>
|
||||||
<string name="common_save">Gardar</string>
|
<string name="common_save">Gardar</string>
|
||||||
<string name="common_send">Enviar</string>
|
<string name="common_send">Enviar</string>
|
||||||
<string name="common_share">Compartir</string>
|
<string name="common_share">Compartir</string>
|
||||||
|
@ -407,14 +407,14 @@
|
||||||
<string name="file_migration_waiting_for_unfinished_sync">Agardando que rematen as sincronizacións…</string>
|
<string name="file_migration_waiting_for_unfinished_sync">Agardando que rematen as sincronizacións…</string>
|
||||||
<string name="file_not_found">Non se atopou o ficheiro</string>
|
<string name="file_not_found">Non se atopou o ficheiro</string>
|
||||||
<string name="file_not_synced">Non foi posíbel sincronizar o ficheiro. Amosase a última versión dispoñíbel.</string>
|
<string name="file_not_synced">Non foi posíbel sincronizar o ficheiro. Amosase a última versión dispoñíbel.</string>
|
||||||
<string name="file_rename">Renomear</string>
|
<string name="file_rename">Cambiar o nome</string>
|
||||||
<string name="file_upload_worker_same_file_already_exists">Xa existe o mesmo ficheiro, non se detectou ningún conflito</string>
|
<string name="file_upload_worker_same_file_already_exists">Xa existe o mesmo ficheiro, non se detectou ningún conflito</string>
|
||||||
<string name="file_version_restored_error">Produciuse un erro ao restaurar a versión do ficheiro</string>
|
<string name="file_version_restored_error">Produciuse un erro ao restaurar a versión do ficheiro</string>
|
||||||
<string name="file_version_restored_successfully">Versión do ficheiro restaurada satisfactoriamente.</string>
|
<string name="file_version_restored_successfully">Versión do ficheiro restaurada satisfactoriamente.</string>
|
||||||
<string name="filedetails_details">Detalles</string>
|
<string name="filedetails_details">Detalles</string>
|
||||||
<string name="filedetails_download">Descargar</string>
|
<string name="filedetails_download">Descargar</string>
|
||||||
<string name="filedetails_export">Exportar</string>
|
<string name="filedetails_export">Exportar</string>
|
||||||
<string name="filedetails_renamed_in_upload_msg">O ficheiro foi renomeado a %1$s durante o envío</string>
|
<string name="filedetails_renamed_in_upload_msg">Cambiouselle o nome do ficheiro a %1$s durante o envío</string>
|
||||||
<string name="filedetails_sync_file">Sincronizar</string>
|
<string name="filedetails_sync_file">Sincronizar</string>
|
||||||
<string name="filedisplay_no_file_selected">Non seleccionou ningún ficheiro</string>
|
<string name="filedisplay_no_file_selected">Non seleccionou ningún ficheiro</string>
|
||||||
<string name="filename_empty">O nome de ficheiro non pode estar baleiro</string>
|
<string name="filename_empty">O nome de ficheiro non pode estar baleiro</string>
|
||||||
|
@ -437,7 +437,7 @@
|
||||||
<string name="forbidden_permissions_create">para crear este ficheiro</string>
|
<string name="forbidden_permissions_create">para crear este ficheiro</string>
|
||||||
<string name="forbidden_permissions_delete">para eliminar este ficheiro</string>
|
<string name="forbidden_permissions_delete">para eliminar este ficheiro</string>
|
||||||
<string name="forbidden_permissions_move">para mover este ficheiro</string>
|
<string name="forbidden_permissions_move">para mover este ficheiro</string>
|
||||||
<string name="forbidden_permissions_rename">para renomear este ficheiro</string>
|
<string name="forbidden_permissions_rename">para cambiarlle o nome a este ficheiro</string>
|
||||||
<string name="foreground_service_upload">Enviando ficheiros…</string>
|
<string name="foreground_service_upload">Enviando ficheiros…</string>
|
||||||
<string name="foreign_files_fail">Non foi posíbel mover algúns ficheiros</string>
|
<string name="foreign_files_fail">Non foi posíbel mover algúns ficheiros</string>
|
||||||
<string name="foreign_files_local_text">Local: %1$s</string>
|
<string name="foreign_files_local_text">Local: %1$s</string>
|
||||||
|
@ -693,8 +693,8 @@
|
||||||
<string name="remove_push_notification">Retirar</string>
|
<string name="remove_push_notification">Retirar</string>
|
||||||
<string name="remove_success_msg">Eliminado</string>
|
<string name="remove_success_msg">Eliminado</string>
|
||||||
<string name="rename_dialog_title">Introduza un nome novo</string>
|
<string name="rename_dialog_title">Introduza un nome novo</string>
|
||||||
<string name="rename_local_fail_msg">Non foi posíbel renomear a copia local, ténteo cun un nome diferente</string>
|
<string name="rename_local_fail_msg">Non foi posíbel cambiarlle o nome a copia local, ténteo cun un nome diferente</string>
|
||||||
<string name="rename_server_fail_msg">Non foi posíbel renomear, o nome xa está ocupado</string>
|
<string name="rename_server_fail_msg">Non foi posíbel cambiarlle o nome, o nome xa está ocupado</string>
|
||||||
<string name="request_account_deletion">Solicitar a eliminación da conta </string>
|
<string name="request_account_deletion">Solicitar a eliminación da conta </string>
|
||||||
<string name="request_account_deletion_button">Solicitat a eliminación</string>
|
<string name="request_account_deletion_button">Solicitat a eliminación</string>
|
||||||
<string name="request_account_deletion_details">Solicitar a eliminación definitiva da conta polo provedor de servizos</string>
|
<string name="request_account_deletion_details">Solicitar a eliminación definitiva da conta polo provedor de servizos</string>
|
||||||
|
|
|
@ -13,12 +13,12 @@ buildscript {
|
||||||
androidLibraryVersion ="50ef03422e17bbcbdbbdc868a27749f548488ec6"
|
androidLibraryVersion ="50ef03422e17bbcbdbbdc868a27749f548488ec6"
|
||||||
androidPluginVersion = '8.5.0'
|
androidPluginVersion = '8.5.0'
|
||||||
androidxMediaVersion = '1.3.1'
|
androidxMediaVersion = '1.3.1'
|
||||||
androidxTestVersion = "1.5.0"
|
androidxTestVersion = "1.6.1"
|
||||||
appCompatVersion = '1.7.0'
|
appCompatVersion = '1.7.0'
|
||||||
checkerVersion = "3.21.2"
|
checkerVersion = "3.21.2"
|
||||||
daggerVersion = "2.51.1"
|
daggerVersion = "2.51.1"
|
||||||
documentScannerVersion = "1.1.1"
|
documentScannerVersion = "1.1.1"
|
||||||
espressoVersion = "3.5.1"
|
espressoVersion = "3.6.1"
|
||||||
fidoVersion = "4.1.0-patch2"
|
fidoVersion = "4.1.0-patch2"
|
||||||
jacoco_version = '0.8.12'
|
jacoco_version = '0.8.12'
|
||||||
kotlin_version = '2.0.0'
|
kotlin_version = '2.0.0'
|
||||||
|
|
|
@ -248,6 +248,7 @@
|
||||||
<trusting group="androidx.transition" name="transition" version="1.5.0"/>
|
<trusting group="androidx.transition" name="transition" version="1.5.0"/>
|
||||||
<trusting group="androidx.webkit" name="webkit" version="1.11.0"/>
|
<trusting group="androidx.webkit" name="webkit" version="1.11.0"/>
|
||||||
<trusting group="^androidx[.]compose($|([.].*))" regex="true"/>
|
<trusting group="^androidx[.]compose($|([.].*))" regex="true"/>
|
||||||
|
<trusting group="^androidx[.]test($|([.].*))" regex="true"/>
|
||||||
<trusting group="^com[.]android($|([.].*))" regex="true"/>
|
<trusting group="^com[.]android($|([.].*))" regex="true"/>
|
||||||
</trusted-key>
|
</trusted-key>
|
||||||
<trusted-key id="A6D6C97108B8585F91B158748671A8DF71296252" group="^com[.]squareup($|([.].*))" regex="true"/>
|
<trusted-key id="A6D6C97108B8585F91B158748671A8DF71296252" group="^com[.]squareup($|([.].*))" regex="true"/>
|
||||||
|
@ -379,6 +380,14 @@
|
||||||
<sha256 value="b7730754793e2fa510ddb10b7514e65f8706e4ec4b100acf7e4215f0bd5519b4" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
<sha256 value="b7730754793e2fa510ddb10b7514e65f8706e4ec4b100acf7e4215f0bd5519b4" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||||
</artifact>
|
</artifact>
|
||||||
</component>
|
</component>
|
||||||
|
<component group="androidx.activity" name="activity" version="1.2.4">
|
||||||
|
<artifact name="activity-1.2.4.aar">
|
||||||
|
<sha256 value="ae8e9c7de57e387d2ad90e73f3a5a5dfd502bd4f034c1dccfdb3506d1d2df81a" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||||
|
</artifact>
|
||||||
|
<artifact name="activity-1.2.4.module">
|
||||||
|
<sha256 value="20f5634f76717910e5b4299909d6998a8078f106bdb9e15b2dd75fcfc1bd42b5" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||||
|
</artifact>
|
||||||
|
</component>
|
||||||
<component group="androidx.activity" name="activity" version="1.5.1">
|
<component group="androidx.activity" name="activity" version="1.5.1">
|
||||||
<artifact name="activity-1.5.1.module">
|
<artifact name="activity-1.5.1.module">
|
||||||
<sha256 value="c4317fb95ce2716b88f1f4a5334795efda084097a3f2447ffccb10a412c85be4" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
<sha256 value="c4317fb95ce2716b88f1f4a5334795efda084097a3f2447ffccb10a412c85be4" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||||
|
@ -595,6 +604,14 @@
|
||||||
<sha256 value="2670902fc6c26047c42e0f60ee34656fa071841db370de958198413b5ab58fc3" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
<sha256 value="2670902fc6c26047c42e0f60ee34656fa071841db370de958198413b5ab58fc3" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||||
</artifact>
|
</artifact>
|
||||||
</component>
|
</component>
|
||||||
|
<component group="androidx.appcompat" name="appcompat" version="1.3.1">
|
||||||
|
<artifact name="appcompat-1.3.1.aar">
|
||||||
|
<sha256 value="959b1daefe40d5e7eed1022f97730b22bc5fd52e6a6083eba284fa86c2971303" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||||
|
</artifact>
|
||||||
|
<artifact name="appcompat-1.3.1.module">
|
||||||
|
<sha256 value="a1cfeb760e51aa3363a72796ed5bed7ba501d94df342f0a9160b67d4f213cc5e" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||||
|
</artifact>
|
||||||
|
</component>
|
||||||
<component group="androidx.appcompat" name="appcompat" version="1.6.1">
|
<component group="androidx.appcompat" name="appcompat" version="1.6.1">
|
||||||
<artifact name="appcompat-1.6.1.aar">
|
<artifact name="appcompat-1.6.1.aar">
|
||||||
<sha256 value="7ea5573b93ababd3bd32312451c6ea48a662b03a140dda81aebe75776a20a422" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
<sha256 value="7ea5573b93ababd3bd32312451c6ea48a662b03a140dda81aebe75776a20a422" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||||
|
@ -619,6 +636,14 @@
|
||||||
<sha256 value="75d9865bc6b0f779043b4f6523a944bd2ccb7526f5c1f5ef24624ac78dec3bd3" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
<sha256 value="75d9865bc6b0f779043b4f6523a944bd2ccb7526f5c1f5ef24624ac78dec3bd3" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||||
</artifact>
|
</artifact>
|
||||||
</component>
|
</component>
|
||||||
|
<component group="androidx.appcompat" name="appcompat-resources" version="1.3.1">
|
||||||
|
<artifact name="appcompat-resources-1.3.1.aar">
|
||||||
|
<sha256 value="e3306cd3e9a19a28a5de5ec3b379580f237c4d81c15c3d795404be9291890a75" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||||
|
</artifact>
|
||||||
|
<artifact name="appcompat-resources-1.3.1.module">
|
||||||
|
<sha256 value="757bb47d84171d055fe67cf6b88efc7a42c05bdb673b8a89a1a3c25a589d4848" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||||
|
</artifact>
|
||||||
|
</component>
|
||||||
<component group="androidx.appcompat" name="appcompat-resources" version="1.6.1">
|
<component group="androidx.appcompat" name="appcompat-resources" version="1.6.1">
|
||||||
<artifact name="appcompat-resources-1.6.1.aar">
|
<artifact name="appcompat-resources-1.6.1.aar">
|
||||||
<sha256 value="db915dbf49357863de1669ff9fdd8e9008d65fe357af6cce9ae63043ad5f6617" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
<sha256 value="db915dbf49357863de1669ff9fdd8e9008d65fe357af6cce9ae63043ad5f6617" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||||
|
@ -1004,6 +1029,14 @@
|
||||||
<sha256 value="77639a0b051e22510bad93affcea0ebd781ef124bf9b7621a95749937bcfcdfd" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
<sha256 value="77639a0b051e22510bad93affcea0ebd781ef124bf9b7621a95749937bcfcdfd" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||||
</artifact>
|
</artifact>
|
||||||
</component>
|
</component>
|
||||||
|
<component group="androidx.concurrent" name="concurrent-futures-ktx" version="1.1.0">
|
||||||
|
<artifact name="concurrent-futures-ktx-1.1.0.jar">
|
||||||
|
<sha256 value="1968bf52039e38636aa6f114cd17d7256919d1e8997417716fef9d1da1f24d85" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||||
|
</artifact>
|
||||||
|
<artifact name="concurrent-futures-ktx-1.1.0.module">
|
||||||
|
<sha256 value="69b79724566d49140846700690b8d2165231c577e93e66726a443e8f976bbe19" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||||
|
</artifact>
|
||||||
|
</component>
|
||||||
<component group="androidx.constraintlayout" name="constraintlayout" version="2.0.1">
|
<component group="androidx.constraintlayout" name="constraintlayout" version="2.0.1">
|
||||||
<artifact name="constraintlayout-2.0.1.aar">
|
<artifact name="constraintlayout-2.0.1.aar">
|
||||||
<sha256 value="ec15b5d4a2eff07888bc1499ce2e2c6efe24c0ed60cc57b08c9dc4b6fd3c2189" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
<sha256 value="ec15b5d4a2eff07888bc1499ce2e2c6efe24c0ed60cc57b08c9dc4b6fd3c2189" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||||
|
@ -1112,6 +1145,14 @@
|
||||||
<sha256 value="988f820899d5a4982e5c878ca1cd417970ace332ea2ff72f5be19b233fa0e788" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
<sha256 value="988f820899d5a4982e5c878ca1cd417970ace332ea2ff72f5be19b233fa0e788" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||||
</artifact>
|
</artifact>
|
||||||
</component>
|
</component>
|
||||||
|
<component group="androidx.core" name="core" version="1.8.0">
|
||||||
|
<artifact name="core-1.8.0.aar">
|
||||||
|
<sha256 value="48c64a15ec3eb11bfb33339e5ceb70ec7f821bd2dfa2eb8675ebd5353317e792" origin="Generated by Gradle"/>
|
||||||
|
</artifact>
|
||||||
|
<artifact name="core-1.8.0.module">
|
||||||
|
<sha256 value="505f1838869611519d65ec7feb650e88856e3682e37e42a2a24b73e655a98d74" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||||
|
</artifact>
|
||||||
|
</component>
|
||||||
<component group="androidx.core" name="core" version="1.9.0">
|
<component group="androidx.core" name="core" version="1.9.0">
|
||||||
<artifact name="core-1.9.0.aar">
|
<artifact name="core-1.9.0.aar">
|
||||||
<sha256 value="8bda3ee3a88887d54f6679fb6b6cd788629f73234ac91c8bbed924e721ec85b8" origin="Generated by Gradle"/>
|
<sha256 value="8bda3ee3a88887d54f6679fb6b6cd788629f73234ac91c8bbed924e721ec85b8" origin="Generated by Gradle"/>
|
||||||
|
@ -2936,6 +2977,14 @@
|
||||||
<sha256 value="fc8b21ebe5fa3a7c96ee098bcdcd00f077ebce73f243fa858e2b0671615f75d8" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
<sha256 value="fc8b21ebe5fa3a7c96ee098bcdcd00f077ebce73f243fa858e2b0671615f75d8" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||||
</artifact>
|
</artifact>
|
||||||
</component>
|
</component>
|
||||||
|
<component group="androidx.tracing" name="tracing" version="1.1.0">
|
||||||
|
<artifact name="tracing-1.1.0.aar">
|
||||||
|
<sha256 value="5b78e2c618fc10b3d14decc01df76158f15954ad746aacf0607766721da081f6" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||||
|
</artifact>
|
||||||
|
<artifact name="tracing-1.1.0.module">
|
||||||
|
<sha256 value="b1fed4309623b6f20bc817d8fbd70e4ea7085e40647694cd399ae58d2f0049e3" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||||
|
</artifact>
|
||||||
|
</component>
|
||||||
<component group="androidx.transition" name="transition" version="1.2.0">
|
<component group="androidx.transition" name="transition" version="1.2.0">
|
||||||
<artifact name="transition-1.2.0.aar">
|
<artifact name="transition-1.2.0.aar">
|
||||||
<sha256 value="a1e059b3bc0b43a58dec0efecdcaa89c82d2bca552ea5bacf6656c46e853157e" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
<sha256 value="a1e059b3bc0b43a58dec0efecdcaa89c82d2bca552ea5bacf6656c46e853157e" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||||
|
|
Loading…
Reference in a new issue