diff --git a/app/src/androidTest/java/com/nextcloud/client/FileDisplayActivityIT.kt b/app/src/androidTest/java/com/nextcloud/client/FileDisplayActivityIT.kt index 00d7165d1380..8dbffad7e74c 100644 --- a/app/src/androidTest/java/com/nextcloud/client/FileDisplayActivityIT.kt +++ b/app/src/androidTest/java/com/nextcloud/client/FileDisplayActivityIT.kt @@ -139,7 +139,7 @@ class FileDisplayActivityIT : AbstractOnServerIT() { val test = storageManager.getFileByPath("/test/") sut.file = test sut.startSyncFolderOperation(test, false) - assertEquals(storageManager.getFileByPath("/test/"), sut.currentDir) + assertEquals(storageManager.getFileByPath("/test/"), sut.getCurrentDir()) EspressoIdlingResource.decrement() // open drawer @@ -150,7 +150,7 @@ class FileDisplayActivityIT : AbstractOnServerIT() { .perform(NavigationViewActions.navigateTo(R.id.nav_all_files)) // then should be in root again - assertEquals(storageManager.getFileByPath("/"), sut.currentDir) + assertEquals(storageManager.getFileByPath("/"), sut.getCurrentDir()) } } } diff --git a/app/src/main/java/com/nextcloud/client/editimage/EditImageActivity.kt b/app/src/main/java/com/nextcloud/client/editimage/EditImageActivity.kt index 9663c6fec9a3..2e41ece8ed16 100644 --- a/app/src/main/java/com/nextcloud/client/editimage/EditImageActivity.kt +++ b/app/src/main/java/com/nextcloud/client/editimage/EditImageActivity.kt @@ -43,7 +43,7 @@ class EditImageActivity : Injectable { private lateinit var binding: ActivityEditImageBinding - private lateinit var file: OCFile + private lateinit var imageFile: OCFile private lateinit var format: Bitmap.CompressFormat companion object { @@ -68,12 +68,12 @@ class EditImageActivity : binding = ActivityEditImageBinding.inflate(layoutInflater) setContentView(binding.root) - file = intent.extras?.getParcelableArgument(EXTRA_FILE, OCFile::class.java) + imageFile = intent.extras?.getParcelableArgument(EXTRA_FILE, OCFile::class.java) ?: throw IllegalArgumentException("Missing file argument") setSupportActionBar(binding.toolbar) supportActionBar?.apply { - title = file.fileName + title = imageFile.fileName setDisplayHomeAsUpEnabled(true) } @@ -91,7 +91,7 @@ class EditImageActivity : return } val resultUri = result.getUriFilePath(this, false) - val newFileName = file.fileName.substring(0, file.fileName.lastIndexOf('.')) + + val newFileName = imageFile.fileName.substring(0, imageFile.fileName.lastIndexOf('.')) + " " + getString(R.string.image_editor_file_edited_suffix) + resultUri?.substring(resultUri.lastIndexOf('.')) @@ -99,7 +99,7 @@ class EditImageActivity : val data = OCUploadLocalPathData( user = storageManager.user, localPaths = arrayOf(it), - remotePaths = arrayOf(file.parentRemotePath + File.separator + newFileName), + remotePaths = arrayOf(imageFile.parentRemotePath + File.separator + newFileName), createRemoteFolder = false, creationType = UploadFileOperation.CREATED_BY_USER, requiresWifi = false, @@ -171,10 +171,10 @@ class EditImageActivity : cropper.setOnSetImageUriCompleteListener(this) cropper.setOnCropImageCompleteListener(this) - cropper.setImageUriAsync(file.storageUri) + cropper.setImageUriAsync(imageFile.storageUri) // determine output file format - format = when (file.mimeType) { + format = when (imageFile.mimeType) { MimeType.PNG -> Bitmap.CompressFormat.PNG MimeType.WEBP -> { diff --git a/app/src/main/java/com/owncloud/android/ui/activity/ContactsPreferenceActivity.java b/app/src/main/java/com/owncloud/android/ui/activity/ContactsPreferenceActivity.java index 15f27b5c2eb6..bb3f1b647ca9 100644 --- a/app/src/main/java/com/owncloud/android/ui/activity/ContactsPreferenceActivity.java +++ b/app/src/main/java/com/owncloud/android/ui/activity/ContactsPreferenceActivity.java @@ -17,6 +17,8 @@ import com.nextcloud.utils.extensions.IntentExtensionsKt; import com.owncloud.android.R; import com.owncloud.android.datamodel.OCFile; +import com.owncloud.android.lib.common.operations.RemoteOperation; +import com.owncloud.android.lib.common.operations.RemoteOperationResult; import com.owncloud.android.ui.fragment.FileFragment; import com.owncloud.android.ui.fragment.contactsbackup.BackupFragment; import com.owncloud.android.ui.fragment.contactsbackup.BackupListFragment; @@ -129,6 +131,11 @@ public void onTransferStateChanged(OCFile file, boolean downloading, boolean upl // not needed } + @Override + public void onRemoteOperationFinish(RemoteOperation operation, RemoteOperationResult result) { + super.onRemoteOperationFinish(operation, result); + } + private final OnBackPressedCallback onBackPressedCallback = new OnBackPressedCallback(true) { @Override public void handleOnBackPressed() { diff --git a/app/src/main/java/com/owncloud/android/ui/activity/FileActivity.java b/app/src/main/java/com/owncloud/android/ui/activity/FileActivity.java deleted file mode 100644 index ac256cd2375c..000000000000 --- a/app/src/main/java/com/owncloud/android/ui/activity/FileActivity.java +++ /dev/null @@ -1,1019 +0,0 @@ -/* - * Nextcloud - Android Client - * - * SPDX-FileCopyrightText: 2021-2026 TSI-mc - * SPDX-FileCopyrightText: 2022 Álvaro Brey - * SPDX-FileCopyrightText: 2017-2023 Tobias Kaminsky - * SPDX-FileCopyrightText: 2019 Chris Narkiewicz - * SPDX-FileCopyrightText: 2018 Andy Scherzinger - * SPDX-FileCopyrightText: 2016 ownCloud Inc. - * SPDX-FileCopyrightText: 2015 María Asensio Valverde - * SPDX-FileCopyrightText: 2013 David A. Velasco - * SPDX-FileCopyrightText: 2011 Bartek Przybylski - * SPDX-License-Identifier: GPL-2.0-only AND (AGPL-3.0-or-later OR GPL-2.0-only) - */ -package com.owncloud.android.ui.activity; - -import android.accounts.Account; -import android.accounts.AuthenticatorException; -import android.app.Activity; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.ServiceConnection; -import android.content.pm.PackageManager; -import android.net.Uri; -import android.os.Bundle; -import android.os.Handler; -import android.os.IBinder; -import android.text.TextUtils; - -import com.google.android.material.snackbar.Snackbar; -import com.nextcloud.client.account.User; -import com.nextcloud.client.account.UserAccountManager; -import com.nextcloud.client.jobs.BackgroundJobManager; -import com.nextcloud.client.jobs.upload.FileUploadHelper; -import com.nextcloud.client.network.ConnectivityService; -import com.nextcloud.client.network.NetworkChangeListener; -import com.nextcloud.client.player.ui.audio.AudioPlayerActivity; -import com.nextcloud.client.utils.IntentUtil; -import com.nextcloud.utils.EditorUtils; -import com.nextcloud.utils.SnackbarUtil; -import com.nextcloud.utils.extensions.ActivityExtensionsKt; -import com.nextcloud.utils.extensions.BundleExtensionsKt; -import com.nextcloud.utils.extensions.FileExtensionsKt; -import com.nextcloud.utils.extensions.IntentExtensionsKt; -import com.nextcloud.utils.mdm.MDMConfig; -import com.owncloud.android.MainApp; -import com.owncloud.android.R; -import com.owncloud.android.authentication.AuthenticatorActivity; -import com.owncloud.android.datamodel.ArbitraryDataProvider; -import com.owncloud.android.datamodel.ArbitraryDataProviderImpl; -import com.owncloud.android.datamodel.OCFile; -import com.owncloud.android.lib.common.OwnCloudAccount; -import com.owncloud.android.lib.common.network.CertificateCombinedException; -import com.owncloud.android.lib.common.operations.OnRemoteOperationListener; -import com.owncloud.android.lib.common.operations.RemoteOperation; -import com.owncloud.android.lib.common.operations.RemoteOperationResult; -import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode; -import com.owncloud.android.lib.common.utils.Log_OC; -import com.owncloud.android.lib.resources.files.model.ServerFileInterface; -import com.owncloud.android.lib.resources.shares.OCShare; -import com.owncloud.android.lib.resources.shares.ShareType; -import com.owncloud.android.operations.CreateShareViaLinkOperation; -import com.owncloud.android.operations.CreateShareWithShareeOperation; -import com.owncloud.android.operations.GetSharesForFileOperation; -import com.owncloud.android.operations.SetFilesDownloadLimitOperation; -import com.owncloud.android.operations.SynchronizeFileOperation; -import com.owncloud.android.operations.SynchronizeFolderOperation; -import com.owncloud.android.operations.UnshareOperation; -import com.owncloud.android.operations.UpdateNoteForShareOperation; -import com.owncloud.android.operations.UpdateShareInfoOperation; -import com.owncloud.android.operations.UpdateSharePermissionsOperation; -import com.owncloud.android.operations.UpdateShareViaLinkOperation; -import com.owncloud.android.providers.UsersAndGroupsSearchConfig; -import com.owncloud.android.providers.UsersAndGroupsSearchProvider; -import com.owncloud.android.services.OperationsService; -import com.owncloud.android.services.OperationsService.OperationsServiceBinder; -import com.owncloud.android.ui.activity.drawer.DrawerActivity; -import com.owncloud.android.ui.asynctasks.CheckRemoteWipeTask; -import com.owncloud.android.ui.asynctasks.LoadingVersionNumberTask; -import com.owncloud.android.ui.dialog.ConfirmationDialogFragment; -import com.owncloud.android.ui.dialog.LoadingDialog; -import com.owncloud.android.ui.dialog.ShareLinkToDialog; -import com.owncloud.android.ui.dialog.SslUntrustedCertDialog; -import com.owncloud.android.ui.events.DialogEvent; -import com.owncloud.android.ui.events.DialogEventType; -import com.owncloud.android.ui.fragment.FileDetailFragment; -import com.owncloud.android.ui.fragment.FileDetailSharingFragment; -import com.owncloud.android.ui.fragment.OCFileListFragment; -import com.owncloud.android.ui.fragment.albums.AlbumItemsFragment; -import com.owncloud.android.ui.fragment.albums.AlbumsFragment; -import com.owncloud.android.ui.fragment.filesRepository.FilesRepository; -import com.owncloud.android.ui.fragment.filesRepository.RemoteFilesRepository; -import com.owncloud.android.ui.helpers.CredentialsUpdateHelper; -import com.owncloud.android.ui.helpers.FileOperationsHelper; -import com.owncloud.android.ui.navigation.model.ActionBarStyle; -import com.owncloud.android.ui.preview.media.PreviewMediaActivity; -import com.owncloud.android.utils.ClipboardUtil; -import com.owncloud.android.utils.ErrorMessageAdapter; -import com.owncloud.android.utils.FilesSyncHelper; -import com.owncloud.android.utils.theme.ViewThemeUtils; - -import org.greenrobot.eventbus.Subscribe; -import org.greenrobot.eventbus.ThreadMode; - -import java.lang.ref.WeakReference; -import java.util.ArrayList; - -import javax.inject.Inject; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.StringRes; -import androidx.appcompat.app.ActionBar; -import androidx.fragment.app.DialogFragment; -import androidx.fragment.app.Fragment; -import androidx.fragment.app.FragmentManager; -import androidx.fragment.app.FragmentTransaction; - -import static com.owncloud.android.ui.activity.FileDisplayActivity.TAG_PUBLIC_LINK; - -/** - * Activity with common behaviour for activities handling {@link OCFile}s in ownCloud {@link Account}s . - */ -public abstract class FileActivity extends DrawerActivity - implements OnRemoteOperationListener, ComponentsGetter, SslUntrustedCertDialog.OnSslUntrustedCertListener, - LoadingVersionNumberTask.VersionDevInterface, FileDetailSharingFragment.OnEditShareListener, NetworkChangeListener { - - public static final String EXTRA_FILE = "com.owncloud.android.ui.activity.FILE"; - public static final String EXTRA_FILE_REMOTE_PATH = "com.owncloud.android.ui.activity.FILE_REMOTE_PATH"; - public static final String EXTRA_LIVE_PHOTO_FILE = "com.owncloud.android.ui.activity.LIVE.PHOTO.FILE"; - public static final String EXTRA_USER = "com.owncloud.android.ui.activity.USER"; - public static final String EXTRA_FROM_NOTIFICATION = "com.owncloud.android.ui.activity.FROM_NOTIFICATION"; - public static final String APP_OPENED_COUNT = "APP_OPENED_COUNT"; - public static final String EXTRA_SEARCH = "com.owncloud.android.ui.activity.SEARCH"; - public static final String EXTRA_SEARCH_QUERY = "com.owncloud.android.ui.activity.SEARCH_QUERY"; - - public static final String TAG = FileActivity.class.getSimpleName(); - - private static final String DIALOG_WAIT_TAG = "DIALOG_WAIT"; - - private static final String KEY_WAITING_FOR_OP_ID = "WAITING_FOR_OP_ID"; - private static final String KEY_ACTION_BAR_TITLE = "ACTION_BAR_TITLE"; - - public static final int REQUEST_CODE__UPDATE_CREDENTIALS = 0; - public static final int REQUEST_CODE__LAST_SHARED = REQUEST_CODE__UPDATE_CREDENTIALS; - - /* Dialog tags */ - private static final String DIALOG_UNTRUSTED_CERT = "DIALOG_UNTRUSTED_CERT"; - private static final String DIALOG_CERT_NOT_SAVED = "DIALOG_CERT_NOT_SAVED"; - - /** Main {@link OCFile} handled by the activity.*/ - private OCFile mFile; - - /** Flag to signal if the activity is launched by a notification */ - private boolean mFromNotification; - - /** Messages handler associated to the main thread and the life cycle of the activity */ - private Handler mHandler; - - private FileOperationsHelper mFileOperationsHelper; - - private ServiceConnection mOperationsServiceConnection; - - private OperationsServiceBinder mOperationsServiceBinder; - - private boolean mResumed; - - protected FileUploadHelper fileUploadHelper = FileUploadHelper.Companion.instance(); - protected boolean isFileDisplayActivityResumed = false; - - @Inject - public UserAccountManager accountManager; - - @Inject public ConnectivityService connectivityService; - - @Inject - protected BackgroundJobManager backgroundJobManager; - - @Inject - EditorUtils editorUtils; - - @Inject - UsersAndGroupsSearchConfig usersAndGroupsSearchConfig; - - @Inject - ArbitraryDataProvider arbitraryDataProvider; - - private FilesRepository filesRepository; - - @Override - public void showFiles(boolean onDeviceOnly, boolean personalFiles) { - // must be specialized in subclasses - MainApp.showOnlyFilesOnDevice(onDeviceOnly); - MainApp.showOnlyPersonalFiles(personalFiles); - if (onDeviceOnly) { - showActionBar(ActionBarStyle.Plain); - } else { - showActionBar(ActionBarStyle.Search); - } - } - - /** - * Loads the ownCloud {@link Account} and main {@link OCFile} to be handled by the instance of - * the {@link FileActivity}. - * - * Grants that a valid ownCloud {@link Account} is associated to the instance, or that the user - * is requested to create a new one. - */ - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - usersAndGroupsSearchConfig.reset(); - mHandler = new Handler(); - mFileOperationsHelper = new FileOperationsHelper(this, getUserAccountManager(), connectivityService, editorUtils); - User user; - - if (savedInstanceState != null) { - mFile = BundleExtensionsKt.getParcelableArgument(savedInstanceState, FileActivity.EXTRA_FILE, OCFile.class); - mFromNotification = savedInstanceState.getBoolean(FileActivity.EXTRA_FROM_NOTIFICATION); - mFileOperationsHelper.setOpIdWaitingFor( - savedInstanceState.getLong(KEY_WAITING_FOR_OP_ID, Long.MAX_VALUE) - ); - final ActionBar actionBar = getSupportActionBar(); - if (actionBar != null && !(this instanceof PreviewMediaActivity)) { - viewThemeUtils.files.themeActionBar(this, actionBar, savedInstanceState.getString(KEY_ACTION_BAR_TITLE)); - } - } else { - user = IntentExtensionsKt.getParcelableArgument(getIntent(), FileActivity.EXTRA_USER, User.class); - mFile = IntentExtensionsKt.getParcelableArgument(getIntent(), FileActivity.EXTRA_FILE, OCFile.class); - mFromNotification = getIntent().getBooleanExtra(FileActivity.EXTRA_FROM_NOTIFICATION, - false); - - if (user != null) { - setUser(user); - } - } - - mOperationsServiceConnection = new OperationsServiceConnection(); - bindService(new Intent(this, OperationsService.class), mOperationsServiceConnection, - Context.BIND_AUTO_CREATE); - filesRepository = new RemoteFilesRepository(getClientRepository(), this); - } - - @Override - public void networkAndServerConnectionListener(boolean isNetworkAndServerAvailable) { - if (isNetworkAndServerAvailable) { - hideInfoBox(); - - // No need to refresh the file list again since file display activity doing it. - if (!isFileDisplayActivityResumed) { - refreshList(); - } - } else { - if (this instanceof AudioPlayerActivity) { - hideInfoBox(); - } else { - showInfoBox(R.string.offline_mode); - } - } - } - - @Override - protected void onStart() { - super.onStart(); - connectivityService.addListener(this); - fetchExternalLinks(false); - } - - @Override - protected void onStop() { - super.onStop(); - connectivityService.removeListener(this); - } - - @Override - protected void onResume() { - super.onResume(); - mResumed = true; - if (mOperationsServiceBinder != null) { - doOnResumeAndBound(); - } - } - - @Override - protected void onPause() { - if (mOperationsServiceBinder != null) { - mOperationsServiceBinder.removeOperationListener(this); - } - mResumed = false; - super.onPause(); - } - - @Override - protected void onDestroy() { - if (mOperationsServiceConnection != null) { - unbindService(mOperationsServiceConnection); - mOperationsServiceBinder = null; - } - - super.onDestroy(); - } - - @Override - protected void onSaveInstanceState(@NonNull Bundle outState) { - super.onSaveInstanceState(outState); - FileExtensionsKt.logFileSize(mFile, TAG); - outState.putParcelable(FileActivity.EXTRA_FILE, mFile); - outState.putBoolean(FileActivity.EXTRA_FROM_NOTIFICATION, mFromNotification); - outState.putLong(KEY_WAITING_FOR_OP_ID, mFileOperationsHelper.getOpIdWaitingFor()); - - final var actionBar = getSupportActionBar(); - - if(actionBar != null) { - final var actionBarTitle = actionBar.getTitle(); - if (actionBarTitle != null) { - outState.putString(KEY_ACTION_BAR_TITLE, actionBarTitle.toString()); - } - } - } - - /** - * Getter for the main {@link OCFile} handled by the activity. - * - * @return Main {@link OCFile} handled by the activity. - */ - @Nullable - public OCFile getFile() { - return mFile; - } - - - /** - * Setter for the main {@link OCFile} handled by the activity. - * - * @param file Main {@link OCFile} to be handled by the activity. - */ - public void setFile(OCFile file) { - mFile = file; - } - - /** - * @return Value of mFromNotification: True if the Activity is launched by a notification - */ - public boolean fromNotification() { - return mFromNotification; - } - - public OperationsServiceBinder getOperationsServiceBinder() { - return mOperationsServiceBinder; - } - - protected ServiceConnection newTransferenceServiceConnection() { - return null; - } - - public OnRemoteOperationListener getRemoteOperationListener() { - return this; - } - - public Handler getHandler() { - return mHandler; - } - - public FileOperationsHelper getFileOperationsHelper() { - return mFileOperationsHelper; - } - - /** - * - * @param operation Operation performed. - * @param result Result of the removal. - */ - @Override - public void onRemoteOperationFinish(RemoteOperation operation, RemoteOperationResult result) { - Log_OC.d(TAG, "Received result of operation in FileActivity - common behaviour for all the " - + "FileActivities "); - - mFileOperationsHelper.setOpIdWaitingFor(Long.MAX_VALUE); - - dismissLoadingDialog(); - - if (!result.isSuccess() && ( - result.getCode() == ResultCode.UNAUTHORIZED || - (result.isException() && result.getException() instanceof AuthenticatorException) - )) { - - requestCredentialsUpdate(); - - if (result.getCode() == ResultCode.UNAUTHORIZED) { - SnackbarUtil.show( - this, ErrorMessageAdapter.getErrorCauseMessage(result, operation, getResources()) - ); - } - - } else if (!result.isSuccess() && ResultCode.SSL_RECOVERABLE_PEER_UNVERIFIED == result.getCode()) { - - showUntrustedCertDialog(result); - - } else if (operation == null || operation instanceof SynchronizeFolderOperation) { - if (result.isSuccess()) { - updateFileFromDB(); - - } else if (result.getCode() != ResultCode.CANCELLED) { - SnackbarUtil.show( - this, ErrorMessageAdapter.getErrorCauseMessage(result, operation, getResources()) - ); - } - - } else if (operation instanceof SynchronizeFileOperation) { - onSynchronizeFileOperationFinish((SynchronizeFileOperation) operation, result); - - } else if (operation instanceof GetSharesForFileOperation) { - if (result.isSuccess() || result.getCode() == ResultCode.SHARE_NOT_FOUND) { - updateFileFromDB(); - - } else { - SnackbarUtil.show(this, - ErrorMessageAdapter.getErrorCauseMessage(result, - operation, - getResources())); - } - } - - if (operation instanceof CreateShareViaLinkOperation) { - onCreateShareViaLinkOperationFinish((CreateShareViaLinkOperation) operation, result); - } else if (operation instanceof CreateShareWithShareeOperation) { - onUpdateShareInformation(result, R.string.sharee_add_failed); - } else if (operation instanceof UpdateShareViaLinkOperation || operation instanceof UpdateShareInfoOperation) { - onUpdateShareInformation(result, R.string.updating_share_failed); - } else if (operation instanceof SetFilesDownloadLimitOperation) { - onUpdateShareInformation(result, R.string.set_download_limit_failed); - } else if (operation instanceof UpdateSharePermissionsOperation) { - onUpdateShareInformation(result, R.string.updating_share_failed); - } else if (operation instanceof UnshareOperation) { - onUpdateShareInformation(result, R.string.unsharing_failed); - } else if (operation instanceof UpdateNoteForShareOperation) { - onUpdateNoteForShareOperationFinish(result); - } - } - - /** - * Invalidates the credentials stored for the current OC account and requests new credentials to the user, - * navigating to {@link AuthenticatorActivity} - * - * Equivalent to call requestCredentialsUpdate(context, null); - * - */ - protected void requestCredentialsUpdate() { - requestCredentialsUpdate(null); - } - - /** - * Invalidates the credentials stored for the given OC account and requests new credentials to the user, - * navigating to {@link AuthenticatorActivity} - * - * @param account Stored OC account to request credentials update for. If null, current account will - * be used. - */ - protected void requestCredentialsUpdate(Account account) { - if (account == null) { - account = getAccount(); - } - - new CheckRemoteWipeTask(backgroundJobManager, account, new WeakReference<>(this)).execute(); - } - - public void performCredentialsUpdate(Account account) { - final var credentialsUpdateHelper = new CredentialsUpdateHelper(this); - try { - credentialsUpdateHelper.invalidateCredentials(account); - startActivityForResult(credentialsUpdateHelper.createUpdateCredentialsIntent(account), - REQUEST_CODE__UPDATE_CREDENTIALS); - } catch (com.owncloud.android.lib.common.accounts.AccountUtils.AccountNotFoundException e) { - SnackbarUtil.show(this, R.string.auth_account_does_not_exist); - } - } - - /** - * Show untrusted cert dialog - */ - public void showUntrustedCertDialog(RemoteOperationResult result) { - // Show a dialog with the certificate info - FragmentManager fm = getSupportFragmentManager(); - SslUntrustedCertDialog dialog = (SslUntrustedCertDialog) fm.findFragmentByTag(DIALOG_UNTRUSTED_CERT); - if(dialog == null) { - dialog = SslUntrustedCertDialog.newInstanceForFullSslError( - (CertificateCombinedException) result.getException()); - FragmentTransaction ft = fm.beginTransaction(); - dialog.show(ft, DIALOG_UNTRUSTED_CERT); - } - } - - private void onSynchronizeFileOperationFinish(SynchronizeFileOperation operation, - RemoteOperationResult result) { - OCFile syncedFile = operation.getLocalFile(); - if (!result.isSuccess()) { - if (result.getCode() == ResultCode.SYNC_CONFLICT) { - Intent intent = ConflictsResolveActivity.createIntent(syncedFile, - getUser().orElseThrow(RuntimeException::new), - -1, - null, - this); - startActivity(intent); - } - - } else { - if (!operation.getTransferWasRequested()) { - SnackbarUtil.show(this, ErrorMessageAdapter.getErrorCauseMessage(result, - operation, getResources())); - } - supportInvalidateOptionsMenu(); - } - } - - protected void updateFileFromDB(){ - OCFile file = getFile(); - if (file != null) { - file = getStorageManager().getFileByPath(file.getRemotePath()); - setFile(file); - } - } - - /** - * Show loading dialog - */ - public void showLoadingDialog(String message) { - runOnUiThread(() -> { - if (!ActivityExtensionsKt.isActive(this)) { - Log_OC.w(TAG, "cannot show loading dialog, activity is finishing"); - return; - } - - FragmentManager fragmentManager = getSupportFragmentManager(); - fragmentManager.executePendingTransactions(); - Fragment existingDialog = fragmentManager.findFragmentByTag(DIALOG_WAIT_TAG); - - if (existingDialog instanceof LoadingDialog loadingDialog) { - Log_OC.d(TAG, "dismiss previous loading dialog"); - - if (!fragmentManager.isStateSaved()) { - loadingDialog.dismiss(); - } else { - loadingDialog.dismissAllowingStateLoss(); - } - } - - // Show new dialog - if (!fragmentManager.isStateSaved()) { - Log_OC.d(TAG, "show loading dialog"); - LoadingDialog loadingDialogFragment = LoadingDialog.newInstance(message); - loadingDialogFragment.show(fragmentManager, DIALOG_WAIT_TAG); - } - }); - } - - /** - * Dismiss loading dialog - */ - public void dismissLoadingDialog() { - runOnUiThread(() -> { - if (!ActivityExtensionsKt.isActive(this)) { - Log_OC.w(TAG, "cannot dismiss loading dialog, activity is finishing"); - return; - } - - FragmentManager fragmentManager = getSupportFragmentManager(); - fragmentManager.executePendingTransactions(); - Fragment fragment = fragmentManager.findFragmentByTag(DIALOG_WAIT_TAG); - - if (fragment instanceof LoadingDialog loadingDialogFragment) { - Log_OC.d(TAG, "dismiss loading dialog"); - - // Avoid dismissing after state is saved - if (!fragmentManager.isStateSaved()) { - loadingDialogFragment.dismiss(); - } else { - // Dismiss allowing state loss if needed - loadingDialogFragment.dismissAllowingStateLoss(); - } - } - }); - } - - private void doOnResumeAndBound() { - mOperationsServiceBinder.addOperationListener(this, mHandler); - long waitingForOpId = mFileOperationsHelper.getOpIdWaitingFor(); - if (waitingForOpId <= Integer.MAX_VALUE) { - boolean wait = mOperationsServiceBinder.dispatchResultIfFinished((int)waitingForOpId, - this); - if (!wait ) { - dismissLoadingDialog(); - } - } - } - - /** - * Implements callback methods for service binding. Passed as a parameter to { - */ - private class OperationsServiceConnection implements ServiceConnection { - - @Override - public void onServiceConnected(ComponentName component, IBinder service) { - if (component.equals(new ComponentName(FileActivity.this, OperationsService.class))) { - Log_OC.d(TAG, "Operations service connected"); - mOperationsServiceBinder = (OperationsServiceBinder) service; - /*if (!mOperationsServiceBinder.isPerformingBlockingOperation()) { - dismissLoadingDialog(); - }*/ - if (mResumed) { - doOnResumeAndBound(); - } - - } else { - return; - } - } - - @Override - public void onServiceDisconnected(ComponentName component) { - if (component.equals(new ComponentName(FileActivity.this, OperationsService.class))) { - Log_OC.d(TAG, "Operations service disconnected"); - mOperationsServiceBinder = null; - // TODO whatever could be waiting for the service is unbound - } - } - } - - @Override - public FileUploadHelper getFileUploaderHelper() { - return fileUploadHelper; - } - - @Nullable - public OCFile getCurrentDir() { - OCFile file = getFile(); - if (file != null) { - if (file.isFolder()) { - return file; - } else if (getStorageManager() != null) { - String parentPath = file.getParentRemotePath(); - return getStorageManager().getFileByPath(parentPath); - } - } - return null; - } - - /* OnSslUntrustedCertListener methods */ - - @Override - public void onSavedCertificate() { - // Nothing to do in this context - } - - @Override - public void onFailedSavingCertificate() { - ConfirmationDialogFragment dialog = ConfirmationDialogFragment.newInstance( - R.string.ssl_validator_not_saved, new String[]{}, 0, R.string.common_ok, -1, -1 - ); - dialog.show(getSupportFragmentManager(), DIALOG_CERT_NOT_SAVED); - } - - public void checkForNewDevVersionNecessary(Context context) { - if (getResources().getBoolean(R.bool.dev_version_direct_download_enabled)) { - ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProviderImpl(this); - int count = arbitraryDataProvider.getIntegerValue(FilesSyncHelper.GLOBAL, APP_OPENED_COUNT); - - if (count > 10 || count == -1) { - checkForNewDevVersion(this, context); - } - } - } - - @Override - public void returnVersion(Integer latestVersion) { - showDevSnackbar(this, latestVersion, false, true); - } - - public static void checkForNewDevVersion(LoadingVersionNumberTask.VersionDevInterface callback, Context context) { - String url = context.getString(R.string.dev_latest); - LoadingVersionNumberTask loadTask = new LoadingVersionNumberTask(callback); - loadTask.execute(url); - } - - public static void showDevSnackbar(Activity activity, - Integer latestVersion, - boolean openDirectly, - boolean inBackground) { - int currentVersion = -1; - try { - currentVersion = activity.getPackageManager().getPackageInfo(activity.getPackageName(), 0).versionCode; - } catch (PackageManager.NameNotFoundException e) { - Log_OC.e(TAG, "Package not found", e); - } - - if (latestVersion == -1 || currentVersion == -1) { - SnackbarUtil.show(activity, R.string.dev_version_no_information_available); - } - if (latestVersion > currentVersion) { - String devApkLink = activity.getString(R.string.dev_link) + latestVersion + ".apk"; - if (openDirectly) { - IntentUtil.startLinkIntent(activity, devApkLink); - } else { - Snackbar.make(activity.findViewById(android.R.id.content), R.string.dev_version_new_version_available, - Snackbar.LENGTH_LONG) - .setAction(activity.getString(R.string.version_dev_download), v -> IntentUtil.startLinkIntent(activity, devApkLink)).show(); - } - } else { - if (!inBackground) { - SnackbarUtil.show(activity, R.string.dev_version_no_new_version_available); - } - } - } - - public static void copyAndShareFileLink(FileActivity activity, - OCFile file, - String link, - final ViewThemeUtils viewThemeUtils) { - if (MDMConfig.INSTANCE.shareViaLink(activity) && MDMConfig.INSTANCE.clipBoardSupport(activity)) { - ClipboardUtil.copyToClipboard(activity, link, false); - Snackbar snackbar = Snackbar.make(activity.findViewById(android.R.id.content), R.string.clipboard_text_copied, - Snackbar.LENGTH_LONG) - .setAction(R.string.share, v -> showShareLinkDialog(activity, file, link)); - viewThemeUtils.material.themeSnackbar(snackbar); - snackbar.show(); - } - } - - public static void showShareLinkDialog(FileActivity activity, ServerFileInterface file, String link) { - // Create dialog to allow the user choose an app to send the link - Intent intentToShareLink = new Intent(Intent.ACTION_SEND); - - intentToShareLink.putExtra(Intent.EXTRA_TEXT, link); - intentToShareLink.setType("text/plain"); - - String username; - try { - OwnCloudAccount oca = new OwnCloudAccount(activity.getAccount(), activity); - if (oca.getDisplayName() != null && !oca.getDisplayName().isEmpty()) { - username = oca.getDisplayName(); - } else { - username = com.owncloud.android.lib.common.accounts.AccountUtils - .getUsernameForAccount(activity.getAccount()); - } - } catch (Exception e) { - username = com.owncloud.android.lib.common.accounts.AccountUtils - .getUsernameForAccount(activity.getAccount()); - } - - if (username != null) { - intentToShareLink.putExtra(Intent.EXTRA_SUBJECT, - activity.getString(R.string.subject_user_shared_with_you, - username, - file.getFileName())); - } else { - intentToShareLink.putExtra(Intent.EXTRA_SUBJECT, - activity.getString(R.string.subject_shared_with_you, - file.getFileName())); - } - - String[] packagesToExclude = new String[]{activity.getPackageName()}; - DialogFragment chooserDialog = ShareLinkToDialog.newInstance(intentToShareLink, packagesToExclude); - chooserDialog.show(activity.getSupportFragmentManager(), FileDisplayActivity.FTAG_CHOOSER_DIALOG); - } - - private void onUpdateNoteForShareOperationFinish(RemoteOperationResult result) { - FileDetailSharingFragment sharingFragment = getShareFileFragment(); - - if (result.isSuccess()) { - if (sharingFragment != null) { - sharingFragment.onUpdateShareInformation(result); - } - } else { - SnackbarUtil.show(this, R.string.note_could_not_sent); - } - } - - private void onUpdateShareInformation(RemoteOperationResult result, @StringRes int defaultError) { - FileDetailSharingFragment sharingFragment = getShareFileFragment(); - - if (result.isSuccess()) { - updateFileFromDB(); - if (sharingFragment != null) { - sharingFragment.onUpdateShareInformation(result); - } - return; - } - - final String message = TextUtils.isEmpty(result.getMessage()) ? getString(defaultError) : result.getMessage(); - - if (sharingFragment == null || sharingFragment.getView() == null) { - SnackbarUtil.show(this, message); - return; - } - - Snackbar snackbar = Snackbar.make(sharingFragment.getView(), message, Snackbar.LENGTH_LONG); - viewThemeUtils.material.themeSnackbar(snackbar); - snackbar.show(); - } - - public void refreshList() { - if (isAlbumsFragment()) { - getFragment(AlbumsFragment.Companion.getTAG(), AlbumsFragment.class) - .ifPresent(AlbumsFragment::refreshAlbums); - return; - } - - if (isAlbumItemsFragment()) { - getFragment(AlbumItemsFragment.Companion.getTAG(), AlbumItemsFragment.class) - .ifPresent(AlbumItemsFragment::refreshData); - return; - } - - final var fragment = - getSupportFragmentManager().findFragmentByTag(FileDisplayActivity.TAG_LIST_OF_FILES); - - if (fragment instanceof OCFileListFragment listFragment) { - listFragment.onRefresh(); - } else if (fragment instanceof FileDetailFragment detailFragment) { - detailFragment.goBackToOCFileListFragment(); - } - } - - private void onCreateShareViaLinkOperationFinish(CreateShareViaLinkOperation operation, - RemoteOperationResult result) { - FileDetailSharingFragment sharingFragment = getShareFileFragment(); - final Fragment fileListFragment = getSupportFragmentManager().findFragmentByTag(FileDisplayActivity.TAG_LIST_OF_FILES); - - if (result.isSuccess()) { - updateFileFromDB(); - - // if share to user and share via link multiple ocshares are returned, - // therefore filtering for public_link - String link = ""; - OCFile file = null; - for (Object object : result.getData()) { - if (object instanceof OCShare shareLink) { - ShareType shareType = shareLink.getShareType(); - - if (shareType != null && TAG_PUBLIC_LINK.equalsIgnoreCase(shareType.name())) { - link = shareLink.getShareLink(); - file = getStorageManager().getFileByEncryptedRemotePath(shareLink.getPath()); - break; - } - } - } - - copyAndShareFileLink(this, file, link, viewThemeUtils); - - if (sharingFragment != null) { - sharingFragment.onUpdateShareInformation(result, file); - } - - if (fileListFragment instanceof OCFileListFragment ocFileListFragment && file != null) { - if (ocFileListFragment.getAdapterFiles().contains(file)) { - ocFileListFragment.updateOCFile(file); - } else { - SnackbarUtil.show(this, R.string.file_activity_shared_file_cannot_be_updated); - } - } - } else { - // Detect Failure (403) --> maybe needs password - String password = operation.getPassword(); - final var optionalCapabilities = getCapabilities(); - - if (result.getCode() == RemoteOperationResult.ResultCode.SHARE_FORBIDDEN && TextUtils.isEmpty(password)) { - if (optionalCapabilities.isPresent()) { - final var capabilities = optionalCapabilities.get(); - if (capabilities.getFilesSharingPublicEnabled().isUnknown()) { - // Was tried without password, but not sure that it's optional. - // Try with password before giving up; see also ShareFileFragment#OnShareViaLinkListener - if (sharingFragment != null && sharingFragment.isAdded()) { - // only if added to the view hierarchy - sharingFragment.requestPasswordForShareViaLink(true, capabilities.getFilesSharingPublicAskForOptionalPassword().isTrue()); - } - } - } - } else { - if (sharingFragment != null) { - sharingFragment.refreshSharesFromDB(); - } - Snackbar snackbar = Snackbar.make(findViewById(android.R.id.content), - ErrorMessageAdapter.getErrorCauseMessage(result, - operation, - getResources()), - Snackbar.LENGTH_LONG); - viewThemeUtils.material.themeSnackbar(snackbar); - snackbar.show(); - } - } - } - - /** - * Shortcut to get access to the {@link FileDetailSharingFragment} instance, if any - * - * @return A {@link FileDetailSharingFragment} instance, or null - */ - private @Nullable - @Deprecated - FileDetailSharingFragment getShareFileFragment() { - Fragment fragment = getSupportFragmentManager().findFragmentByTag(ShareActivity.TAG_SHARE_FRAGMENT); - - if (fragment == null) { - fragment = getSupportFragmentManager().findFragmentByTag(FileDisplayActivity.TAG_LIST_OF_FILES); - } - - if (fragment instanceof FileDetailSharingFragment) { - return (FileDetailSharingFragment) fragment; - } else if (fragment instanceof FileDetailFragment fileDetailFragment) { - return fileDetailFragment.getFileDetailSharingFragment(); - } else { - return null; - } - } - - @Override - protected void onNewIntent(Intent intent) { - super.onNewIntent(intent); - - if (UsersAndGroupsSearchProvider.ACTION_SHARE_WITH.equals(intent.getAction())) { - Uri data = intent.getData(); - String dataString = intent.getDataString(); - String shareWith = dataString.substring(dataString.lastIndexOf('/') + 1); - - ArrayList existingSharees = new ArrayList<>(); - for (OCShare share : getStorageManager().getSharesWithForAFile(getFileFromDetailFragment().getRemotePath(), - getAccount().name)) { - existingSharees.add(share.getShareType() + "_" + share.getShareWith()); - } - - String dataAuthority = data.getAuthority(); - ShareType shareType = UsersAndGroupsSearchProvider.getShareType(dataAuthority); - - if (!existingSharees.contains(shareType + "_" + shareWith)) { - doShareWith(shareWith, shareType); - } else { - SnackbarUtil.show(this, getString(R.string.sharee_already_added_to_file)); - } - } - } - - /** - * returns the file that is selected for sharing, getFile() only returns the containing folder - */ - private OCFile getFileFromDetailFragment() { - FileDetailFragment fragment = getFileDetailFragment(); - if (fragment != null) { - return fragment.getFile(); - } - return getFile(); - } - - /** - * open the new sharing process fragment to create the share - * - * @param shareeName - * @param shareType - */ - protected void doShareWith(String shareeName, ShareType shareType) { - FileDetailFragment fragment = getFileDetailFragment(); - if (fragment != null) { - fragment.initiateSharingProcess(shareeName, - shareType, - usersAndGroupsSearchConfig.getSearchOnlyUsers()); - } - } - - /** - * open the new sharing process to modify the created share - * @param share - * @param screenTypePermission - * @param isReshareShown - */ - @Override - public void editExistingShare(OCShare share, int screenTypePermission, boolean isReshareShown) { - FileDetailFragment fragment = getFileDetailFragment(); - if (fragment != null) { - fragment.editExistingShare(share, screenTypePermission, isReshareShown); - } - } - - /** - * callback triggered on closing/finishing the sharing process - */ - @Override - public void onShareProcessClosed() { - FileDetailFragment fragment = getFileDetailFragment(); - if (fragment != null) { - fragment.showHideFragmentView(false); - } - } - - private FileDetailFragment getFileDetailFragment() { - Fragment fragment = getSupportFragmentManager().findFragmentByTag(FileDisplayActivity.TAG_LIST_OF_FILES); - if (fragment instanceof FileDetailFragment) { - return (FileDetailFragment) fragment; - } - return null; - } - - public FilesRepository getFilesRepository() { - return filesRepository; - } - - public void showSyncLoadingDialog(boolean isFolder) { - if (isFolder) { - return; - } - - showLoadingDialog(getApplicationContext().getString(R.string.wait_a_moment)); - } - - @Subscribe(threadMode = ThreadMode.MAIN) - public void handleSyncDialogEvent(DialogEvent event) { - if (event.getType() == DialogEventType.SYNC) { - dismissLoadingDialog(); - } - } -} diff --git a/app/src/main/java/com/owncloud/android/ui/activity/FileActivity.kt b/app/src/main/java/com/owncloud/android/ui/activity/FileActivity.kt new file mode 100644 index 000000000000..e5a24b5cd41a --- /dev/null +++ b/app/src/main/java/com/owncloud/android/ui/activity/FileActivity.kt @@ -0,0 +1,397 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2026 Alper Ozturk + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +package com.owncloud.android.ui.activity + +import android.accounts.Account +import android.content.Context +import android.content.Intent +import android.content.ServiceConnection +import android.os.Bundle +import android.os.Handler +import android.os.Looper +import com.nextcloud.client.account.User +import com.nextcloud.client.jobs.BackgroundJobManager +import com.nextcloud.client.jobs.upload.FileUploadHelper +import com.nextcloud.client.network.ConnectivityService +import com.nextcloud.client.network.NetworkChangeListener +import com.nextcloud.client.player.ui.audio.AudioPlayerActivity +import com.nextcloud.utils.EditorUtils +import com.nextcloud.utils.SnackbarUtil +import com.nextcloud.utils.extensions.getParcelableArgument +import com.nextcloud.utils.extensions.logFileSize +import com.owncloud.android.MainApp +import com.owncloud.android.R +import com.owncloud.android.datamodel.ArbitraryDataProvider +import com.owncloud.android.datamodel.OCFile +import com.owncloud.android.lib.common.accounts.AccountUtils +import com.owncloud.android.lib.common.network.CertificateCombinedException +import com.owncloud.android.lib.common.operations.OnRemoteOperationListener +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 +import com.owncloud.android.lib.resources.shares.OCShare +import com.owncloud.android.lib.resources.shares.ShareType +import com.owncloud.android.providers.UsersAndGroupsSearchConfig +import com.owncloud.android.providers.UsersAndGroupsSearchProvider +import com.owncloud.android.services.OperationsService.OperationsServiceBinder +import com.owncloud.android.ui.activity.drawer.DrawerActivity +import com.owncloud.android.ui.activity.fileactivity.DevVersionChecker +import com.owncloud.android.ui.activity.fileactivity.FileOperationResultHandler +import com.owncloud.android.ui.activity.fileactivity.LoadingDialogController +import com.owncloud.android.ui.activity.fileactivity.OperationsServiceConnector +import com.owncloud.android.ui.activity.fileactivity.ShareOperationResultHandler +import com.owncloud.android.ui.activity.fileactivity.ShareWithIntentHandler +import com.owncloud.android.ui.asynctasks.CheckRemoteWipeTask +import com.owncloud.android.ui.asynctasks.LoadingVersionNumberTask.VersionDevInterface +import com.owncloud.android.ui.dialog.ConfirmationDialogFragment +import com.owncloud.android.ui.dialog.SslUntrustedCertDialog +import com.owncloud.android.ui.dialog.SslUntrustedCertDialog.OnSslUntrustedCertListener +import com.owncloud.android.ui.events.DialogEvent +import com.owncloud.android.ui.events.DialogEventType +import com.owncloud.android.ui.fragment.FileDetailFragment +import com.owncloud.android.ui.fragment.FileDetailSharingFragment.OnEditShareListener +import com.owncloud.android.ui.fragment.OCFileListFragment +import com.owncloud.android.ui.fragment.albums.AlbumItemsFragment +import com.owncloud.android.ui.fragment.albums.AlbumsFragment +import com.owncloud.android.ui.fragment.filesRepository.FilesRepository +import com.owncloud.android.ui.fragment.filesRepository.RemoteFilesRepository +import com.owncloud.android.ui.helpers.CredentialsUpdateHelper +import com.owncloud.android.ui.helpers.FileOperationsHelper +import com.owncloud.android.ui.navigation.model.ActionBarStyle +import com.owncloud.android.ui.preview.media.PreviewMediaActivity +import org.greenrobot.eventbus.Subscribe +import org.greenrobot.eventbus.ThreadMode +import java.lang.ref.WeakReference +import javax.inject.Inject + +@Suppress("TooManyFunctions") +abstract class FileActivity : + DrawerActivity(), + OnRemoteOperationListener, + ComponentsGetter, + OnSslUntrustedCertListener, + VersionDevInterface, + OnEditShareListener, + NetworkChangeListener { + + private var handledFile: OCFile? = null + + open var file: OCFile? + get() = handledFile + set(value) { + handledFile = value + } + + var isFromNotification = false + private set + + private val mainThreadHandler = Handler(Looper.getMainLooper()) + + open val handler: Handler + get() = mainThreadHandler + + private lateinit var operationsHelper: FileOperationsHelper + private val operationsServiceConnector = OperationsServiceConnector(this, this, ::doOnResumeAndBound) + private val loadingDialogController = LoadingDialogController(this) + private val operationResultHandler = FileOperationResultHandler(this) + private val shareOperationResultHandler = ShareOperationResultHandler(this) + private val shareWithIntentHandler = ShareWithIntentHandler(this, ::doShareWith) + + protected val fileUploadHelper: FileUploadHelper = FileUploadHelper.instance() + protected var isFileDisplayActivityResumed = false + + @Inject + lateinit var connectivityService: ConnectivityService + + @Inject + lateinit var backgroundJobManager: BackgroundJobManager + + @Inject + lateinit var editorUtils: EditorUtils + + @Inject + lateinit var usersAndGroupsSearchConfig: UsersAndGroupsSearchConfig + + @Inject + lateinit var arbitraryDataProvider: ArbitraryDataProvider + + lateinit var filesRepository: FilesRepository + private set + + private val fileDetailFragment: FileDetailFragment? + get() = supportFragmentManager.findFragmentByTag(FileDisplayActivity.TAG_LIST_OF_FILES) as? FileDetailFragment + + private val fileFromDetailFragment: OCFile? + get() { + val fragment = fileDetailFragment ?: return file + return fragment.file + } + + override fun showFiles(onDeviceOnly: Boolean, onlyPersonalFiles: Boolean) { + MainApp.showOnlyFilesOnDevice(onDeviceOnly) + MainApp.showOnlyPersonalFiles(onlyPersonalFiles) + showActionBar(if (onDeviceOnly) ActionBarStyle.Plain else ActionBarStyle.Search) + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + usersAndGroupsSearchConfig.reset() + operationsHelper = FileOperationsHelper(this, userAccountManager, connectivityService, editorUtils) + + if (savedInstanceState != null) { + restoreInstanceState(savedInstanceState) + } else { + readIntentExtras() + } + + operationsServiceConnector.bind() + filesRepository = RemoteFilesRepository(clientRepository, this) + } + + private fun restoreInstanceState(savedInstanceState: Bundle) { + handledFile = savedInstanceState.getParcelableArgument(EXTRA_FILE, OCFile::class.java) + isFromNotification = savedInstanceState.getBoolean(EXTRA_FROM_NOTIFICATION) + operationsHelper.opIdWaitingFor = savedInstanceState.getLong(KEY_WAITING_FOR_OP_ID, Long.MAX_VALUE) + + val actionBar = supportActionBar ?: return + if (this is PreviewMediaActivity) return + val actionBarTitle = savedInstanceState.getString(KEY_ACTION_BAR_TITLE) ?: return + viewThemeUtils.files.themeActionBar(this, actionBar, actionBarTitle) + } + + private fun readIntentExtras() { + val user = intent.getParcelableArgument(EXTRA_USER, User::class.java) + handledFile = intent.getParcelableArgument(EXTRA_FILE, OCFile::class.java) + isFromNotification = intent.getBooleanExtra(EXTRA_FROM_NOTIFICATION, false) + user?.let { setUser(it) } + } + + override fun networkAndServerConnectionListener(isNetworkAndServerAvailable: Boolean) { + when { + isNetworkAndServerAvailable -> { + hideInfoBox() + if (!isFileDisplayActivityResumed) { + refreshList() + } + } + + this is AudioPlayerActivity -> hideInfoBox() + + else -> showInfoBox(R.string.offline_mode) + } + } + + override fun onStart() { + super.onStart() + connectivityService.addListener(this) + fetchExternalLinks(false) + } + + override fun onStop() { + super.onStop() + connectivityService.removeListener(this) + } + + override fun onResume() { + super.onResume() + operationsServiceConnector.onResume() + } + + override fun onPause() { + operationsServiceConnector.onPause() + super.onPause() + } + + override fun onDestroy() { + operationsServiceConnector.unbind() + super.onDestroy() + } + + override fun onSaveInstanceState(outState: Bundle) { + super.onSaveInstanceState(outState) + handledFile.logFileSize(TAG) + outState.run { + putParcelable(EXTRA_FILE, handledFile) + putBoolean(EXTRA_FROM_NOTIFICATION, isFromNotification) + putLong(KEY_WAITING_FOR_OP_ID, operationsHelper.opIdWaitingFor) + supportActionBar?.title?.let { putString(KEY_ACTION_BAR_TITLE, it.toString()) } + } + } + + override fun getOperationsServiceBinder(): OperationsServiceBinder? = operationsServiceConnector.binder + + protected open fun newTransferenceServiceConnection(): ServiceConnection? = null + + override fun getFileOperationsHelper(): FileOperationsHelper = operationsHelper + + override fun getFileUploaderHelper(): FileUploadHelper = fileUploadHelper + + fun getCurrentDir(): OCFile? { + val currentFile = file ?: return null + if (currentFile.isFolder) return currentFile + return storageManager?.getFileByPath(currentFile.parentRemotePath) + } + + override fun onRemoteOperationFinish(operation: RemoteOperation<*>?, result: RemoteOperationResult<*>) { + Log_OC.d(TAG, "Received result of operation in FileActivity - common behaviour for all the FileActivities ") + operationsHelper.opIdWaitingFor = Long.MAX_VALUE + dismissLoadingDialog() + operationResultHandler.onOperationFinish(operation, result) + shareOperationResultHandler.onOperationFinish(operation, result) + } + + private fun doOnResumeAndBound(binder: OperationsServiceBinder) { + binder.addOperationListener(this, mainThreadHandler) + + val waitingForOpId = operationsHelper.opIdWaitingFor + if (waitingForOpId > Int.MAX_VALUE) return + + val isOperationDispatchedOrPending = binder.dispatchResultIfFinished(waitingForOpId.toInt(), this) + if (!isOperationDispatchedOrPending) { + dismissLoadingDialog() + } + } + + @JvmOverloads + fun requestCredentialsUpdate(account: Account? = null) { + val targetAccount = account ?: this.account + CheckRemoteWipeTask(backgroundJobManager, targetAccount, WeakReference(this)).execute() + } + + fun performCredentialsUpdate(account: Account) { + val credentialsUpdateHelper = CredentialsUpdateHelper(this) + try { + credentialsUpdateHelper.invalidateCredentials(account) + startActivityForResult( + credentialsUpdateHelper.createUpdateCredentialsIntent(account), + REQUEST_CODE__UPDATE_CREDENTIALS + ) + } catch (e: AccountUtils.AccountNotFoundException) { + Log_OC.e(TAG, "Account not found while updating credentials", e) + SnackbarUtil.show(this, R.string.auth_account_does_not_exist) + } + } + + fun showUntrustedCertDialog(result: RemoteOperationResult<*>) { + val fragmentManager = supportFragmentManager + if (fragmentManager.findFragmentByTag(DIALOG_UNTRUSTED_CERT) != null) return + + SslUntrustedCertDialog + .newInstanceForFullSslError(result.exception as CertificateCombinedException?) + .show(fragmentManager.beginTransaction(), DIALOG_UNTRUSTED_CERT) + } + + fun updateFileFromDB() { + val currentFile = file ?: return + file = storageManager.getFileByPath(currentFile.remotePath) + } + + fun showLoadingDialog(message: String?) = loadingDialogController.show(message) + + fun dismissLoadingDialog() = loadingDialogController.dismiss() + + fun showSyncLoadingDialog(isFolder: Boolean) { + if (isFolder) return + showLoadingDialog(applicationContext.getString(R.string.wait_a_moment)) + } + + @Subscribe(threadMode = ThreadMode.MAIN) + fun handleSyncDialogEvent(event: DialogEvent) { + if (event.type == DialogEventType.SYNC) { + dismissLoadingDialog() + } + } + + override fun onSavedCertificate() = Unit + + override fun onFailedSavingCertificate() { + ConfirmationDialogFragment.newInstance( + messageResId = R.string.ssl_validator_not_saved, + messageArguments = emptyArray(), + titleResId = NO_TITLE_RES, + positiveButtonTextId = R.string.common_ok, + negativeButtonTextId = NO_BUTTON_RES, + neutralButtonTextId = NO_BUTTON_RES + ).show(supportFragmentManager, DIALOG_CERT_NOT_SAVED) + } + + fun checkForNewDevVersionNecessary(context: Context) { + if (DevVersionChecker.isCheckNecessary(resources, arbitraryDataProvider)) { + DevVersionChecker.checkForNewDevVersion(this, context) + } + } + + override fun returnVersion(latestVersion: Int?) { + val version = latestVersion ?: return + DevVersionChecker.showDevSnackbar(this, version, openDirectly = false, inBackground = true) + } + + fun refreshList() { + when { + isAlbumsFragment -> getFragment(AlbumsFragment.TAG, AlbumsFragment::class.java) + .ifPresent { it.refreshAlbums() } + + isAlbumItemsFragment -> getFragment(AlbumItemsFragment.TAG, AlbumItemsFragment::class.java) + .ifPresent { it.refreshData() } + + else -> refreshListOfFilesFragment() + } + } + + private fun refreshListOfFilesFragment() { + when (val fragment = supportFragmentManager.findFragmentByTag(FileDisplayActivity.TAG_LIST_OF_FILES)) { + is OCFileListFragment -> fragment.onRefresh() + is FileDetailFragment -> fragment.goBackToOCFileListFragment() + } + } + + override fun onNewIntent(intent: Intent) { + super.onNewIntent(intent) + if (intent.action != UsersAndGroupsSearchProvider.ACTION_SHARE_WITH) return + + val remotePath = fileFromDetailFragment?.remotePath ?: return + shareWithIntentHandler.handle(intent, remotePath) + } + + protected open fun doShareWith(shareeName: String, shareType: ShareType) { + fileDetailFragment?.initiateSharingProcess(shareeName, shareType, usersAndGroupsSearchConfig.searchOnlyUsers) + } + + override fun editExistingShare(share: OCShare, screenTypePermission: Int, isReshareShown: Boolean) { + fileDetailFragment?.editExistingShare(share, screenTypePermission, isReshareShown) + } + + override fun onShareProcessClosed() { + fileDetailFragment?.showHideFragmentView(false) + } + + companion object { + const val EXTRA_FILE = "com.owncloud.android.ui.activity.FILE" + const val EXTRA_FILE_REMOTE_PATH = "com.owncloud.android.ui.activity.FILE_REMOTE_PATH" + const val EXTRA_LIVE_PHOTO_FILE = "com.owncloud.android.ui.activity.LIVE.PHOTO.FILE" + const val EXTRA_USER = "com.owncloud.android.ui.activity.USER" + const val EXTRA_FROM_NOTIFICATION = "com.owncloud.android.ui.activity.FROM_NOTIFICATION" + const val APP_OPENED_COUNT = "APP_OPENED_COUNT" + const val EXTRA_SEARCH = "com.owncloud.android.ui.activity.SEARCH" + const val EXTRA_SEARCH_QUERY = "com.owncloud.android.ui.activity.SEARCH_QUERY" + + @JvmField + val TAG: String = FileActivity::class.java.simpleName + + const val REQUEST_CODE__UPDATE_CREDENTIALS = 0 + const val REQUEST_CODE__LAST_SHARED = REQUEST_CODE__UPDATE_CREDENTIALS + + private const val DIALOG_UNTRUSTED_CERT = "DIALOG_UNTRUSTED_CERT" + private const val DIALOG_CERT_NOT_SAVED = "DIALOG_CERT_NOT_SAVED" + + private const val KEY_WAITING_FOR_OP_ID = "WAITING_FOR_OP_ID" + private const val KEY_ACTION_BAR_TITLE = "ACTION_BAR_TITLE" + + private const val NO_TITLE_RES = 0 + private const val NO_BUTTON_RES = -1 + } +} diff --git a/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt b/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt index 0385a900130a..d3ae4fa89eee 100644 --- a/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt +++ b/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt @@ -558,7 +558,7 @@ class FileDisplayActivity : return } - setFile(initialFile) + file = initialFile val accountName = currentUser.accountName switchAccountButton.tag = accountName @@ -644,12 +644,12 @@ class FileDisplayActivity : fragment.listDirectory(MainApp.isOnlyOnDevice) } fragment.registerFabListener() - updateActionBarTitleAndHomeButton(currentDir) + updateActionBarTitleAndHomeButton(getCurrentDir()) } else -> { fragment.listDirectory(startFile, false) - updateActionBarTitleAndHomeButton(currentDir ?: startFile) + updateActionBarTitleAndHomeButton(getCurrentDir() ?: startFile) } } } @@ -703,7 +703,7 @@ class FileDisplayActivity : when { ACTION_DETAILS.equals(action, ignoreCase = true) -> { val detailsFile = getFileFromIntent(intent) - setFile(detailsFile) + file = detailsFile showDetails(detailsFile) isShowingDetailsFromPreview = intent.getBooleanExtra(EXTRA_RETURN_TO_PREVIEW, false) } @@ -980,10 +980,12 @@ class FileDisplayActivity : override val menuItemId: Int get() = MainApp.menuItemId ?: listOfFilesFragment?.menuItemId ?: R.id.nav_all_files - override fun setFile(file: OCFile?) { - super.setFile(file) - file?.takeIf { it.isFolder }?.let { viewModel.setCurrentFolder(it.fileId) } - } + override var file: OCFile? + get() = super.file + set(value) { + super.file = value + value?.takeIf { it.isFolder }?.let { viewModel.setCurrentFolder(it.fileId) } + } private fun replaceLeftFragment(fragment: Fragment, showSortListGroup: Boolean = true) { isShowingDetailsFromPreview = false @@ -1120,7 +1122,7 @@ class FileDisplayActivity : fun performUnifiedSearch(query: String, listOfHiddenFiles: ArrayList?) { val unifiedSearchFragment = - UnifiedSearchFragment.newInstance(query, listOfHiddenFiles, currentDir?.decryptedRemotePath) + UnifiedSearchFragment.newInstance(query, listOfHiddenFiles, getCurrentDir()?.decryptedRemotePath) replaceLeftFragment(unifiedSearchFragment, showSortListGroup = false) } @@ -2071,7 +2073,7 @@ class FileDisplayActivity : configureToolbarForPreview(file) waitingToPreview = file downloadWaitingPreview() - setFile(file) + this.file = file } // endregion @@ -2137,7 +2139,7 @@ class FileDisplayActivity : } supportInvalidateOptionsMenu() - fetchRecommendedFilesIfNeeded(ignoreETag = true, currentDir) + fetchRecommendedFilesIfNeeded(ignoreETag = true, getCurrentDir()) } private fun updateFileListAfterRemoval(fragment: OCFileListFragment, removedFile: OCFile, onlyLocalCopy: Boolean) { @@ -2192,7 +2194,7 @@ class FileDisplayActivity : return } - val restoredFile = getFile() + val restoredFile = file // the new version is only downloaded when the old one was downloaded if (restoredFile?.isDown == true) { @@ -2238,10 +2240,10 @@ class FileDisplayActivity : (leftFragment as? FileFragment)?.let { refreshFileFragmentAfterRename(it, renamedFile, currentUser) } val parentDir = storageManager.getFileById(renamedFile.parentId) - val isRenamedInCurrentDir = parentDir != null && parentDir == currentDir + val isRenamedInCurrentDir = parentDir != null && parentDir == getCurrentDir() // the parent check fails in the Shared/Favorites root, so the root is always refreshed too - if (currentDir?.isRootDirectory == true || isRenamedInCurrentDir) { + if (getCurrentDir()?.isRootDirectory == true || isRenamedInCurrentDir) { val fileList = topFileListFragment // OCFileSearchTask may still run during rename, so use a single refresh path to avoid @@ -2254,7 +2256,7 @@ class FileDisplayActivity : } refreshGalleryFragmentIfNeeded() - fetchRecommendedFilesIfNeeded(ignoreETag = true, currentDir) + fetchRecommendedFilesIfNeeded(ignoreETag = true, getCurrentDir()) } private fun refreshFileFragmentAfterRename(fragment: FileFragment, renamedFile: OCFile, user: User) { diff --git a/app/src/main/java/com/owncloud/android/ui/activity/FolderPickerActivity.kt b/app/src/main/java/com/owncloud/android/ui/activity/FolderPickerActivity.kt index 930366a7782f..f71f54bf079c 100644 --- a/app/src/main/java/com/owncloud/android/ui/activity/FolderPickerActivity.kt +++ b/app/src/main/java/com/owncloud/android/ui/activity/FolderPickerActivity.kt @@ -241,7 +241,7 @@ open class FolderPickerActivity : } override fun onSavedCertificate() { - startSyncFolderOperation(currentDir, false) + startSyncFolderOperation(getCurrentDir(), false) } private fun startSyncFolderOperation(folder: OCFile?, ignoreETag: Boolean) { @@ -450,7 +450,8 @@ open class FolderPickerActivity : } private fun getSelectedFolderPathTitle(): String? { - val atRoot = (currentDir == null || currentDir?.parentId == 0L) + val currentDir = getCurrentDir() + val atRoot = (currentDir == null || currentDir.parentId == 0L) return if (atRoot) captionText ?: "" else currentDir?.fileName } diff --git a/app/src/main/java/com/owncloud/android/ui/activity/ManageAccountsActivity.kt b/app/src/main/java/com/owncloud/android/ui/activity/ManageAccountsActivity.kt index 3660d64b59fe..5e48950b5887 100644 --- a/app/src/main/java/com/owncloud/android/ui/activity/ManageAccountsActivity.kt +++ b/app/src/main/java/com/owncloud/android/ui/activity/ManageAccountsActivity.kt @@ -52,7 +52,6 @@ import com.owncloud.android.ui.adapter.UserListAdapter import com.owncloud.android.ui.adapter.UserListItem import com.owncloud.android.ui.dialog.AccountRemovalDialog.Companion.newInstance import com.owncloud.android.ui.events.AccountRemovedEvent -import com.owncloud.android.ui.helpers.FileOperationsHelper import com.owncloud.android.ui.navigation.model.ActionBarStyle import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.ThreadMode @@ -69,7 +68,7 @@ class ManageAccountsActivity : UserListAdapter.ClickListener { private var recyclerView: RecyclerView? = null - private val handler = Handler() + override val handler = Handler() private var accountName: String? = null private var userListAdapter: UserListAdapter? = null private var originalUsers: Set? = null @@ -341,14 +340,10 @@ class ManageAccountsActivity : } } - override fun getHandler(): Handler = handler - override fun getOperationsServiceBinder(): OperationsServiceBinder? = null override fun getStorageManager(): FileDataStorageManager = super.getStorageManager() - override fun getFileOperationsHelper(): FileOperationsHelper? = null - @Suppress("DEPRECATION") @SuppressLint("NotifyDataSetChanged") private fun performAccountRemoval(user: User) { diff --git a/app/src/main/java/com/owncloud/android/ui/activity/SettingsActivity.java b/app/src/main/java/com/owncloud/android/ui/activity/SettingsActivity.java index 0b8c51755f78..92a192af45c9 100644 --- a/app/src/main/java/com/owncloud/android/ui/activity/SettingsActivity.java +++ b/app/src/main/java/com/owncloud/android/ui/activity/SettingsActivity.java @@ -72,6 +72,7 @@ import com.owncloud.android.operations.e2e.E2EDeletionService; import com.owncloud.android.providers.documentstorage.DocumentsStorageProvider; import com.owncloud.android.ui.ThemeableSwitchPreference; +import com.owncloud.android.ui.activity.fileactivity.DevVersionChecker; import com.owncloud.android.ui.asynctasks.LoadingVersionNumberTask; import com.owncloud.android.ui.dialog.setupEncryption.SetupEncryptionDialogFragment; import com.owncloud.android.ui.helpers.FileOperationsHelper; @@ -241,7 +242,7 @@ private void setupDevCategory(PreferenceScreen preferenceScreen) { if (pDevLink != null) { if (getResources().getBoolean(R.bool.dev_version_direct_download_enabled)) { pDevLink.setOnPreferenceClickListener(preference -> { - FileActivity.checkForNewDevVersion(this, getApplicationContext()); + DevVersionChecker.checkForNewDevVersion(this, getApplicationContext()); return true; }); } else { @@ -1324,6 +1325,6 @@ public void onCancelMigration() { @Override public void returnVersion(Integer latestVersion) { - FileActivity.showDevSnackbar(this, latestVersion, true, false); + DevVersionChecker.showDevSnackbar(this, latestVersion, true, false); } } diff --git a/app/src/main/java/com/owncloud/android/ui/activity/fileactivity/DevVersionChecker.kt b/app/src/main/java/com/owncloud/android/ui/activity/fileactivity/DevVersionChecker.kt new file mode 100644 index 000000000000..ca995f2114f5 --- /dev/null +++ b/app/src/main/java/com/owncloud/android/ui/activity/fileactivity/DevVersionChecker.kt @@ -0,0 +1,83 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2026 Alper Ozturk + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +package com.owncloud.android.ui.activity.fileactivity + +import android.app.Activity +import android.content.Context +import android.content.pm.PackageManager +import android.content.res.Resources +import com.google.android.material.snackbar.Snackbar +import com.nextcloud.client.utils.IntentUtil +import com.nextcloud.utils.SnackbarUtil +import com.owncloud.android.R +import com.owncloud.android.datamodel.ArbitraryDataProvider +import com.owncloud.android.lib.common.utils.Log_OC +import com.owncloud.android.ui.activity.FileActivity +import com.owncloud.android.ui.asynctasks.LoadingVersionNumberTask +import com.owncloud.android.ui.asynctasks.LoadingVersionNumberTask.VersionDevInterface +import com.owncloud.android.utils.FilesSyncHelper + +object DevVersionChecker { + private const val TAG = "DevVersionChecker" + private const val UNKNOWN_VALUE = -1 + private const val MIN_APP_OPENED_COUNT_FOR_CHECK = 10 + private const val DEV_APK_EXTENSION = ".apk" + + fun isCheckNecessary(resources: Resources, arbitraryDataProvider: ArbitraryDataProvider): Boolean { + if (!resources.getBoolean(R.bool.dev_version_direct_download_enabled)) return false + + val appOpenedCount = + arbitraryDataProvider.getIntegerValue(FilesSyncHelper.GLOBAL, FileActivity.APP_OPENED_COUNT) + return appOpenedCount > MIN_APP_OPENED_COUNT_FOR_CHECK || appOpenedCount == UNKNOWN_VALUE + } + + @JvmStatic + fun checkForNewDevVersion(callback: VersionDevInterface?, context: Context) { + LoadingVersionNumberTask(callback).execute(context.getString(R.string.dev_latest)) + } + + @JvmStatic + fun showDevSnackbar(activity: Activity, latestVersion: Int, openDirectly: Boolean, inBackground: Boolean) { + val currentVersion = activity.installedVersionCode() + + if (latestVersion == UNKNOWN_VALUE || currentVersion == UNKNOWN_VALUE) { + SnackbarUtil.show(activity, R.string.dev_version_no_information_available) + } + + if (latestVersion <= currentVersion) { + if (!inBackground) { + SnackbarUtil.show(activity, R.string.dev_version_no_new_version_available) + } + return + } + + val devApkLink = activity.getString(R.string.dev_link) + latestVersion + DEV_APK_EXTENSION + if (openDirectly) { + IntentUtil.startLinkIntent(activity, devApkLink) + return + } + + Snackbar + .make( + activity.findViewById(android.R.id.content), + R.string.dev_version_new_version_available, + Snackbar.LENGTH_LONG + ) + .setAction(activity.getString(R.string.version_dev_download)) { + IntentUtil.startLinkIntent(activity, devApkLink) + } + .show() + } + + @Suppress("DEPRECATION") + private fun Activity.installedVersionCode(): Int = try { + packageManager.getPackageInfo(packageName, 0).versionCode + } catch (e: PackageManager.NameNotFoundException) { + Log_OC.e(TAG, "Package not found", e) + UNKNOWN_VALUE + } +} diff --git a/app/src/main/java/com/owncloud/android/ui/activity/fileactivity/FileOperationResultHandler.kt b/app/src/main/java/com/owncloud/android/ui/activity/fileactivity/FileOperationResultHandler.kt new file mode 100644 index 000000000000..b75611566f71 --- /dev/null +++ b/app/src/main/java/com/owncloud/android/ui/activity/fileactivity/FileOperationResultHandler.kt @@ -0,0 +1,99 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2026 Alper Ozturk + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +package com.owncloud.android.ui.activity.fileactivity + +import android.accounts.AuthenticatorException +import com.nextcloud.utils.SnackbarUtil +import com.owncloud.android.lib.common.operations.RemoteOperation +import com.owncloud.android.lib.common.operations.RemoteOperationResult +import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode +import com.owncloud.android.operations.GetSharesForFileOperation +import com.owncloud.android.operations.SynchronizeFileOperation +import com.owncloud.android.operations.SynchronizeFolderOperation +import com.owncloud.android.ui.activity.ConflictsResolveActivity +import com.owncloud.android.ui.activity.FileActivity +import com.owncloud.android.utils.ErrorMessageAdapter + +class FileOperationResultHandler(private val activity: FileActivity) { + + fun onOperationFinish(operation: RemoteOperation<*>?, result: RemoteOperationResult<*>) { + when { + result.isAuthenticationFailure() -> onAuthenticationFailure(operation, result) + + !result.isSuccess && result.code == ResultCode.SSL_RECOVERABLE_PEER_UNVERIFIED -> + activity.showUntrustedCertDialog(result) + + operation == null || operation is SynchronizeFolderOperation -> + onSynchronizeFolderOperationFinish(operation, result) + + operation is SynchronizeFileOperation -> onSynchronizeFileOperationFinish(operation, result) + + operation is GetSharesForFileOperation -> onGetSharesForFileOperationFinish(operation, result) + } + } + + private fun RemoteOperationResult<*>.isAuthenticationFailure(): Boolean = + !isSuccess && (code == ResultCode.UNAUTHORIZED || (isException && exception is AuthenticatorException)) + + private fun onAuthenticationFailure(operation: RemoteOperation<*>?, result: RemoteOperationResult<*>) { + activity.requestCredentialsUpdate() + if (result.code == ResultCode.UNAUTHORIZED) { + showErrorCause(operation, result) + } + } + + private fun onSynchronizeFolderOperationFinish(operation: RemoteOperation<*>?, result: RemoteOperationResult<*>) { + when { + result.isSuccess -> activity.updateFileFromDB() + result.code != ResultCode.CANCELLED -> showErrorCause(operation, result) + } + } + + private fun onGetSharesForFileOperationFinish( + operation: GetSharesForFileOperation, + result: RemoteOperationResult<*> + ) { + if (result.isSuccess || result.code == ResultCode.SHARE_NOT_FOUND) { + activity.updateFileFromDB() + } else { + showErrorCause(operation, result) + } + } + + private fun onSynchronizeFileOperationFinish( + operation: SynchronizeFileOperation, + result: RemoteOperationResult<*> + ) { + if (result.isSuccess) { + if (!operation.transferWasRequested) { + showErrorCause(operation, result) + } + activity.supportInvalidateOptionsMenu() + return + } + + if (result.code != ResultCode.SYNC_CONFLICT) return + + val currentUser = activity.user.orElseThrow { RuntimeException() } + val intent = ConflictsResolveActivity.createIntent( + operation.localFile, + currentUser, + NO_CONFLICT_UPLOAD_ID, + null, + activity + ) + activity.startActivity(intent) + } + + private fun showErrorCause(operation: RemoteOperation<*>?, result: RemoteOperationResult<*>) { + SnackbarUtil.show(activity, ErrorMessageAdapter.getErrorCauseMessage(result, operation, activity.resources)) + } + + companion object { + private const val NO_CONFLICT_UPLOAD_ID = -1L + } +} diff --git a/app/src/main/java/com/owncloud/android/ui/activity/fileactivity/LoadingDialogController.kt b/app/src/main/java/com/owncloud/android/ui/activity/fileactivity/LoadingDialogController.kt new file mode 100644 index 000000000000..e120fa9be6ca --- /dev/null +++ b/app/src/main/java/com/owncloud/android/ui/activity/fileactivity/LoadingDialogController.kt @@ -0,0 +1,63 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2026 Alper Ozturk + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +package com.owncloud.android.ui.activity.fileactivity + +import androidx.appcompat.app.AppCompatActivity +import androidx.fragment.app.FragmentManager +import com.nextcloud.utils.extensions.isActive +import com.owncloud.android.lib.common.utils.Log_OC +import com.owncloud.android.ui.dialog.LoadingDialog + +class LoadingDialogController(private val activity: AppCompatActivity) { + + fun show(message: String?) { + activity.runOnUiThread { + if (!activity.isActive()) { + Log_OC.w(TAG, "cannot show loading dialog, activity is finishing") + return@runOnUiThread + } + + val fragmentManager = activity.supportFragmentManager + fragmentManager.executePendingTransactions() + fragmentManager.dismissExistingLoadingDialog("dismiss previous loading dialog") + + if (fragmentManager.isStateSaved) return@runOnUiThread + + Log_OC.d(TAG, "show loading dialog") + LoadingDialog.newInstance(message).show(fragmentManager, DIALOG_WAIT_TAG) + } + } + + fun dismiss() { + activity.runOnUiThread { + if (!activity.isActive()) { + Log_OC.w(TAG, "cannot dismiss loading dialog, activity is finishing") + return@runOnUiThread + } + + val fragmentManager = activity.supportFragmentManager + fragmentManager.executePendingTransactions() + fragmentManager.dismissExistingLoadingDialog("dismiss loading dialog") + } + } + + private fun FragmentManager.dismissExistingLoadingDialog(logMessage: String) { + val loadingDialog = findFragmentByTag(DIALOG_WAIT_TAG) as? LoadingDialog ?: return + Log_OC.d(TAG, logMessage) + + if (isStateSaved) { + loadingDialog.dismissAllowingStateLoss() + } else { + loadingDialog.dismiss() + } + } + + companion object { + private const val TAG = "LoadingDialogController" + private const val DIALOG_WAIT_TAG = "DIALOG_WAIT" + } +} diff --git a/app/src/main/java/com/owncloud/android/ui/activity/fileactivity/OperationsServiceConnector.kt b/app/src/main/java/com/owncloud/android/ui/activity/fileactivity/OperationsServiceConnector.kt new file mode 100644 index 000000000000..f2a550212420 --- /dev/null +++ b/app/src/main/java/com/owncloud/android/ui/activity/fileactivity/OperationsServiceConnector.kt @@ -0,0 +1,77 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2026 Alper Ozturk + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +package com.owncloud.android.ui.activity.fileactivity + +import android.app.Activity +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.content.ServiceConnection +import android.os.IBinder +import com.owncloud.android.lib.common.operations.OnRemoteOperationListener +import com.owncloud.android.lib.common.utils.Log_OC +import com.owncloud.android.services.OperationsService +import com.owncloud.android.services.OperationsService.OperationsServiceBinder + +class OperationsServiceConnector( + private val activity: Activity, + private val listener: OnRemoteOperationListener, + private val onBoundWhileResumed: (OperationsServiceBinder) -> Unit +) : ServiceConnection { + + var binder: OperationsServiceBinder? = null + private set + + private var isBound = false + private var isActivityResumed = false + + fun bind() { + activity.bindService(Intent(activity, OperationsService::class.java), this, Context.BIND_AUTO_CREATE) + isBound = true + } + + fun unbind() { + if (!isBound) return + activity.unbindService(this) + binder = null + } + + fun onResume() { + isActivityResumed = true + binder?.let(onBoundWhileResumed) + } + + fun onPause() { + binder?.removeOperationListener(listener) + isActivityResumed = false + } + + override fun onServiceConnected(component: ComponentName, service: IBinder) { + if (!component.isOperationsService()) return + + Log_OC.d(TAG, "Operations service connected") + val connectedBinder = service as OperationsServiceBinder + binder = connectedBinder + if (isActivityResumed) { + onBoundWhileResumed(connectedBinder) + } + } + + override fun onServiceDisconnected(component: ComponentName) { + if (!component.isOperationsService()) return + + Log_OC.d(TAG, "Operations service disconnected") + binder = null + } + + private fun ComponentName.isOperationsService(): Boolean = + this == ComponentName(activity, OperationsService::class.java) + + companion object { + private const val TAG = "OperationsServiceConnector" + } +} diff --git a/app/src/main/java/com/owncloud/android/ui/activity/fileactivity/ShareLinkHelper.kt b/app/src/main/java/com/owncloud/android/ui/activity/fileactivity/ShareLinkHelper.kt new file mode 100644 index 000000000000..b214144a775c --- /dev/null +++ b/app/src/main/java/com/owncloud/android/ui/activity/fileactivity/ShareLinkHelper.kt @@ -0,0 +1,67 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2026 Alper Ozturk + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +package com.owncloud.android.ui.activity.fileactivity + +import android.content.Intent +import com.google.android.material.snackbar.Snackbar +import com.nextcloud.utils.mdm.MDMConfig +import com.owncloud.android.R +import com.owncloud.android.datamodel.OCFile +import com.owncloud.android.lib.common.OwnCloudAccount +import com.owncloud.android.lib.common.accounts.AccountUtils +import com.owncloud.android.lib.common.utils.Log_OC +import com.owncloud.android.lib.resources.files.model.ServerFileInterface +import com.owncloud.android.ui.activity.FileActivity +import com.owncloud.android.ui.activity.FileDisplayActivity +import com.owncloud.android.ui.dialog.ShareLinkToDialog +import com.owncloud.android.utils.ClipboardUtil +import com.owncloud.android.utils.MimeType + +object ShareLinkHelper { + private const val TAG = "ShareLinkHelper" + + fun copyAndShareFileLink(activity: FileActivity, file: OCFile, link: String?) { + if (!MDMConfig.shareViaLink(activity) || !MDMConfig.clipBoardSupport(activity)) return + + ClipboardUtil.copyToClipboard(activity, link, false) + Snackbar + .make(activity.findViewById(android.R.id.content), R.string.clipboard_text_copied, Snackbar.LENGTH_LONG) + .setAction(R.string.share) { showShareLinkDialog(activity, file, link) } + .also { activity.viewThemeUtils.material.themeSnackbar(it) } + .show() + } + + fun showShareLinkDialog(activity: FileActivity, file: ServerFileInterface, link: String?) { + val username = activity.sharingUsername() + val subject = if (username != null) { + activity.getString(R.string.subject_user_shared_with_you, username, file.fileName) + } else { + activity.getString(R.string.subject_shared_with_you, file.fileName) + } + + val intentToShareLink = Intent(Intent.ACTION_SEND).apply { + putExtra(Intent.EXTRA_TEXT, link) + type = MimeType.TEXT_PLAIN + putExtra(Intent.EXTRA_SUBJECT, subject) + } + + ShareLinkToDialog + .newInstance(intentToShareLink, activity.packageName) + .show(activity.supportFragmentManager, FileDisplayActivity.FTAG_CHOOSER_DIALOG) + } + + @Suppress("TooGenericExceptionCaught") + private fun FileActivity.sharingUsername(): String? { + val displayName = try { + OwnCloudAccount(account, this).displayName + } catch (e: Exception) { + Log_OC.e(TAG, "Could not read account display name", e) + null + } + return displayName.takeUnless { it.isNullOrEmpty() } ?: AccountUtils.getUsernameForAccount(account) + } +} diff --git a/app/src/main/java/com/owncloud/android/ui/activity/fileactivity/ShareOperationResultHandler.kt b/app/src/main/java/com/owncloud/android/ui/activity/fileactivity/ShareOperationResultHandler.kt new file mode 100644 index 000000000000..b03a19d80124 --- /dev/null +++ b/app/src/main/java/com/owncloud/android/ui/activity/fileactivity/ShareOperationResultHandler.kt @@ -0,0 +1,155 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2026 Alper Ozturk + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +package com.owncloud.android.ui.activity.fileactivity + +import androidx.annotation.StringRes +import com.google.android.material.snackbar.Snackbar +import com.nextcloud.utils.SnackbarUtil +import com.owncloud.android.R +import com.owncloud.android.lib.common.operations.RemoteOperation +import com.owncloud.android.lib.common.operations.RemoteOperationResult +import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode +import com.owncloud.android.lib.resources.shares.OCShare +import com.owncloud.android.operations.CreateShareViaLinkOperation +import com.owncloud.android.operations.CreateShareWithShareeOperation +import com.owncloud.android.operations.SetFilesDownloadLimitOperation +import com.owncloud.android.operations.UnshareOperation +import com.owncloud.android.operations.UpdateNoteForShareOperation +import com.owncloud.android.operations.UpdateShareInfoOperation +import com.owncloud.android.operations.UpdateSharePermissionsOperation +import com.owncloud.android.operations.UpdateShareViaLinkOperation +import com.owncloud.android.ui.activity.FileActivity +import com.owncloud.android.ui.activity.FileDisplayActivity +import com.owncloud.android.ui.activity.ShareActivity +import com.owncloud.android.ui.fragment.FileDetailFragment +import com.owncloud.android.ui.fragment.FileDetailSharingFragment +import com.owncloud.android.ui.fragment.OCFileListFragment +import com.owncloud.android.utils.ErrorMessageAdapter + +class ShareOperationResultHandler(private val activity: FileActivity) { + + private val fragmentManager + get() = activity.supportFragmentManager + + private val sharingFragment: FileDetailSharingFragment? + get() { + val fragment = fragmentManager.findFragmentByTag(ShareActivity.TAG_SHARE_FRAGMENT) + ?: fragmentManager.findFragmentByTag(FileDisplayActivity.TAG_LIST_OF_FILES) + + return when (fragment) { + is FileDetailSharingFragment -> fragment + is FileDetailFragment -> fragment.fileDetailSharingFragment + else -> null + } + } + + fun onOperationFinish(operation: RemoteOperation<*>?, result: RemoteOperationResult<*>) { + when (operation) { + is CreateShareViaLinkOperation -> onCreateShareViaLinkOperationFinish(operation, result) + + is CreateShareWithShareeOperation -> onUpdateShareInformation(result, R.string.sharee_add_failed) + + is UpdateShareViaLinkOperation, + is UpdateShareInfoOperation, + is UpdateSharePermissionsOperation -> onUpdateShareInformation(result, R.string.updating_share_failed) + + is SetFilesDownloadLimitOperation -> onUpdateShareInformation(result, R.string.set_download_limit_failed) + + is UnshareOperation -> onUpdateShareInformation(result, R.string.unsharing_failed) + + is UpdateNoteForShareOperation -> onUpdateNoteForShareOperationFinish(result) + } + } + + private fun onUpdateNoteForShareOperationFinish(result: RemoteOperationResult<*>) { + if (!result.isSuccess) { + SnackbarUtil.show(activity, R.string.note_could_not_sent) + return + } + sharingFragment?.onUpdateShareInformation(result) + } + + private fun onUpdateShareInformation(result: RemoteOperationResult<*>, @StringRes defaultError: Int) { + val sharingFragment = sharingFragment + + if (result.isSuccess) { + activity.updateFileFromDB() + sharingFragment?.onUpdateShareInformation(result) + return + } + + val message = result.message.takeUnless { it.isNullOrEmpty() } ?: activity.getString(defaultError) + val sharingFragmentView = sharingFragment?.view + if (sharingFragmentView == null) { + SnackbarUtil.show(activity, message) + return + } + + Snackbar.make(sharingFragmentView, message, Snackbar.LENGTH_LONG).showThemed() + } + + private fun onCreateShareViaLinkOperationFinish( + operation: CreateShareViaLinkOperation, + result: RemoteOperationResult<*> + ) { + if (result.isSuccess) { + onShareViaLinkCreated(result) + } else { + onShareViaLinkFailed(operation, result) + } + } + + private fun onShareViaLinkCreated(result: RemoteOperationResult<*>) { + val sharingFragment = sharingFragment + val fileListFragment = fragmentManager.findFragmentByTag(FileDisplayActivity.TAG_LIST_OF_FILES) + activity.updateFileFromDB() + + val publicLinkShare = result.data + .filterIsInstance() + .firstOrNull { FileDisplayActivity.TAG_PUBLIC_LINK.equals(it.shareType?.name, ignoreCase = true) } + val sharedFile = publicLinkShare?.let { activity.storageManager.getFileByEncryptedRemotePath(it.path) } + + if (publicLinkShare != null && sharedFile != null) { + ShareLinkHelper.copyAndShareFileLink(activity, sharedFile, publicLinkShare.shareLink) + } + sharingFragment?.onUpdateShareInformation(result, sharedFile) + + if (fileListFragment !is OCFileListFragment || sharedFile == null) return + + if (fileListFragment.adapterFiles.contains(sharedFile)) { + fileListFragment.updateOCFile(sharedFile) + } else { + SnackbarUtil.show(activity, R.string.file_activity_shared_file_cannot_be_updated) + } + } + + private fun onShareViaLinkFailed(operation: CreateShareViaLinkOperation, result: RemoteOperationResult<*>) { + val sharingFragment = sharingFragment + val isForbiddenWithoutPassword = + result.code == ResultCode.SHARE_FORBIDDEN && operation.password.isNullOrEmpty() + + if (!isForbiddenWithoutPassword) { + sharingFragment?.refreshSharesFromDB() + val errorMessage = ErrorMessageAdapter.getErrorCauseMessage(result, operation, activity.resources) + Snackbar + .make(activity.findViewById(android.R.id.content), errorMessage, Snackbar.LENGTH_LONG) + .showThemed() + return + } + + val capability = activity.capabilities.orElse(null) ?: return + if (!capability.filesSharingPublicEnabled.isUnknown) return + if (sharingFragment == null || !sharingFragment.isAdded) return + + sharingFragment.requestPasswordForShareViaLink(true, capability.filesSharingPublicAskForOptionalPassword.isTrue) + } + + private fun Snackbar.showThemed() { + activity.viewThemeUtils.material.themeSnackbar(this) + show() + } +} diff --git a/app/src/main/java/com/owncloud/android/ui/activity/fileactivity/ShareWithIntentHandler.kt b/app/src/main/java/com/owncloud/android/ui/activity/fileactivity/ShareWithIntentHandler.kt new file mode 100644 index 000000000000..a88021f87798 --- /dev/null +++ b/app/src/main/java/com/owncloud/android/ui/activity/fileactivity/ShareWithIntentHandler.kt @@ -0,0 +1,38 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2026 Alper Ozturk + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +package com.owncloud.android.ui.activity.fileactivity + +import android.content.Intent +import com.nextcloud.utils.SnackbarUtil +import com.owncloud.android.R +import com.owncloud.android.lib.resources.shares.ShareType +import com.owncloud.android.providers.UsersAndGroupsSearchProvider +import com.owncloud.android.ui.activity.FileActivity + +class ShareWithIntentHandler( + private val activity: FileActivity, + private val onNewSharee: (shareeName: String, shareType: ShareType) -> Unit +) { + + fun handle(intent: Intent, remotePath: String) { + val data = intent.data ?: return + val shareWith = data.toString().substringAfterLast('/') + val shareType = UsersAndGroupsSearchProvider.getShareType(data.authority) + + val isAlreadyShared = activity.storageManager + .getSharesWithForAFile(remotePath, activity.account.name) + .any { shareeKey(it.shareType, it.shareWith) == shareeKey(shareType, shareWith) } + + if (isAlreadyShared) { + SnackbarUtil.show(activity, activity.getString(R.string.sharee_already_added_to_file)) + } else { + onNewSharee(shareWith, shareType) + } + } + + private fun shareeKey(shareType: ShareType?, shareWith: String?): String = "${shareType}_$shareWith" +} diff --git a/app/src/main/java/com/owncloud/android/ui/dialog/SendShareDialog.kt b/app/src/main/java/com/owncloud/android/ui/dialog/SendShareDialog.kt index f0e7cb45db41..16894e49cc63 100644 --- a/app/src/main/java/com/owncloud/android/ui/dialog/SendShareDialog.kt +++ b/app/src/main/java/com/owncloud/android/ui/dialog/SendShareDialog.kt @@ -144,8 +144,9 @@ class SendShareDialog : private fun shareByLink() { val fileOperationsHelper = (requireActivity() as FileActivity).fileOperationsHelper - if (file?.isSharedViaLink == true) { - fileOperationsHelper.getFileWithLink(file!!, viewThemeUtils) + val sharedFile = file + if (sharedFile?.isSharedViaLink == true) { + fileOperationsHelper.getFileWithLink(sharedFile) } else if (sharingPublicPasswordEnforced || sharingPublicAskForPassword) { // password enforced by server, request to the user before trying to create requestPasswordForShareViaLink() diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/ExtendedListFragment.kt b/app/src/main/java/com/owncloud/android/ui/fragment/ExtendedListFragment.kt index 5ed2c45e47a4..d4c3270b2986 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/ExtendedListFragment.kt +++ b/app/src/main/java/com/owncloud/android/ui/fragment/ExtendedListFragment.kt @@ -393,7 +393,7 @@ open class ExtendedListFragment : } private fun getEmptyListViewTextId(): Int { - val currentDir = getTypedActivity(FileDisplayActivity::class.java)?.currentDir + val currentDir = getTypedActivity(FileDisplayActivity::class.java)?.getCurrentDir() return if (currentDir?.canCreateFileAndFolder() == false) { R.string.extented_list_fragment_empty_list_message_no_write_permission } else { diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.kt b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.kt index 343811c31d9a..c1b1f6ccada5 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.kt +++ b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.kt @@ -63,6 +63,7 @@ import com.owncloud.android.lib.resources.status.OCCapability import com.owncloud.android.operations.RefreshFolderOperation import com.owncloud.android.providers.UsersAndGroupsSearchConfig import com.owncloud.android.ui.activity.FileActivity +import com.owncloud.android.ui.activity.fileactivity.ShareLinkHelper import com.owncloud.android.ui.adapter.ShareeListAdapter import com.owncloud.android.ui.adapter.ShareeListAdapterListener import com.owncloud.android.ui.asynctasks.RetrieveHoverCardAsyncTask @@ -550,11 +551,12 @@ class FileDetailSharingFragment : } if (publicShare.shareLink.isNullOrEmpty()) { - fileOperationsHelper?.getFileWithLink(file, viewThemeUtils) + fileOperationsHelper?.getFileWithLink(file) return } - FileActivity.showShareLinkDialog(fileActivity, file, publicShare.shareLink) + val activity = fileActivity ?: return + ShareLinkHelper.showShareLinkDialog(activity, file, publicShare.shareLink) } private fun refreshLegacyShareUiFromDb() { @@ -717,8 +719,10 @@ class FileDetailSharingFragment : } override fun sendLink(share: OCShare) { - if (file?.isSharedViaLink == true && !share.shareLink.isNullOrEmpty()) { - FileActivity.showShareLinkDialog(fileActivity, file, share.shareLink) + val currentFile = file + val activity = fileActivity + if (currentFile?.isSharedViaLink == true && !share.shareLink.isNullOrEmpty() && activity != null) { + ShareLinkHelper.showShareLinkDialog(activity, currentFile, share.shareLink) } else { showSendLinkTo(share) } @@ -736,7 +740,9 @@ class FileDetailSharingFragment : return } - file?.let { FileActivity.showShareLinkDialog(fileActivity, file, createInternalLink(user, it, capabilities)) } + val currentFile = file ?: return + val activity = fileActivity ?: return + ShareLinkHelper.showShareLinkDialog(activity, currentFile, createInternalLink(user, currentFile, capabilities)) } private fun OCCapability?.shouldAskForPublicLinkPassword(): Boolean = @@ -767,7 +773,7 @@ class FileDetailSharingFragment : } if (share.shareLink.isNullOrEmpty()) { - fileOperationsHelper?.getFileWithLink(file, viewThemeUtils) + fileOperationsHelper?.getFileWithLink(file) return } @@ -925,7 +931,7 @@ class FileDetailSharingFragment : // endregion interface OnEditShareListener { - fun editExistingShare(share: OCShare?, screenTypePermission: Int, isReshareShown: Boolean) + fun editExistingShare(share: OCShare, screenTypePermission: Int, isReshareShown: Boolean) fun onShareProcessClosed() } diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.kt b/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.kt index ba5b88d8ae13..365673c4d9f9 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.kt +++ b/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.kt @@ -402,7 +402,7 @@ open class OCFileListFragment : } mSwitchGridViewButton?.setOnClickListener { toggleGridAndListLayout() } - (activity as? FileDisplayActivity)?.let { it.updateActionBarTitleAndHomeButton(it.currentDir) } + (activity as? FileDisplayActivity)?.let { it.updateActionBarTitleAndHomeButton(it.getCurrentDir()) } } private fun toggleGridAndListLayout() { diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/albums/AlbumOperationListener.kt b/app/src/main/java/com/owncloud/android/ui/fragment/albums/AlbumOperationListener.kt index 823b3c356fd9..833da879c81a 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/albums/AlbumOperationListener.kt +++ b/app/src/main/java/com/owncloud/android/ui/fragment/albums/AlbumOperationListener.kt @@ -111,7 +111,7 @@ class AlbumOperationListener(private val activity: FileDisplayActivity) { private fun showUntrustedCertDialog(result: RemoteOperationResult<*>) { if (result.isSslRecoverableException) { activity.lastSslUntrustedServerResult = result - activity.showUntrustedCertDialog(activity.lastSslUntrustedServerResult) + activity.showUntrustedCertDialog(result) } } diff --git a/app/src/main/java/com/owncloud/android/ui/helpers/FileOperationsHelper.kt b/app/src/main/java/com/owncloud/android/ui/helpers/FileOperationsHelper.kt index 669150aab334..915c20c180cc 100755 --- a/app/src/main/java/com/owncloud/android/ui/helpers/FileOperationsHelper.kt +++ b/app/src/main/java/com/owncloud/android/ui/helpers/FileOperationsHelper.kt @@ -65,6 +65,7 @@ import com.owncloud.android.ui.activity.FileDisplayActivity import com.owncloud.android.ui.activity.RichDocumentsEditorWebView import com.owncloud.android.ui.activity.ShareActivity import com.owncloud.android.ui.activity.TextEditorWebView +import com.owncloud.android.ui.activity.fileactivity.ShareLinkHelper import com.owncloud.android.ui.dialog.SendFilesDialog import com.owncloud.android.ui.dialog.SendShareDialog import com.owncloud.android.ui.events.FavoriteEvent @@ -73,7 +74,6 @@ import com.owncloud.android.ui.events.SyncEventFinished import com.owncloud.android.utils.EncryptionUtils import com.owncloud.android.utils.FileStorageUtils import com.owncloud.android.utils.UriUtils -import com.owncloud.android.utils.theme.ViewThemeUtils import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import org.greenrobot.eventbus.EventBus @@ -387,12 +387,12 @@ class FileOperationsHelper( queueOperation(intent) } - fun getFileWithLink(file: OCFile, viewThemeUtils: ViewThemeUtils?) { + fun getFileWithLink(file: OCFile) { val shares = fileActivity.storageManager.getSharesByPathAndType(file.remotePath, ShareType.PUBLIC_LINK, "") when { shares.size == SINGLE_LINK_SIZE -> - FileActivity.copyAndShareFileLink(fileActivity, file, shares[0].shareLink, viewThemeUtils) + ShareLinkHelper.copyAndShareFileLink(fileActivity, file, shares[0].shareLink) fileActivity is FileDisplayActivity -> fileActivity.showDetails(file, SHARING_TAB) @@ -877,7 +877,8 @@ class FileOperationsHelper( } private fun queueOperation(intent: Intent) { - opIdWaitingFor = fileActivity.operationsServiceBinder.queueNewOperation(intent) + val binder = fileActivity.operationsServiceBinder ?: return + opIdWaitingFor = binder.queueNewOperation(intent) } private fun queueOperationAndShowWaitDialog(intent: Intent) { diff --git a/app/src/main/java/com/owncloud/android/ui/preview/media/PreviewMediaActivity.kt b/app/src/main/java/com/owncloud/android/ui/preview/media/PreviewMediaActivity.kt index e8c7df4eb155..0e40d9a31346 100644 --- a/app/src/main/java/com/owncloud/android/ui/preview/media/PreviewMediaActivity.kt +++ b/app/src/main/java/com/owncloud/android/ui/preview/media/PreviewMediaActivity.kt @@ -172,11 +172,11 @@ class PreviewMediaActivity : } } - override fun onNewIntent(intent: Intent?) { + override fun onNewIntent(intent: Intent) { super.onNewIntent(intent) // every later read goes through getIntent(), so the selection of the new intent would be lost otherwise - intent?.let { setIntent(it) } + setIntent(intent) initializeContent() } @@ -383,7 +383,7 @@ class PreviewMediaActivity : val isNewSelection = currentFile != file - setFile(currentFile) + file = currentFile showFile(currentFile, optionalUser.get(), isNewSelection) }