Merge pull request #12527 from nextcloud/feature/global_upload_pause

Add global upload pause button
This commit is contained in:
Jonas Mayer 2024-02-13 23:50:50 +01:00 committed by GitHub
commit 90f588ddad
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 284 additions and 110 deletions

View file

@ -251,6 +251,7 @@ class BackgroundJobFactory @Inject constructor(
viewThemeUtils.get(),
localBroadcastManager.get(),
backgroundJobManager.get(),
preferences,
context,
params
)

View file

@ -160,7 +160,7 @@ class FileUploadHelper {
}
}
private fun cancelAndRestartUploadJob(user: User) {
fun cancelAndRestartUploadJob(user: User) {
backgroundJobManager.run {
cancelFilesUploadJob(user)
startFilesUploadJob(user)

View file

@ -32,6 +32,7 @@ import com.nextcloud.client.device.PowerManagementService
import com.nextcloud.client.jobs.BackgroundJobManager
import com.nextcloud.client.jobs.BackgroundJobManagerImpl
import com.nextcloud.client.network.ConnectivityService
import com.nextcloud.client.preferences.AppPreferences
import com.nextcloud.model.WorkerState
import com.nextcloud.model.WorkerStateLiveData
import com.owncloud.android.datamodel.FileDataStorageManager
@ -58,6 +59,7 @@ class FileUploadWorker(
val viewThemeUtils: ViewThemeUtils,
val localBroadcastManager: LocalBroadcastManager,
private val backgroundJobManager: BackgroundJobManager,
val preferences: AppPreferences,
val context: Context,
params: WorkerParameters
) : Worker(context, params), OnDatatransferProgressListener {
@ -136,11 +138,20 @@ class FileUploadWorker(
WorkerStateLiveData.instance().setWorkState(WorkerState.Idle)
}
@Suppress("ReturnCount")
private fun retrievePagesBySortingUploadsByID(): Result {
val accountName = inputData.getString(ACCOUNT) ?: return Result.failure()
var currentPage = uploadsStorageManager.getCurrentAndPendingUploadsForAccountPageAscById(-1, accountName)
while (currentPage.isNotEmpty() && !isStopped) {
if (preferences.isGlobalUploadPaused) {
Log_OC.d(TAG, "Upload is paused, skip uploading files!")
notificationManager.notifyPaused(
intents.notificationStartIntent(null)
)
return Result.success()
}
Log_OC.d(TAG, "Handling ${currentPage.size} uploads for account $accountName")
val lastId = currentPage.last().uploadId
uploadFiles(currentPage, accountName)

View file

@ -97,10 +97,10 @@ class FileUploaderIntents(private val context: Context) {
)
}
fun notificationStartIntent(operation: UploadFileOperation): PendingIntent {
fun notificationStartIntent(operation: UploadFileOperation?): PendingIntent {
val intent = UploadListActivity.createIntent(
operation.file,
operation.user,
operation?.file,
operation?.user,
Intent.FLAG_ACTIVITY_CLEAR_TOP,
context
)

View file

@ -35,17 +35,14 @@ import com.owncloud.android.operations.UploadFileOperation
import com.owncloud.android.ui.notifications.NotificationUtils
import com.owncloud.android.utils.theme.ViewThemeUtils
class UploadNotificationManager(private val context: Context, private val viewThemeUtils: ViewThemeUtils) {
class UploadNotificationManager(private val context: Context, viewThemeUtils: ViewThemeUtils) {
companion object {
private const val ID = 411
}
private var notification: Notification? = null
private var notificationBuilder: NotificationCompat.Builder
private val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
init {
notificationBuilder = NotificationUtils.newNotificationBuilder(context, viewThemeUtils).apply {
private var notificationBuilder: NotificationCompat.Builder =
NotificationUtils.newNotificationBuilder(context, viewThemeUtils).apply {
setContentTitle(context.getString(R.string.foreground_service_upload))
setSmallIcon(R.drawable.notification_icon)
setLargeIcon(BitmapFactory.decodeResource(context.resources, R.drawable.notification_icon))
@ -54,18 +51,16 @@ class UploadNotificationManager(private val context: Context, private val viewTh
setChannelId(NotificationUtils.NOTIFICATION_CHANNEL_UPLOAD)
}
}
private val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
init {
notification = notificationBuilder.build()
}
@Suppress("MagicNumber")
fun prepareForStart(upload: UploadFileOperation, pendingIntent: PendingIntent, startIntent: PendingIntent) {
notificationBuilder = NotificationUtils.newNotificationBuilder(context, viewThemeUtils).apply {
setSmallIcon(R.drawable.notification_icon)
setOngoing(true)
setTicker(context.getString(R.string.foreground_service_upload))
notificationBuilder.run {
setContentTitle(context.getString(R.string.uploader_upload_in_progress_ticker))
setProgress(100, 0, false)
setContentText(
String.format(
context.getString(R.string.uploader_upload_in_progress),
@ -73,6 +68,9 @@ class UploadNotificationManager(private val context: Context, private val viewTh
upload.fileName
)
)
setTicker(context.getString(R.string.foreground_service_upload))
setProgress(100, 0, false)
setOngoing(true)
clearActions()
addAction(
@ -81,10 +79,6 @@ class UploadNotificationManager(private val context: Context, private val viewTh
pendingIntent
)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
setChannelId(NotificationUtils.NOTIFICATION_CHANNEL_UPLOAD)
}
setContentIntent(startIntent)
}
@ -192,4 +186,18 @@ class UploadNotificationManager(private val context: Context, private val viewTh
fun dismissWorkerNotifications() {
notificationManager.cancel(ID)
}
fun notifyPaused(intent: PendingIntent) {
notificationBuilder.apply {
setContentTitle(context.getString(R.string.upload_global_pause_title))
setTicker(context.getString(R.string.upload_global_pause_title))
setOngoing(true)
setAutoCancel(false)
setProgress(0, 0, false)
clearActions()
setContentIntent(intent)
}
showNotification()
}
}

View file

@ -387,6 +387,10 @@ public interface AppPreferences {
void setCalendarLastBackup(long timestamp);
boolean isGlobalUploadPaused();
void setGlobalUploadPaused(boolean globalPausedState);
void setPdfZoomTipShownCount(int count);
int getPdfZoomTipShownCount();

View file

@ -107,6 +107,8 @@ public final class AppPreferencesImpl implements AppPreferences {
private static final String PREF__CALENDAR_AUTOMATIC_BACKUP = "calendar_automatic_backup";
private static final String PREF__CALENDAR_LAST_BACKUP = "calendar_last_backup";
private static final String PREF__GLOBAL_PAUSE_STATE = "global_pause_state";
private static final String PREF__PDF_ZOOM_TIP_SHOWN = "pdf_zoom_tip_shown";
private static final String PREF__MEDIA_FOLDER_LAST_PATH = "media_folder_last_path";
@ -741,6 +743,16 @@ public final class AppPreferencesImpl implements AppPreferences {
preferences.edit().putLong(PREF__CALENDAR_LAST_BACKUP, timestamp).apply();
}
@Override
public boolean isGlobalUploadPaused() {
return preferences.getBoolean(PREF__GLOBAL_PAUSE_STATE,false);
}
@Override
public void setGlobalUploadPaused(boolean globalPausedState) {
preferences.edit().putBoolean(PREF__GLOBAL_PAUSE_STATE, globalPausedState).apply();
}
@Override
public void setPdfZoomTipShownCount(int count) {
preferences.edit().putInt(PREF__PDF_ZOOM_TIP_SHOWN, count).apply();

View file

@ -1155,6 +1155,10 @@ public abstract class DrawerActivity extends ToolbarActivity
return true;
}
public AppPreferences getAppPreferences(){
return preferences;
}
@Override
protected void onStart() {
super.onStart();

View file

@ -24,6 +24,7 @@
package com.owncloud.android.ui.activity;
import android.accounts.Account;
import android.annotation.SuppressLint;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@ -49,7 +50,6 @@ import com.owncloud.android.R;
import com.owncloud.android.databinding.UploadListLayoutBinding;
import com.owncloud.android.datamodel.OCFile;
import com.owncloud.android.datamodel.UploadsStorageManager;
import com.owncloud.android.db.OCUpload;
import com.owncloud.android.lib.common.operations.RemoteOperation;
import com.owncloud.android.lib.common.operations.RemoteOperationResult;
import com.owncloud.android.lib.common.utils.Log_OC;
@ -65,11 +65,11 @@ import javax.inject.Inject;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
/**
* Activity listing pending, active, and completed uploads. User can delete
* completed uploads from view. Content of this list of coming from
* {@link UploadsStorageManager}.
* Activity listing pending, active, and completed uploads. User can delete completed uploads from view. Content of this
* list of coming from {@link UploadsStorageManager}.
*/
public class UploadListActivity extends FileActivity {
@ -210,13 +210,17 @@ public class UploadListActivity extends FileActivity {
private void refresh() {
backgroundJobManager.startImmediateFilesSyncJob(false, true);
if(uploadsStorageManager.getFailedUploads().length > 0){
new Thread(() -> FileUploadHelper.Companion.instance().retryFailedUploads(
uploadsStorageManager,
connectivityService,
userAccountManager,
powerManagementService))
.start();
if (uploadsStorageManager.getFailedUploads().length > 0) {
new Thread(() -> {
FileUploadHelper.Companion.instance().retryFailedUploads(
uploadsStorageManager,
connectivityService,
accountManager,
powerManagementService);
this.runOnUiThread(() -> {
uploadListAdapter.loadUploadItemsFromDb();
});
}).start();
DisplayUtils.showSnackMessage(this, R.string.uploader_local_files_uploaded);
}
@ -265,13 +269,47 @@ public class UploadListActivity extends FileActivity {
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.activity_upload_list, menu);
updateGlobalPauseIcon(menu.getItem(0));
return true;
}
@SuppressFBWarnings("RV_RETURN_VALUE_IGNORED_NO_SIDE_EFFECT")
private void updateGlobalPauseIcon(MenuItem pauseMenuItem) {
if (pauseMenuItem.getItemId() != R.id.action_toggle_global_pause) {
return;
}
int iconId;
String title;
if (preferences.isGlobalUploadPaused()) {
iconId = R.drawable.ic_play;
title = getString(R.string.upload_action_global_upload_resume);
} else {
iconId = R.drawable.ic_pause;
title = getString(R.string.upload_action_global_upload_pause);
}
pauseMenuItem.setIcon(iconId);
pauseMenuItem.setTitle(title);
}
@SuppressLint("NotifyDataSetChanged")
private void toggleGlobalPause(MenuItem pauseMenuItem) {
preferences.setGlobalUploadPaused(!preferences.isGlobalUploadPaused());
updateGlobalPauseIcon(pauseMenuItem);
for (User user : accountManager.getAllUsers()) {
if (user != null) {
FileUploadHelper.Companion.instance().cancelAndRestartUploadJob(user);
}
}
uploadListAdapter.notifyDataSetChanged();
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
boolean retval = true;
int itemId = item.getItemId();
if (itemId == android.R.id.home) {
@ -280,17 +318,13 @@ public class UploadListActivity extends FileActivity {
} else {
openDrawer();
}
} else if (itemId == R.id.action_clear_failed_uploads) {
for (OCUpload upload : uploadsStorageManager.getFailedButNotDelayedUploadsForCurrentAccount()){
uploadListAdapter.cancelOldErrorNotification(upload);
}
uploadsStorageManager.clearFailedButNotDelayedUploads();
uploadListAdapter.loadUploadItemsFromDb();
} else if (itemId == R.id.action_toggle_global_pause) {
toggleGlobalPause(item);
} else {
retval = super.onOptionsItemSelected(item);
return super.onOptionsItemSelected(item);
}
return retval;
return true;
}
@Override

View file

@ -29,7 +29,6 @@ import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.text.format.DateUtils;
@ -123,31 +122,57 @@ public class UploadListAdapter extends SectionedRecyclerViewAdapter<SectionedVie
switch (group.type) {
case CURRENT, FINISHED -> headerViewHolder.binding.uploadListAction.setImageResource(R.drawable.ic_close);
case FAILED -> headerViewHolder.binding.uploadListAction.setImageResource(R.drawable.ic_sync);
case FAILED -> headerViewHolder.binding.uploadListAction.setImageResource(R.drawable.ic_dots_vertical);
}
headerViewHolder.binding.uploadListAction.setOnClickListener(v -> {
switch (group.type) {
case CURRENT -> {
// cancel all current uploads
for (OCUpload upload : group.getItems()) {
uploadHelper.cancelFileUpload(upload.getRemotePath(), upload.getAccountName());
}
loadUploadItemsFromDb();
}
case FINISHED -> uploadsStorageManager.clearSuccessfulUploads();
case FAILED -> new Thread(() -> FileUploadHelper.Companion.instance().retryFailedUploads(
uploadsStorageManager,
connectivityService,
accountManager,
powerManagementService)).start();
default -> {
case FINISHED -> {
// clear successfully uploaded section
uploadsStorageManager.clearSuccessfulUploads();
loadUploadItemsFromDb();
}
case FAILED -> {
// show popup with option clear or retry filed uploads
createFailedPopupMenu(headerViewHolder);
}
// do nothing
}
loadUploadItemsFromDb();
});
}
private void createFailedPopupMenu(HeaderViewHolder headerViewHolder) {
PopupMenu failedPopup = new PopupMenu(MainApp.getAppContext(), headerViewHolder.binding.uploadListAction);
failedPopup.inflate(R.menu.upload_list_failed_options);
failedPopup.setOnMenuItemClickListener(i -> {
int itemId = i.getItemId();
if (itemId == R.id.action_upload_list_failed_clear) {
uploadsStorageManager.clearFailedButNotDelayedUploads();
loadUploadItemsFromDb();
} else {
new Thread(() -> {
FileUploadHelper.Companion.instance().retryFailedUploads(
uploadsStorageManager,
connectivityService,
accountManager,
powerManagementService);
parentActivity.runOnUiThread(this::loadUploadItemsFromDb);
}).start();
}
return true;
});
failedPopup.show();
}
@Override
public void onBindFooterViewHolder(SectionedViewHolder holder, int section) {
// not needed
@ -228,7 +253,7 @@ public class UploadListAdapter extends SectionedRecyclerViewAdapter<SectionedVie
// file size
if (item.getFileSize() != 0) {
itemViewHolder.binding.uploadFileSize.setText(String.format("%s, ",
DisplayUtils.bytesToHumanReadable(item.getFileSize())));
DisplayUtils.bytesToHumanReadable(item.getFileSize())));
} else {
itemViewHolder.binding.uploadFileSize.setText("");
}
@ -260,7 +285,6 @@ public class UploadListAdapter extends SectionedRecyclerViewAdapter<SectionedVie
itemViewHolder.binding.uploadRemotePath.setVisibility(View.VISIBLE);
itemViewHolder.binding.uploadFileSize.setVisibility(View.VISIBLE);
itemViewHolder.binding.uploadStatus.setVisibility(View.VISIBLE);
itemViewHolder.binding.uploadStatus.setTypeface(null, Typeface.NORMAL);
itemViewHolder.binding.uploadProgressBar.setVisibility(View.GONE);
// Update information depending of upload details
@ -304,9 +328,8 @@ public class UploadListAdapter extends SectionedRecyclerViewAdapter<SectionedVie
}
// show status if same file conflict or local file deleted
if (item.getUploadStatus() == UploadStatus.UPLOAD_SUCCEEDED && item.getLastResult() != UploadResult.UPLOADED){
if (item.getUploadStatus() == UploadStatus.UPLOAD_SUCCEEDED && item.getLastResult() != UploadResult.UPLOADED) {
itemViewHolder.binding.uploadStatus.setVisibility(View.VISIBLE);
itemViewHolder.binding.uploadStatus.setTypeface(null, Typeface.BOLD);
itemViewHolder.binding.uploadDate.setVisibility(View.GONE);
itemViewHolder.binding.uploadFileSize.setVisibility(View.GONE);
}
@ -373,16 +396,16 @@ public class UploadListAdapter extends SectionedRecyclerViewAdapter<SectionedVie
DisplayUtils.showSnackMessage(
v.getRootView().findViewById(android.R.id.content),
R.string.local_file_not_found_message
);
);
}
});
} else if (item.getUploadStatus() == UploadStatus.UPLOAD_SUCCEEDED){
} else if (item.getUploadStatus() == UploadStatus.UPLOAD_SUCCEEDED) {
itemViewHolder.binding.uploadListItemLayout.setOnClickListener(v -> onUploadedItemClick(item));
}
// click on thumbnail to open locally
if (item.getUploadStatus() != UploadStatus.UPLOAD_SUCCEEDED){
if (item.getUploadStatus() != UploadStatus.UPLOAD_SUCCEEDED) {
itemViewHolder.binding.thumbnail.setOnClickListener(v -> onUploadingItemClick(item));
}
@ -395,17 +418,17 @@ public class UploadListAdapter extends SectionedRecyclerViewAdapter<SectionedVie
fakeFileToCheatThumbnailsCacheManagerInterface.setMimeType(item.getMimeType());
boolean allowedToCreateNewThumbnail = ThumbnailsCacheManager.cancelPotentialThumbnailWork(
fakeFileToCheatThumbnailsCacheManagerInterface, itemViewHolder.binding.thumbnail
);
fakeFileToCheatThumbnailsCacheManagerInterface, itemViewHolder.binding.thumbnail
);
// TODO this code is duplicated; refactor to a common place
if (MimeTypeUtil.isImage(fakeFileToCheatThumbnailsCacheManagerInterface)
&& fakeFileToCheatThumbnailsCacheManagerInterface.getRemoteId() != null &&
item.getUploadStatus() == UploadStatus.UPLOAD_SUCCEEDED) {
&& fakeFileToCheatThumbnailsCacheManagerInterface.getRemoteId() != null &&
item.getUploadStatus() == UploadStatus.UPLOAD_SUCCEEDED) {
// Thumbnail in Cache?
Bitmap thumbnail = ThumbnailsCacheManager.getBitmapFromDiskCache(
String.valueOf(fakeFileToCheatThumbnailsCacheManagerInterface.getRemoteId())
);
String.valueOf(fakeFileToCheatThumbnailsCacheManagerInterface.getRemoteId())
);
if (thumbnail != null && !fakeFileToCheatThumbnailsCacheManagerInterface.isUpdateThumbnailNeeded()) {
itemViewHolder.binding.thumbnail.setImageBitmap(thumbnail);
} else {
@ -413,11 +436,11 @@ public class UploadListAdapter extends SectionedRecyclerViewAdapter<SectionedVie
Optional<User> user = parentActivity.getUser();
if (allowedToCreateNewThumbnail && user.isPresent()) {
final ThumbnailsCacheManager.ThumbnailGenerationTask task =
new ThumbnailsCacheManager.ThumbnailGenerationTask(
itemViewHolder.binding.thumbnail,
parentActivity.getStorageManager(),
user.get()
);
new ThumbnailsCacheManager.ThumbnailGenerationTask(
itemViewHolder.binding.thumbnail,
parentActivity.getStorageManager(),
user.get()
);
if (thumbnail == null) {
if (MimeTypeUtil.isVideo(fakeFileToCheatThumbnailsCacheManagerInterface)) {
thumbnail = ThumbnailsCacheManager.mDefaultVideo;
@ -426,20 +449,20 @@ public class UploadListAdapter extends SectionedRecyclerViewAdapter<SectionedVie
}
}
final ThumbnailsCacheManager.AsyncThumbnailDrawable asyncDrawable =
new ThumbnailsCacheManager.AsyncThumbnailDrawable(
parentActivity.getResources(),
thumbnail,
task
);
new ThumbnailsCacheManager.AsyncThumbnailDrawable(
parentActivity.getResources(),
thumbnail,
task
);
itemViewHolder.binding.thumbnail.setImageDrawable(asyncDrawable);
task.execute(new ThumbnailsCacheManager.ThumbnailGenerationTaskObject(
fakeFileToCheatThumbnailsCacheManagerInterface, null));
fakeFileToCheatThumbnailsCacheManagerInterface, null));
}
}
if ("image/png".equals(item.getMimeType())) {
itemViewHolder.binding.thumbnail.setBackgroundColor(parentActivity.getResources()
.getColor(R.color.bg_default));
.getColor(R.color.bg_default));
}
@ -447,14 +470,14 @@ public class UploadListAdapter extends SectionedRecyclerViewAdapter<SectionedVie
File file = new File(item.getLocalPath());
// Thumbnail in Cache?
Bitmap thumbnail = ThumbnailsCacheManager.getBitmapFromDiskCache(
String.valueOf(file.hashCode()));
String.valueOf(file.hashCode()));
if (thumbnail != null) {
itemViewHolder.binding.thumbnail.setImageBitmap(thumbnail);
} else {
// generate new Thumbnail
if (allowedToCreateNewThumbnail) {
final ThumbnailsCacheManager.ThumbnailGenerationTask task =
new ThumbnailsCacheManager.ThumbnailGenerationTask(itemViewHolder.binding.thumbnail);
new ThumbnailsCacheManager.ThumbnailGenerationTask(itemViewHolder.binding.thumbnail);
if (MimeTypeUtil.isVideo(file)) {
thumbnail = ThumbnailsCacheManager.mDefaultVideo;
@ -474,7 +497,7 @@ public class UploadListAdapter extends SectionedRecyclerViewAdapter<SectionedVie
if ("image/png".equalsIgnoreCase(item.getMimeType())) {
itemViewHolder.binding.thumbnail.setBackgroundColor(parentActivity.getResources()
.getColor(R.color.bg_default));
.getColor(R.color.bg_default));
}
} else {
if (optionalUser.isPresent()) {
@ -605,8 +628,7 @@ public class UploadListAdapter extends SectionedRecyclerViewAdapter<SectionedVie
}
/**
* Gets the status text to show to the user according to the status and last result of the
* the given upload.
* Gets the status text to show to the user according to the status and last result of the the given upload.
*
* @param upload Upload to describe.
* @return Text describing the status of the given upload.
@ -614,30 +636,27 @@ public class UploadListAdapter extends SectionedRecyclerViewAdapter<SectionedVie
private String getStatusText(OCUpload upload) {
String status;
switch (upload.getUploadStatus()) {
case UPLOAD_IN_PROGRESS:
case UPLOAD_IN_PROGRESS -> {
status = parentActivity.getString(R.string.uploads_view_later_waiting_to_upload);
if (uploadHelper.isUploadingNow(upload)) {
// really uploading, bind the progress bar to listen for progress updates
status = parentActivity.getString(R.string.uploader_upload_in_progress_ticker);
}
break;
case UPLOAD_SUCCEEDED:
if (upload.getLastResult() == UploadResult.SAME_FILE_CONFLICT){
if (parentActivity.getAppPreferences().isGlobalUploadPaused()) {
status = parentActivity.getString(R.string.upload_global_pause_title);
}
}
case UPLOAD_SUCCEEDED -> {
if (upload.getLastResult() == UploadResult.SAME_FILE_CONFLICT) {
status = parentActivity.getString(R.string.uploads_view_upload_status_succeeded_same_file);
}else if (upload.getLastResult() == UploadResult.FILE_NOT_FOUND) {
} else if (upload.getLastResult() == UploadResult.FILE_NOT_FOUND) {
status = getUploadFailedStatusText(upload.getLastResult());
} else {
status = parentActivity.getString(R.string.uploads_view_upload_status_succeeded);
}
break;
case UPLOAD_FAILED:
status = getUploadFailedStatusText(upload.getLastResult());
break;
default:
status = "Uncontrolled status: " + upload.getUploadStatus();
}
case UPLOAD_FAILED -> status = getUploadFailedStatusText(upload.getLastResult());
default -> status = "Uncontrolled status: " + upload.getUploadStatus();
}
return status;
}
@ -690,8 +709,8 @@ public class UploadListAdapter extends SectionedRecyclerViewAdapter<SectionedVie
case SSL_RECOVERABLE_PEER_UNVERIFIED:
status =
parentActivity.getString(
R.string.uploads_view_upload_status_failed_ssl_certificate_not_trusted
);
R.string.uploads_view_upload_status_failed_ssl_certificate_not_trusted
);
break;
case UNKNOWN:
status = parentActivity.getString(R.string.uploads_view_upload_status_unknown_fail);
@ -701,7 +720,7 @@ public class UploadListAdapter extends SectionedRecyclerViewAdapter<SectionedVie
break;
case DELAYED_IN_POWER_SAVE_MODE:
status = parentActivity.getString(
R.string.uploads_view_upload_status_waiting_exit_power_save_mode);
R.string.uploads_view_upload_status_waiting_exit_power_save_mode);
break;
case VIRUS_DETECTED:
status = parentActivity.getString(R.string.uploads_view_upload_status_virus_detected);
@ -776,17 +795,17 @@ public class UploadListAdapter extends SectionedRecyclerViewAdapter<SectionedVie
*/
private void onUploadedItemClick(OCUpload upload) {
final OCFile file = parentActivity.getStorageManager().getFileByEncryptedRemotePath(upload.getRemotePath());
if (file == null){
if (file == null) {
DisplayUtils.showSnackMessage(parentActivity, R.string.error_retrieving_file);
Log_OC.i(TAG, "Could not find uploaded file on remote.");
return;
}
if (PreviewImageFragment.canBePreviewed(file)){
if (PreviewImageFragment.canBePreviewed(file)) {
//show image preview and stay in uploads tab
Intent intent = FileDisplayActivity.openFileIntent(parentActivity, parentActivity.getUser().get(), file);
parentActivity.startActivity(intent);
}else{
} else {
Intent intent = new Intent(parentActivity, FileDisplayActivity.class);
intent.setAction(Intent.ACTION_VIEW);
intent.putExtra(FileDisplayActivity.KEY_FILE_PATH, upload.getRemotePath());
@ -882,14 +901,16 @@ public class UploadListAdapter extends SectionedRecyclerViewAdapter<SectionedVie
}
}
public void cancelOldErrorNotification(OCUpload upload){
public void cancelOldErrorNotification(OCUpload upload) {
if (mNotificationManager == null) {
mNotificationManager = (NotificationManager) parentActivity.getSystemService(parentActivity.NOTIFICATION_SERVICE);
}
if (upload == null) return;
mNotificationManager.cancel(NotificationUtils.createUploadNotificationTag(upload.getRemotePath(),upload.getLocalPath()),
if (upload == null) {
return;
}
mNotificationManager.cancel(NotificationUtils.createUploadNotificationTag(upload.getRemotePath(), upload.getLocalPath()),
FileUploadWorker.NOTIFICATION_ERROR_ID);
}

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:fillColor="@color/foreground_highlight"
android:pathData="M560,760v-560h160v560L560,760ZM240,760v-560h160v560L240,760Z"/>
</vector>

View file

@ -0,0 +1,30 @@
<!--
~ Nextcloud Android client application
~
~ @author Alper Ozturk
~ Copyright (C) 2023 Alper Ozturk
~ Copyright (C) 2023 Nextcloud GmbH
~
~ This program is free software: you can redistribute it and/or modify
~ it under the terms of the GNU Affero General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or
~ (at your option) any later version.
~
~ This program is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU Affero General Public License for more details.
~
~ You should have received a copy of the GNU Affero General Public License
~ along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:fillColor="@color/foreground_highlight"
android:pathData="M320,760v-560l440,280 -440,280Z"/>
</vector>

View file

@ -16,13 +16,16 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<menu xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android">
<group
android:id="@+id/upload_list_actions"
android:checkableBehavior="none">
<item
android:id="@+id/action_clear_failed_uploads"
android:title="@string/action_clear_failed_uploads" />
android:id="@+id/action_toggle_global_pause"
android:icon="@android:drawable/ic_media_pause"
android:title="@string/upload_action_global_upload_pause"
app:showAsAction="always" />
</group>
</menu>

View file

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Nextcloud Android client application
@author Jonas Mayer
Copyright (C) 2024 Jonas Mayer
Copyright (C) 2024 Nextcloud GmbH
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/action_upload_list_failed_retry"
android:icon="@drawable/ic_sync"
android:title="@string/upload_action_failed_retry" />
<item
android:id="@+id/action_upload_list_failed_clear"
android:title="@string/upload_action_failed_clear"
android:icon="@drawable/ic_close" />
</menu>

View file

@ -33,6 +33,7 @@
<color name="grey_200">#818181</color>
<color name="nc_grey">#222222</color>
<color name="icon_on_nc_grey">#ffffff</color>
<color name="foreground_highlight">#EAE0E5</color>
<!-- Multiselect backgrounds -->
<color name="action_mode_background">@color/appbar</color>

View file

@ -59,6 +59,7 @@
<color name="secondary_button_text_color">#000000</color>
<color name="nc_grey">#ededed</color>
<color name="icon_on_nc_grey">#000000</color>
<color name="foreground_highlight">#1D1B1E</color>
<color name="process_dialog_background">#ffffff</color>
<color name="indicator_dot_selected">#ffffff</color>

View file

@ -522,8 +522,6 @@
<string name="share_room_clarification">%1$s (conversation)</string>
<string name="share_known_remote_on_clarification">on %1$s</string>
<string name="action_clear_failed_uploads">Clear failed uploads</string>
<string name="action_switch_grid_view">Grid view</string>
<string name="action_switch_list_view">List view</string>
@ -845,9 +843,14 @@
<string name="upload_sync_conflict">Sync conflict, please resolve manually</string>
<string name="upload_cannot_create_file">Cannot create local file</string>
<string name="upload_local_storage_not_copied">File could not be copied to local storage</string>
<string name="upload_global_pause_title">All uploads are paused</string>
<string name="upload_quota_exceeded">Storage quota exceeded</string>
<string name="host_not_available">Server not available</string>
<string name="delete_entries">Delete entries</string>
<string name="upload_action_failed_retry">Retry failed uploads</string>
<string name="upload_action_failed_clear">Clear failed uploads</string>
<string name="upload_action_global_upload_pause">Pause all uploads</string>
<string name="upload_action_global_upload_resume">Resume all uploads</string>
<string name="dismiss_notification_description">Dismiss notification</string>
<string name="action_empty_notifications">Clear all notifications</string>
<string name="timeout_richDocuments">Loading is taking longer than expected</string>