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
|
||||
|
||||
on: pull_request
|
||||
on: [pull_request]
|
||||
|
||||
jobs:
|
||||
reuse-compliance-check:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
- name: Checkout
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
|
||||
- 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 'org.lukhnos:nnio:0.3.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.chrisbanes:PhotoView:2.3.0'
|
||||
implementation 'pl.droidsonroids.gif:android-gif-drawable:1.2.28'
|
||||
|
@ -310,7 +310,7 @@ dependencies {
|
|||
}
|
||||
implementation 'com.caverock:androidsvg:1.4'
|
||||
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-fido2:$fidoVersion"
|
||||
|
@ -378,11 +378,11 @@ dependencies {
|
|||
|
||||
// dependencies for instrumented tests
|
||||
// JUnit4 Rules
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.2.1'
|
||||
androidTestImplementation "androidx.test:rules:$androidxTestVersion"
|
||||
// Android JUnit Runner
|
||||
androidTestImplementation "androidx.test:runner:1.5.2"
|
||||
androidTestUtil "androidx.test:orchestrator:1.4.2"
|
||||
androidTestImplementation "androidx.test:runner:1.6.1"
|
||||
androidTestUtil "androidx.test:orchestrator:1.5.0"
|
||||
androidTestImplementation "androidx.test:core-ktx:$androidxTestVersion"
|
||||
|
||||
// Espresso
|
||||
|
|
|
@ -182,7 +182,7 @@ class FilesSyncWork(
|
|||
} else {
|
||||
// Check every file in synced folder for changes and update
|
||||
// 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 com.nextcloud.client.account.User
|
||||
import com.nextcloud.client.account.UserAccountManager
|
||||
import com.nextcloud.client.device.BatteryStatus
|
||||
import com.nextcloud.client.device.PowerManagementService
|
||||
import com.nextcloud.client.jobs.BackgroundJobManager
|
||||
import com.nextcloud.client.jobs.upload.FileUploadWorker.Companion.currentUploadFileOperation
|
||||
import com.nextcloud.client.network.Connectivity
|
||||
import com.nextcloud.client.network.ConnectivityService
|
||||
import com.owncloud.android.MainApp
|
||||
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.resources.files.ReadFileRemoteOperation
|
||||
import com.owncloud.android.lib.resources.files.model.RemoteFile
|
||||
import com.owncloud.android.operations.UploadFileOperation
|
||||
import com.owncloud.android.utils.FileUtil
|
||||
import java.io.File
|
||||
import java.util.Optional
|
||||
import javax.inject.Inject
|
||||
|
||||
@Suppress("TooManyFunctions")
|
||||
|
@ -117,34 +119,44 @@ class FileUploadHelper {
|
|||
failedUploads: Array<OCUpload>
|
||||
): Boolean {
|
||||
var showNotExistMessage = false
|
||||
val (gotNetwork, _, gotWifi) = connectivityService.connectivity
|
||||
val isOnline = checkConnectivity(connectivityService)
|
||||
val connectivity = connectivityService.connectivity
|
||||
val batteryStatus = powerManagementService.battery
|
||||
val charging = batteryStatus.isCharging || batteryStatus.isFull
|
||||
val isPowerSaving = powerManagementService.isPowerSavingEnabled
|
||||
var uploadUser = Optional.empty<User>()
|
||||
val accountNames = accountManager.accounts.filter { account ->
|
||||
accountManager.getUser(account.name).isPresent
|
||||
}.map { account ->
|
||||
account.name
|
||||
}.toHashSet()
|
||||
|
||||
for (failedUpload in failedUploads) {
|
||||
val isDeleted = !File(failedUpload.localPath).exists()
|
||||
if (isDeleted) {
|
||||
showNotExistMessage = true
|
||||
if (!accountNames.contains(failedUpload.accountName)) {
|
||||
uploadsStorageManager.removeUpload(failedUpload)
|
||||
continue
|
||||
}
|
||||
|
||||
// 2A. for deleted files, mark as permanently failed
|
||||
if (failedUpload.lastResult != UploadResult.FILE_NOT_FOUND) {
|
||||
failedUpload.lastResult = UploadResult.FILE_NOT_FOUND
|
||||
val uploadResult =
|
||||
checkUploadConditions(failedUpload, connectivity, batteryStatus, powerManagementService, isOnline)
|
||||
|
||||
if (uploadResult != UploadResult.UPLOADED) {
|
||||
if (failedUpload.lastResult != uploadResult) {
|
||||
failedUpload.lastResult = uploadResult
|
||||
uploadsStorageManager.updateUpload(failedUpload)
|
||||
}
|
||||
} else if (!isPowerSaving && gotNetwork &&
|
||||
canUploadBeRetried(failedUpload, gotWifi, charging) && !connectivityService.isInternetWalled
|
||||
) {
|
||||
// 2B. for existing local files, try restarting it if possible
|
||||
failedUpload.uploadStatus = UploadStatus.UPLOAD_IN_PROGRESS
|
||||
uploadsStorageManager.updateUpload(failedUpload)
|
||||
if (uploadResult == UploadResult.FILE_NOT_FOUND) {
|
||||
showNotExistMessage = true
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
failedUpload.uploadStatus = UploadStatus.UPLOAD_IN_PROGRESS
|
||||
uploadsStorageManager.updateUpload(failedUpload)
|
||||
}
|
||||
|
||||
accountManager.accounts.forEach {
|
||||
val user = accountManager.getUser(it.name)
|
||||
if (user.isPresent) backgroundJobManager.startFilesUploadJob(user.get())
|
||||
accountNames.forEach { accountName ->
|
||||
val user = accountManager.getUser(accountName)
|
||||
if (user.isPresent) {
|
||||
backgroundJobManager.startFilesUploadJob(user.get())
|
||||
}
|
||||
}
|
||||
|
||||
return showNotExistMessage
|
||||
|
@ -216,11 +228,50 @@ class FileUploadHelper {
|
|||
return upload.uploadStatus == UploadStatus.UPLOAD_IN_PROGRESS
|
||||
}
|
||||
|
||||
private fun canUploadBeRetried(upload: OCUpload, gotWifi: Boolean, isCharging: Boolean): Boolean {
|
||||
val file = File(upload.localPath)
|
||||
val needsWifi = upload.isUseWifiOnly
|
||||
val needsCharging = upload.isWhileChargingOnly
|
||||
return file.exists() && (!needsWifi || gotWifi) && (!needsCharging || isCharging)
|
||||
private fun checkConnectivity(connectivityService: ConnectivityService): Boolean {
|
||||
// check that connection isn't walled off and that the server is reachable
|
||||
return connectivityService.getConnectivity().isConnected && !connectivityService.isInternetWalled()
|
||||
}
|
||||
|
||||
/**
|
||||
* 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")
|
||||
|
|
|
@ -130,7 +130,7 @@ class FileUploadWorker(
|
|||
@Suppress("ReturnCount")
|
||||
private fun retrievePagesBySortingUploadsByID(): Result {
|
||||
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)
|
||||
|
||||
Log_OC.d(TAG, "Total upload size: $totalUploadSize")
|
||||
|
@ -148,7 +148,7 @@ class FileUploadWorker(
|
|||
val lastId = uploadsPerPage.last().uploadId
|
||||
uploadFiles(totalUploadSize, uploadsPerPage, accountName)
|
||||
uploadsPerPage =
|
||||
uploadsStorageManager.getCurrentAndPendingUploadsForAccountPageAscById(lastId, accountName)
|
||||
uploadsStorageManager.getCurrentUploadsForAccountPageAscById(lastId, accountName)
|
||||
}
|
||||
|
||||
if (isStopped) {
|
||||
|
|
|
@ -471,7 +471,7 @@ public class UploadsStorageManager extends Observable {
|
|||
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 +
|
||||
OR + ProviderTableMeta.UPLOADS_LAST_RESULT +
|
||||
EQUAL + UploadResult.DELAYED_FOR_WIFI.getValue() +
|
||||
|
@ -485,7 +485,8 @@ public class UploadsStorageManager extends Observable {
|
|||
}
|
||||
|
||||
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;
|
||||
|
||||
Cursor cursor = getDB().query(
|
||||
|
@ -605,17 +606,29 @@ public class UploadsStorageManager extends Observable {
|
|||
}
|
||||
|
||||
public OCUpload[] getCurrentAndPendingUploadsForAccount(final @NonNull String accountName) {
|
||||
String inProgressUploadsSelection = getInProgressUploadsSelection();
|
||||
String inProgressUploadsSelection = getInProgressAndDelayedUploadsSelection();
|
||||
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.
|
||||
* <p>
|
||||
* If <code>afterId</code> is -1, returns the first page
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
|
@ -150,7 +150,7 @@ class ConflictsResolveActivity : FileActivity(), OnConflictDecisionMadeListener
|
|||
outState.putInt(EXTRA_LOCAL_BEHAVIOUR, localBehaviour)
|
||||
}
|
||||
|
||||
override fun conflictDecisionMade(decision: Decision) {
|
||||
override fun conflictDecisionMade(decision: Decision?) {
|
||||
listener?.conflictDecisionMade(decision)
|
||||
}
|
||||
|
||||
|
@ -205,10 +205,10 @@ class ConflictsResolveActivity : FileActivity(), OnConflictDecisionMadeListener
|
|||
if (prev != null) {
|
||||
fragmentTransaction.remove(prev)
|
||||
}
|
||||
if (existingFile != null && storageManager.fileExists(remotePath)) {
|
||||
if (existingFile != null && storageManager.fileExists(remotePath) && newFile != null) {
|
||||
val dialog = ConflictsResolveDialog.newInstance(
|
||||
existingFile,
|
||||
newFile,
|
||||
newFile!!,
|
||||
userOptional.get()
|
||||
)
|
||||
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 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,
|
||||
String[] changedFiles) {
|
||||
final ContentResolver contentResolver = MainApp.getAppContext().getContentResolver();
|
||||
|
@ -248,66 +236,13 @@ public final class FilesSyncHelper {
|
|||
final UserAccountManager accountManager,
|
||||
final ConnectivityService connectivityService,
|
||||
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(() -> {
|
||||
if (connectivityService.getConnectivity().isConnected()) {
|
||||
FileUploadHelper.Companion.instance().retryFailedUploads(
|
||||
uploadsStorageManager,
|
||||
connectivityService,
|
||||
accountManager,
|
||||
powerManagementService);
|
||||
}
|
||||
FileUploadHelper.Companion.instance().retryFailedUploads(
|
||||
uploadsStorageManager,
|
||||
connectivityService,
|
||||
accountManager,
|
||||
powerManagementService);
|
||||
}).start();
|
||||
}
|
||||
|
||||
|
|
|
@ -146,7 +146,7 @@
|
|||
<string name="common_ok">Aceptar</string>
|
||||
<string name="common_pending">Pendente</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_send">Enviar</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_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_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_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="filedetails_details">Detalles</string>
|
||||
<string name="filedetails_download">Descargar</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="filedisplay_no_file_selected">Non seleccionou ningún ficheiro</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_delete">para eliminar 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="foreign_files_fail">Non foi posíbel mover algúns ficheiros</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_success_msg">Eliminado</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_server_fail_msg">Non foi posíbel renomear, o nome xa está ocupado</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 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_button">Solicitat a eliminación</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"
|
||||
androidPluginVersion = '8.5.0'
|
||||
androidxMediaVersion = '1.3.1'
|
||||
androidxTestVersion = "1.5.0"
|
||||
androidxTestVersion = "1.6.1"
|
||||
appCompatVersion = '1.7.0'
|
||||
checkerVersion = "3.21.2"
|
||||
daggerVersion = "2.51.1"
|
||||
documentScannerVersion = "1.1.1"
|
||||
espressoVersion = "3.5.1"
|
||||
espressoVersion = "3.6.1"
|
||||
fidoVersion = "4.1.0-patch2"
|
||||
jacoco_version = '0.8.12'
|
||||
kotlin_version = '2.0.0'
|
||||
|
|
|
@ -248,6 +248,7 @@
|
|||
<trusting group="androidx.transition" name="transition" version="1.5.0"/>
|
||||
<trusting group="androidx.webkit" name="webkit" version="1.11.0"/>
|
||||
<trusting group="^androidx[.]compose($|([.].*))" regex="true"/>
|
||||
<trusting group="^androidx[.]test($|([.].*))" regex="true"/>
|
||||
<trusting group="^com[.]android($|([.].*))" regex="true"/>
|
||||
</trusted-key>
|
||||
<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"/>
|
||||
</artifact>
|
||||
</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">
|
||||
<artifact name="activity-1.5.1.module">
|
||||
<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"/>
|
||||
</artifact>
|
||||
</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">
|
||||
<artifact name="appcompat-1.6.1.aar">
|
||||
<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"/>
|
||||
</artifact>
|
||||
</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">
|
||||
<artifact name="appcompat-resources-1.6.1.aar">
|
||||
<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"/>
|
||||
</artifact>
|
||||
</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">
|
||||
<artifact name="constraintlayout-2.0.1.aar">
|
||||
<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"/>
|
||||
</artifact>
|
||||
</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">
|
||||
<artifact name="core-1.9.0.aar">
|
||||
<sha256 value="8bda3ee3a88887d54f6679fb6b6cd788629f73234ac91c8bbed924e721ec85b8" origin="Generated by Gradle"/>
|
||||
|
@ -2936,6 +2977,14 @@
|
|||
<sha256 value="fc8b21ebe5fa3a7c96ee098bcdcd00f077ebce73f243fa858e2b0671615f75d8" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||
</artifact>
|
||||
</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">
|
||||
<artifact name="transition-1.2.0.aar">
|
||||
<sha256 value="a1e059b3bc0b43a58dec0efecdcaa89c82d2bca552ea5bacf6656c46e853157e" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||
|
|
Loading…
Reference in a new issue