diff --git a/app/src/main/java/com/owncloud/android/ui/activity/UploadFilesActivity.java b/app/src/main/java/com/owncloud/android/ui/activity/UploadFilesActivity.java deleted file mode 100644 index 4b8307d1c1dd..000000000000 --- a/app/src/main/java/com/owncloud/android/ui/activity/UploadFilesActivity.java +++ /dev/null @@ -1,802 +0,0 @@ -/* - * Nextcloud - Android Client - * - * SPDX-FileCopyrightText: 2022 Álvaro Brey - * SPDX-FileCopyrightText: 2020-2022 Tobias Kaminsky - * SPDX-FileCopyrightText: 2020 Joris Bodin - * SPDX-FileCopyrightText: 2019 Chris Narkiewicz - * SPDX-FileCopyrightText: 2018 Andy Scherzinger - * SPDX-FileCopyrightText: 2015 ownCloud Inc. - * SPDX-FileCopyrightText: 2015 María Asensio Valverde - * SPDX-FileCopyrightText: 2012 David A. Velasco - * 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.annotation.SuppressLint; -import android.app.Activity; -import android.content.Intent; -import android.os.Bundle; -import android.os.Environment; -import android.view.Menu; -import android.view.MenuItem; -import android.view.View; -import android.view.View.OnClickListener; -import android.widget.AdapterView; -import android.widget.ArrayAdapter; -import android.widget.TextView; - -import com.nextcloud.android.common.ui.theme.utils.ColorRole; -import com.nextcloud.client.account.User; -import com.nextcloud.client.core.Clock; -import com.nextcloud.client.di.Injectable; -import com.nextcloud.client.jobs.upload.FileUploadHelper; -import com.nextcloud.client.jobs.upload.FileUploadWorker; -import com.nextcloud.client.preferences.AppPreferences; -import com.nextcloud.utils.extensions.ActivityExtensionsKt; -import com.nextcloud.utils.extensions.FileExtensionsKt; -import com.nextcloud.utils.extensions.SyncedFolderExtensionsKt; -import com.owncloud.android.R; -import com.owncloud.android.databinding.UploadFilesLayoutBinding; -import com.owncloud.android.datamodel.SyncedFolderProvider; -import com.owncloud.android.lib.common.utils.Log_OC; -import com.owncloud.android.ui.adapter.StoragePathAdapter; -import com.owncloud.android.ui.asynctasks.CheckAvailableSpaceTask; -import com.owncloud.android.ui.dialog.ConfirmationDialogFragment; -import com.owncloud.android.ui.dialog.ConfirmationDialogFragment.ConfirmationDialogFragmentListener; -import com.owncloud.android.ui.dialog.IndeterminateProgressDialog; -import com.owncloud.android.ui.dialog.LocalStoragePathPickerDialogFragment; -import com.owncloud.android.ui.dialog.SortingOrderDialogFragment; -import com.owncloud.android.ui.fragment.ExtendedListFragment; -import com.owncloud.android.ui.fragment.localfilelist.LocalFileListFragment; -import com.owncloud.android.ui.fragment.localfilelist.LocalFileListListener; -import com.owncloud.android.utils.FileSortOrder; -import com.owncloud.android.utils.FileUtil; -import com.owncloud.android.utils.PermissionUtil; - -import java.io.File; -import java.util.ArrayList; -import java.util.List; -import java.util.function.Consumer; - -import javax.inject.Inject; - -import androidx.activity.OnBackPressedCallback; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.appcompat.app.ActionBar; -import androidx.appcompat.widget.SearchView; -import androidx.core.view.MenuItemCompat; -import androidx.fragment.app.DialogFragment; -import androidx.fragment.app.FragmentManager; -import androidx.fragment.app.FragmentTransaction; -import kotlin.Unit; - -import static com.owncloud.android.ui.activity.FileActivity.EXTRA_USER; - -/** - * Displays local files and let the user choose what of them wants to upload to the current Nextcloud account. - */ -public class UploadFilesActivity extends DrawerActivity implements LocalFileListListener, - OnClickListener, ConfirmationDialogFragmentListener, SortingOrderDialogFragment.OnSortingOrderListener, - CheckAvailableSpaceTask.CheckAvailableSpaceListener, StoragePathAdapter.StoragePathAdapterListener, Injectable { - - private static final String KEY_ALL_SELECTED = UploadFilesActivity.class.getCanonicalName() + ".KEY_ALL_SELECTED"; - public final static String KEY_LOCAL_FOLDER_PICKER_MODE = UploadFilesActivity.class.getCanonicalName() + ".LOCAL_FOLDER_PICKER_MODE"; - public static final String LOCAL_BASE_PATH = UploadFilesActivity.class.getCanonicalName() + ".LOCAL_BASE_PATH"; - public static final String EXTRA_CHOSEN_FILES = UploadFilesActivity.class.getCanonicalName() + ".EXTRA_CHOSEN_FILES"; - public static final String KEY_DIRECTORY_PATH = UploadFilesActivity.class.getCanonicalName() + ".KEY_DIRECTORY_PATH"; - - private static final int SINGLE_DIR = 1; - public static final int RESULT_OK_AND_DELETE = 3; - public static final int RESULT_OK_AND_DO_NOTHING = 2; - public static final int RESULT_OK_AND_MOVE = RESULT_FIRST_USER; - public static final String REQUEST_CODE_KEY = "requestCode"; - private static final String ENCRYPTED_FOLDER_KEY = "encrypted_folder"; - - private static final String QUERY_TO_MOVE_DIALOG_TAG = "QUERY_TO_MOVE"; - private static final String SUB_FOLDER_WARNING_DIALOG_TAG = "SUB_FOLDER_WARNING_DIALOG"; - private static final String TAG = "UploadFilesActivity"; - private static final String WAIT_DIALOG_TAG = "WAIT"; - - @Inject AppPreferences preferences; - - @Inject - Clock clock; - - private Account mAccountOnCreation; - private ArrayAdapter mDirectories; - private boolean mLocalFolderPickerMode; - private boolean mSelectAll; - private DialogFragment mCurrentDialog; - private File mCurrentDir; - private int requestCode; - private LocalFileListFragment mFileListFragment; - private LocalStoragePathPickerDialogFragment dialog; - private Menu mOptionsMenu; - private SearchView mSearchView; - private UploadFilesLayoutBinding binding; - private boolean isWithinEncryptedFolder = false; - private String[] mChosenFilePaths; - - public LocalFileListFragment getFileListFragment() { - return mFileListFragment; - } - - /** - * Helper to launch the UploadFilesActivity for which you would like a result when it finished. Your - * onActivityResult() method will be called with the given requestCode. - * - * @param activity the activity which should call the upload activity for a result - * @param user the user for which the upload activity is called - * @param requestCode If >= 0, this code will be returned in onActivityResult() - */ - public static void startUploadActivityForResult(Activity activity, - User user, - int requestCode, - boolean isWithinEncryptedFolder) { - Intent action = new Intent(activity, UploadFilesActivity.class); - action.putExtra(EXTRA_USER, user); - action.putExtra(REQUEST_CODE_KEY, requestCode); - action.putExtra(ENCRYPTED_FOLDER_KEY, isWithinEncryptedFolder); - activity.startActivityForResult(action, requestCode); - } - - @Override - @SuppressLint("WrongViewCast") // wrong error on finding local_files_list - public void onCreate(Bundle savedInstanceState) { - Log_OC.d(TAG, "onCreate() start"); - super.onCreate(savedInstanceState); - - Bundle extras = getIntent().getExtras(); - if (extras != null) { - mLocalFolderPickerMode = extras.getBoolean(KEY_LOCAL_FOLDER_PICKER_MODE, false); - requestCode = (int) extras.get(REQUEST_CODE_KEY); - isWithinEncryptedFolder = extras.getBoolean(ENCRYPTED_FOLDER_KEY, false); - } - - if (savedInstanceState != null) { - mCurrentDir = new File(savedInstanceState.getString(KEY_DIRECTORY_PATH, - Environment.getExternalStorageDirectory().getAbsolutePath())); - mSelectAll = savedInstanceState.getBoolean(KEY_ALL_SELECTED, false); - isWithinEncryptedFolder = savedInstanceState.getBoolean(ENCRYPTED_FOLDER_KEY, false); - } else { - String lastUploadFrom = preferences.getUploadFromLocalLastPath(); - - if (!lastUploadFrom.isEmpty()) { - mCurrentDir = new File(lastUploadFrom); - - while (!mCurrentDir.exists()) { - mCurrentDir = mCurrentDir.getParentFile(); - } - } else { - mCurrentDir = Environment.getExternalStorageDirectory(); - } - } - - mAccountOnCreation = getAccount(); - - /// USER INTERFACE - - // Drop-down navigation - mDirectories = new ArrayAdapter<>(this, android.R.layout.simple_spinner_item); - mDirectories.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); - fillDirectoryDropdown(); - - // Inflate and set the layout view - binding = UploadFilesLayoutBinding.inflate(getLayoutInflater()); - setContentView(binding.getRoot()); - - if (mLocalFolderPickerMode) { - binding.uploadOptions.setVisibility(View.GONE); - binding.uploadFilesBtnUpload.setText(R.string.uploader_btn_alternative_text); - } - - mFileListFragment = (LocalFileListFragment) getSupportFragmentManager().findFragmentByTag("local_files_list"); - - // Set input controllers - viewThemeUtils.material.colorMaterialButtonPrimaryOutlined(binding.uploadFilesBtnCancel); - binding.uploadFilesBtnCancel.setOnClickListener(this); - - viewThemeUtils.material.colorMaterialButtonPrimaryFilled(binding.uploadFilesBtnUpload); - binding.uploadFilesBtnUpload.setOnClickListener(this); - binding.uploadFilesBtnUpload.setEnabled(mLocalFolderPickerMode); - - int localBehaviour = preferences.getUploaderBehaviour(); - - // file upload spinner - List behaviours = new ArrayList<>(); - behaviours.add(getString(R.string.uploader_upload_files_behaviour_move_to_nextcloud_folder, - themeUtils.getDefaultDisplayNameForRootFolder(this))); - behaviours.add(getString(R.string.uploader_upload_files_behaviour_only_upload)); - behaviours.add(getString(R.string.uploader_upload_files_behaviour_upload_and_delete_from_source)); - - ArrayAdapter behaviourAdapter = new ArrayAdapter<>(this, android.R.layout.simple_spinner_item, - behaviours); - behaviourAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); - binding.uploadFilesSpinnerBehaviour.setAdapter(behaviourAdapter); - binding.uploadFilesSpinnerBehaviour.setSelection(localBehaviour); - - // setup the toolbar - setupToolbar(); - binding.uploadFilesToolbar.sortListButtonGroup.setVisibility(View.VISIBLE); - binding.uploadFilesToolbar.switchGridViewButton.setVisibility(View.GONE); - - // Action bar setup - ActionBar actionBar = getSupportActionBar(); - - if (actionBar != null) { - actionBar.setHomeButtonEnabled(true); // mandatory since Android ICS, according to the official documentation - actionBar.setDisplayHomeAsUpEnabled(mCurrentDir != null); - actionBar.setDisplayShowTitleEnabled(false); - - viewThemeUtils.files.themeActionBar(this, actionBar); - } - - showToolbarSpinner(); - mToolbarSpinner.setAdapter(mDirectories); - mToolbarSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { - @Override - public void onItemSelected(AdapterView parent, View view, int position, long id) { - int i = position; - while (i-- != 0) { - getOnBackPressedDispatcher().onBackPressed(); - } - // the next operation triggers a new call to this method, but it's necessary to - // ensure that the name exposed in the action bar is the current directory when the - // user selected it in the navigation list - if (position != 0) { - mToolbarSpinner.setSelection(0); - } - } - - @Override - public void onNothingSelected(AdapterView parent) { - // no action - } - }); - - // wait dialog - if (mCurrentDialog != null) { - mCurrentDialog.dismiss(); - mCurrentDialog = null; - } - - checkWritableFolder(mCurrentDir); - - getOnBackPressedDispatcher().addCallback(this, onBackPressedCallback); - - Log_OC.d(TAG, "onCreate() end"); - } - - public void showToolbarSpinner() { - mToolbarSpinner.setVisibility(View.VISIBLE); - } - - private void fillDirectoryDropdown() { - File currentDir = mCurrentDir; - while (currentDir != null && currentDir.getParentFile() != null) { - mDirectories.add(currentDir.getName()); - currentDir = currentDir.getParentFile(); - } - mDirectories.add(File.separator); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - mOptionsMenu = menu; - getMenuInflater().inflate(R.menu.activity_upload_files, menu); - - if (!mLocalFolderPickerMode) { - MenuItem selectAll = menu.findItem(R.id.action_select_all); - setSelectAllMenuItem(selectAll, mSelectAll); - } - - final MenuItem item = menu.findItem(R.id.action_search); - mSearchView = (SearchView) MenuItemCompat.getActionView(item); - viewThemeUtils.androidx.themeToolbarSearchView(mSearchView); - - mSearchView.setOnSearchClickListener(v -> mToolbarSpinner.setVisibility(View.GONE)); - - MenuItem chooseStoragePathItem = menu.findItem(R.id.action_choose_storage_path); - if (chooseStoragePathItem != null) { - chooseStoragePathItem.setVisible(PermissionUtil.checkStoragePermission(this)); - - final var chooseStoragePathDrawable = chooseStoragePathItem.getIcon(); - if (chooseStoragePathDrawable != null) { - viewThemeUtils.platform.tintDrawable(this, chooseStoragePathDrawable, ColorRole.ON_SURFACE); - } - } - - return super.onCreateOptionsMenu(menu); - } - - private static final String rootDir = "/storage/emulated/0"; - - private boolean isRoot() { - if (mCurrentDir == null) { - return false; - } - - return mCurrentDir.getAbsolutePath().equals(rootDir); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - int itemId = item.getItemId(); - - if (itemId == android.R.id.home) { - handleHomePressed(); - return true; - } - - if (itemId == R.id.action_select_all) { - mSelectAll = !item.isChecked(); - item.setChecked(mSelectAll); - mFileListFragment.selectAllFiles(mSelectAll); - setSelectAllMenuItem(item, mSelectAll); - return true; - } - - if (itemId == R.id.action_choose_storage_path) { - showLocalStoragePathPickerDialog(); - return true; - } - - return super.onOptionsItemSelected(item); - } - - private void cancelAndFinish() { - setResult(RESULT_CANCELED); - finish(); - } - - private void handleHomePressed() { - boolean root = isRoot(); - boolean hasPermission = PermissionUtil.checkStoragePermission(this); - - if (root && !hasPermission) { - cancelAndFinish(); - return; - } - - if (mCurrentDir == null || mCurrentDir.getParentFile() == null) { - return; - } - - if (root) { - cancelAndFinish(); - } else { - getOnBackPressedDispatcher().onBackPressed(); - } - } - - private void showLocalStoragePathPickerDialog() { - if (!PermissionUtil.checkStoragePermission(this)) { - cancelAndFinish(); - return; - } - - FragmentManager fm = getSupportFragmentManager(); - FragmentTransaction ft = fm.beginTransaction(); - ft.addToBackStack(null); - dialog = LocalStoragePathPickerDialogFragment.newInstance(); - dialog.show(ft, LocalStoragePathPickerDialogFragment.LOCAL_STORAGE_PATH_PICKER_FRAGMENT); - } - - @Override - public void onSortingOrderChosen(FileSortOrder selection) { - preferences.setSortOrder(FileSortOrder.Type.localFileListView, selection); - mFileListFragment.sortFiles(selection); - } - - private boolean isSearchOpen() { - if (mSearchView == null) { - return false; - } else { - View mSearchEditFrame = mSearchView.findViewById(androidx.appcompat.R.id.search_edit_frame); - return mSearchEditFrame != null && mSearchEditFrame.getVisibility() == View.VISIBLE; - } - } - - private final OnBackPressedCallback onBackPressedCallback = new OnBackPressedCallback(true) { - @Override - public void handleOnBackPressed() { - if (isSearchOpen() && mSearchView != null) { - mSearchView.setQuery("", false); - mFileListFragment.onClose(); - mSearchView.onActionViewCollapsed(); - setDrawerIndicatorEnabled(isDrawerIndicatorAvailable()); - } else { - if (mDirectories.getCount() <= SINGLE_DIR) { - finish(); - return; - } - - File parentFolder = mCurrentDir.getParentFile(); - if (parentFolder != null && !parentFolder.canRead()) { - return; - } - - popDirname(); - mFileListFragment.onNavigateUp(); - mCurrentDir = mFileListFragment.getCurrentDirectory(); - checkWritableFolder(mCurrentDir); - - if (mCurrentDir.getParentFile() == null) { - ActionBar actionBar = getSupportActionBar(); - if (actionBar != null) { - actionBar.setDisplayHomeAsUpEnabled(false); - } - } - - // invalidate checked state when navigating directories - if (!mLocalFolderPickerMode) { - setSelectAllMenuItem(mOptionsMenu.findItem(R.id.action_select_all), false); - } - } - } - }; - - @Override - protected void onSaveInstanceState(@NonNull Bundle outState) { - // responsibility of restore is preferred in onCreate() before than in - // onRestoreInstanceState when there are Fragments involved - FileExtensionsKt.logFileSize(mCurrentDir, TAG); - super.onSaveInstanceState(outState); - outState.putString(UploadFilesActivity.KEY_DIRECTORY_PATH, mCurrentDir.getAbsolutePath()); - if (mOptionsMenu != null && mOptionsMenu.findItem(R.id.action_select_all) != null) { - outState.putBoolean(UploadFilesActivity.KEY_ALL_SELECTED, mOptionsMenu.findItem(R.id.action_select_all).isChecked()); - } else { - outState.putBoolean(UploadFilesActivity.KEY_ALL_SELECTED, false); - } - Log_OC.d(TAG, "onSaveInstanceState() end"); - } - - /** - * Pushes a directory to the drop down list - * - * @param directory to push - * @throws IllegalArgumentException If the {@link File#isDirectory()} returns false. - */ - public void pushDirname(File directory) { - if (!directory.isDirectory()) { - throw new IllegalArgumentException("Only directories may be pushed!"); - } - mDirectories.insert(directory.getName(), 0); - mCurrentDir = directory; - checkWritableFolder(mCurrentDir); - } - - /** - * Pops a directory name from the drop down list - */ - public void popDirname() { - mDirectories.remove(mDirectories.getItem(0)); - } - - private void updateUploadButtonActive() { - final boolean anySelected = mFileListFragment.getCheckedFilesCount() > 0; - binding.uploadFilesBtnUpload.setEnabled(anySelected || mLocalFolderPickerMode); - } - - private void setSelectAllMenuItem(MenuItem selectAll, boolean checked) { - if (selectAll != null) { - selectAll.setChecked(checked); - if (checked) { - selectAll.setIcon(R.drawable.ic_select_none); - } else { - selectAll.setIcon( - viewThemeUtils.platform.tintPrimaryDrawable(this, R.drawable.ic_select_all)); - } - updateUploadButtonActive(); - } - } - - @Override - public void onCheckAvailableSpaceStart() { - if (requestCode == FileDisplayActivity.REQUEST_CODE__SELECT_FILES_FROM_FILE_SYSTEM) { - mCurrentDialog = IndeterminateProgressDialog.newInstance(R.string.wait_a_moment, false); - mCurrentDialog.show(getSupportFragmentManager(), WAIT_DIALOG_TAG); - } - } - - /** - * Updates the activity UI after the check of space is done. If there is not space enough. shows a new dialog to - * query the user if wants to move the files instead of copy them. - * - * @param hasEnoughSpaceAvailable 'True' when there is space enough to copy all the selected files. - */ - @Override - public void onCheckAvailableSpaceFinish(boolean hasEnoughSpaceAvailable, String... filesToUpload) { - if (mCurrentDialog != null && ActivityExtensionsKt.isDialogFragmentReady(this, mCurrentDialog)) { - mCurrentDialog.dismiss(); - mCurrentDialog = null; - } - - if (hasEnoughSpaceAvailable) { - // return the list of files (success) - Intent data = new Intent(); - - if (requestCode == FileDisplayActivity.REQUEST_CODE__UPLOAD_FROM_CAMERA) { - data.putExtra(EXTRA_CHOSEN_FILES, new String[]{filesToUpload[0]}); - setResult(RESULT_OK_AND_DELETE, data); - - preferences.setUploaderBehaviour(FileUploadWorker.LOCAL_BEHAVIOUR_DELETE); - } else { - data.putExtra(EXTRA_CHOSEN_FILES, filesToUpload); - data.putExtra(LOCAL_BASE_PATH, mCurrentDir.getAbsolutePath()); - - // set result code - switch (binding.uploadFilesSpinnerBehaviour.getSelectedItemPosition()) { - case 0: // move to nextcloud folder - setResult(RESULT_OK_AND_MOVE, data); - break; - - case 1: // only upload - setResult(RESULT_OK_AND_DO_NOTHING, data); - break; - - case 2: // upload and delete from source - setResult(RESULT_OK_AND_DELETE, data); - break; - - default: - // do nothing - break; - } - - // store behaviour - preferences.setUploaderBehaviour(binding.uploadFilesSpinnerBehaviour.getSelectedItemPosition()); - } - - finish(); - } else { - // show a dialog to query the user if wants to move the selected files - // to the ownCloud folder instead of copying - String[] args = { getString(R.string.app_name) }; - ConfirmationDialogFragment dialog = ConfirmationDialogFragment.newInstance( - R.string.upload_query_move_foreign_files, args, 0, R.string.common_yes, R.string.common_no, -1); - dialog.setOnConfirmationListener(this); - dialog.show(getSupportFragmentManager(), QUERY_TO_MOVE_DIALOG_TAG); - } - } - - @Override - public void chosenPath(String path) { - if (getListOfFilesFragment() instanceof LocalFileListFragment) { - File file = new File(path); - ((LocalFileListFragment) getListOfFilesFragment()).listDirectory(file); - onDirectoryClick(file); - - mCurrentDir = new File(path); - mDirectories.clear(); - - fillDirectoryDropdown(); - } - } - - /** - * {@inheritDoc} - */ - @Override - public void onDirectoryClick(File directory) { - if (!mLocalFolderPickerMode) { - // invalidate checked state when navigating directories - MenuItem selectAll = mOptionsMenu.findItem(R.id.action_select_all); - setSelectAllMenuItem(selectAll, false); - } - - pushDirname(directory); - ActionBar actionBar = getSupportActionBar(); - actionBar.setDisplayHomeAsUpEnabled(true); - } - - private void checkWritableFolder(File folder) { - FileUtil.INSTANCE.isFolderWritable(folder, getLifecycle(), canWriteIntoFolder -> { - binding.uploadFilesSpinnerBehaviour.setEnabled(canWriteIntoFolder); - - TextView textView = findViewById(R.id.upload_files_upload_files_behaviour_text); - - if (canWriteIntoFolder) { - textView.setText(getString(R.string.uploader_upload_files_behaviour)); - int localBehaviour = preferences.getUploaderBehaviour(); - binding.uploadFilesSpinnerBehaviour.setSelection(localBehaviour); - } else { - binding.uploadFilesSpinnerBehaviour.setSelection(1); - textView.setText(new StringBuilder().append(getString(R.string.uploader_upload_files_behaviour)) - .append(' ') - .append(getString(R.string.uploader_upload_files_behaviour_not_writable)) - .toString()); - } - return Unit.INSTANCE; - }); - } - - /** - * {@inheritDoc} - */ - @Override - public void onFileClick(File file) { - updateUploadButtonActive(); - - boolean selectAll = mFileListFragment.getCheckedFilesCount() == mFileListFragment.getFilesCount(); - setSelectAllMenuItem(mOptionsMenu.findItem(R.id.action_select_all), selectAll); - } - - /** - * {@inheritDoc} - */ - @Override - public File getInitialDirectory() { - return mCurrentDir; - } - - /** - * {@inheritDoc} - */ - @Override - public boolean isFolderPickerMode() { - return mLocalFolderPickerMode; - } - - @Override - public boolean isWithinEncryptedFolder() { - return isWithinEncryptedFolder; - } - - private boolean isGivenLocalPathHasEnabledParent() { - if (mCurrentDir == null) { - return false; - } - - final var chosenPath = mCurrentDir.getPath(); - final var syncedFolderProvider = new SyncedFolderProvider(getContentResolver(), preferences, clock); - final var syncedFolders = syncedFolderProvider.getSyncedFolders(); - return SyncedFolderExtensionsKt.hasEnabledParent(syncedFolders, chosenPath); - } - - /** - * Performs corresponding action when user presses 'Cancel' or 'Upload' button - *

- * TODO Make here the real request to the Upload service ; will require to receive the account and target folder - * where the upload must be done in the received intent. - */ - @Override - public void onClick(View v) { - if (v.getId() == R.id.upload_files_btn_cancel) { - cancelAndFinish(); - } else if (v.getId() == R.id.upload_files_btn_upload && PermissionUtil.checkStoragePermission(this)) { - if (mCurrentDir != null) { - preferences.setUploadFromLocalLastPath(mCurrentDir.getAbsolutePath()); - } - if (mLocalFolderPickerMode) { - Intent data = new Intent(); - if (mCurrentDir != null) { - data.putExtra(EXTRA_CHOSEN_FILES, mCurrentDir.getAbsolutePath()); - } - setResult(RESULT_OK, data); - - if (isGivenLocalPathHasEnabledParent()) { - showSubFolderWarningDialog(); - } else { - finish(); - } - } else { - collectChosenFiles(chosenFiles -> { - boolean isPositionZero = (binding.uploadFilesSpinnerBehaviour.getSelectedItemPosition() == 0); - new CheckAvailableSpaceTask(this, chosenFiles).execute(isPositionZero); - }); - } - } - } - - private void collectChosenFiles(Consumer onCollected) { - mFileListFragment.collectCheckedFilePaths(chosenFiles -> { - if (chosenFiles.length > FileUploadHelper.MAX_FILE_COUNT) { - FileUploadHelper.Companion.instance().showFileUploadLimitMessage(this); - return Unit.INSTANCE; - } - - mChosenFilePaths = chosenFiles; - onCollected.accept(chosenFiles); - return Unit.INSTANCE; - }); - } - - private void showSubFolderWarningDialog() { - final var dialog = ConfirmationDialogFragment.newInstance( - R.string.auto_upload_sub_folder_warning, - null, - R.string.sync_duplication, - R.drawable.ic_info, - R.string.sync_anyway, - R.string.common_cancel, - -1); - - dialog.setOnConfirmationListener(new ConfirmationDialogFragmentListener() { - @Override - public void onConfirmation(@Nullable String callerTag) { - finish(); - } - - @Override - public void onNeutral(@Nullable String callerTag) { - - } - - @Override - public void onCancel(@Nullable String callerTag) { - - } - }); - - final var isDialogFragmentReady = ActivityExtensionsKt.isDialogFragmentReady(this, dialog); - if (isDialogFragmentReady) { - dialog.show(getSupportFragmentManager(), SUB_FOLDER_WARNING_DIALOG_TAG); - } - } - - @Override - public void onConfirmation(String callerTag) { - Log_OC.d(TAG, "Positive button in dialog was clicked; dialog tag is " + callerTag); - - if (QUERY_TO_MOVE_DIALOG_TAG.equals(callerTag) && mChosenFilePaths != null) { - // return the list of selected files to the caller activity (success), - // signaling that they should be moved to the ownCloud folder, instead of copied - Intent data = new Intent(); - data.putExtra(EXTRA_CHOSEN_FILES, mChosenFilePaths); - data.putExtra(LOCAL_BASE_PATH, mCurrentDir.getAbsolutePath()); - setResult(RESULT_OK_AND_MOVE, data); - finish(); - } - } - - @Override - public void onNeutral(String callerTag) { - Log_OC.d(TAG, "Phantom neutral button in dialog was clicked; dialog tag is " + callerTag); - } - - @Override - public void onCancel(String callerTag) { - /// nothing to do; don't finish, let the user change the selection - Log_OC.d(TAG, "Negative button in dialog was clicked; dialog tag is " + callerTag); - } - - @Override - protected void onStart() { - super.onStart(); - final Account account = getAccount(); - if (mAccountOnCreation == null || !mAccountOnCreation.equals(account)) { - cancelAndFinish(); - } - } - - private boolean isGridView() { - return getListOfFilesFragment().isGridEnabled(); - } - - private ExtendedListFragment getListOfFilesFragment() { - if (mFileListFragment == null) { - Log_OC.e(TAG, "Access to unexisting list of files fragment!!"); - } - - return mFileListFragment; - } - - @Override - protected void onStop() { - if (dialog != null) { - dialog.dismissAllowingStateLoss(); - } - - super.onStop(); - } - - public void setupStoragePermissionWarningBanner() { - if (getListOfFilesFragment() instanceof LocalFileListFragment fragment) { - fragment.setupStoragePermissionWarningBanner(); - } - } -} diff --git a/app/src/main/java/com/owncloud/android/ui/activity/UploadFilesActivity.kt b/app/src/main/java/com/owncloud/android/ui/activity/UploadFilesActivity.kt new file mode 100644 index 000000000000..a951b9304fe9 --- /dev/null +++ b/app/src/main/java/com/owncloud/android/ui/activity/UploadFilesActivity.kt @@ -0,0 +1,514 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2026 Alper Ozturk + * SPDX-FileCopyrightText: 2022 Álvaro Brey + * SPDX-FileCopyrightText: 2020-2022 Tobias Kaminsky + * SPDX-FileCopyrightText: 2020 Joris Bodin + * SPDX-FileCopyrightText: 2019 Chris Narkiewicz + * SPDX-FileCopyrightText: 2018 Andy Scherzinger + * SPDX-FileCopyrightText: 2015 ownCloud Inc. + * SPDX-FileCopyrightText: 2015 María Asensio Valverde + * SPDX-FileCopyrightText: 2012 David A. Velasco + * 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.annotation.SuppressLint +import android.app.Activity +import android.content.Intent +import android.os.Bundle +import android.os.Environment +import android.view.Menu +import android.view.MenuInflater +import android.view.MenuItem +import android.view.View +import android.widget.AdapterView +import android.widget.ArrayAdapter +import androidx.activity.OnBackPressedCallback +import androidx.appcompat.widget.SearchView +import androidx.core.view.MenuProvider +import com.nextcloud.android.common.ui.theme.utils.ColorRole +import com.nextcloud.client.account.User +import com.nextcloud.client.core.Clock +import com.nextcloud.client.di.Injectable +import com.nextcloud.client.jobs.upload.FileUploadHelper +import com.nextcloud.client.jobs.upload.FileUploadWorker +import com.nextcloud.utils.extensions.hasEnabledParent +import com.owncloud.android.R +import com.owncloud.android.databinding.UploadFilesLayoutBinding +import com.owncloud.android.datamodel.SyncedFolderProvider +import com.owncloud.android.lib.common.utils.Log_OC +import com.owncloud.android.ui.activity.uploadfiles.UploadBehaviourSelector +import com.owncloud.android.ui.activity.uploadfiles.UploadFilesDialogs +import com.owncloud.android.ui.activity.uploadfiles.UploadFilesDialogs.Companion.QUERY_TO_MOVE_DIALOG_TAG +import com.owncloud.android.ui.adapter.StoragePathAdapter.StoragePathAdapterListener +import com.owncloud.android.ui.asynctasks.CheckAvailableSpaceTask +import com.owncloud.android.ui.asynctasks.CheckAvailableSpaceTask.CheckAvailableSpaceListener +import com.owncloud.android.ui.dialog.ConfirmationDialogFragment.ConfirmationDialogFragmentListener +import com.owncloud.android.ui.dialog.SortingOrderDialogFragment.OnSortingOrderListener +import com.owncloud.android.ui.fragment.localfilelist.LocalFileListFragment +import com.owncloud.android.ui.fragment.localfilelist.LocalFileListListener +import com.owncloud.android.utils.FileSortOrder +import com.owncloud.android.utils.PermissionUtil.checkStoragePermission +import java.io.File +import javax.inject.Inject + +class UploadFilesActivity : + DrawerActivity(), + LocalFileListListener, + View.OnClickListener, + ConfirmationDialogFragmentListener, + OnSortingOrderListener, + CheckAvailableSpaceListener, + StoragePathAdapterListener, + Injectable { + + @Inject + lateinit var clock: Clock + + lateinit var fileListFragment: LocalFileListFragment + private set + + override var isFolderPickerMode = false + private set + + override var isWithinEncryptedFolder = false + private set + + override val initialDirectory: File? + get() = currentDir + + private lateinit var binding: UploadFilesLayoutBinding + private lateinit var directories: ArrayAdapter + private lateinit var uploadBehaviour: UploadBehaviourSelector + private val dialogs = UploadFilesDialogs(this) + + private var currentDir: File? = null + private var accountOnCreation: Account? = null + private var requestCode = 0 + private var isAllSelected = false + private var optionsMenu: Menu? = null + private var searchView: SearchView? = null + private var chosenFilePaths: Array? = null + + private val isRoot: Boolean + get() = currentDir?.absolutePath == ROOT_DIR + + private val isSearchOpen: Boolean + get() = searchView?.findViewById(androidx.appcompat.R.id.search_edit_frame)?.visibility == View.VISIBLE + + private val selectAllMenuItem: MenuItem? + get() = optionsMenu?.findItem(R.id.action_select_all) + + private val isGivenLocalPathHasEnabledParent: Boolean + get() { + val chosenPath = currentDir?.path ?: return false + return SyncedFolderProvider(contentResolver, preferences, clock).syncedFolders.hasEnabledParent(chosenPath) + } + + private val onBackPressedCallback = object : OnBackPressedCallback(true) { + override fun handleOnBackPressed() { + if (isSearchOpen) closeSearch() else navigateToParentDirectory() + } + } + + private val menuProvider = object : MenuProvider { + override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) { + optionsMenu = menu + menuInflater.inflate(R.menu.activity_upload_files, menu) + + if (!isFolderPickerMode) { + setSelectAllMenuItem(menu.findItem(R.id.action_select_all), isAllSelected) + } + + searchView = (menu.findItem(R.id.action_search).actionView as SearchView).also { + viewThemeUtils.androidx.themeToolbarSearchView(it) + it.setOnSearchClickListener { mToolbarSpinner.visibility = View.GONE } + } + + menu.findItem(R.id.action_choose_storage_path)?.let { item -> + item.isVisible = checkStoragePermission(this@UploadFilesActivity) + item.icon?.let { + viewThemeUtils.platform.tintDrawable(this@UploadFilesActivity, it, ColorRole.ON_SURFACE) + } + } + } + + override fun onMenuItemSelected(menuItem: MenuItem): Boolean = when (menuItem.itemId) { + android.R.id.home -> { + handleHomePressed() + true + } + + R.id.action_select_all -> { + isAllSelected = !menuItem.isChecked + menuItem.isChecked = isAllSelected + fileListFragment.selectAllFiles(isAllSelected) + setSelectAllMenuItem(menuItem, isAllSelected) + true + } + + R.id.action_choose_storage_path -> { + if (checkStoragePermission(this@UploadFilesActivity)) { + dialogs.showStoragePathPicker() + } else { + cancelAndFinish() + } + true + } + + else -> false + } + } + + @SuppressLint("WrongViewCast") // wrong error on finding local_files_list + public override fun onCreate(savedInstanceState: Bundle?) { + Log_OC.d(TAG, "onCreate() start") + super.onCreate(savedInstanceState) + restoreState(savedInstanceState) + accountOnCreation = account + + directories = ArrayAdapter(this, android.R.layout.simple_spinner_item).apply { + setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) + addDirectoryHierarchy(currentDir) + } + + binding = UploadFilesLayoutBinding.inflate(layoutInflater) + setContentView(binding.root) + fileListFragment = supportFragmentManager.findFragmentByTag(LOCAL_FILES_LIST_TAG) as LocalFileListFragment + + setupButtons() + uploadBehaviour = UploadBehaviourSelector(this, binding, preferences).apply { + setup(themeUtils.getDefaultDisplayNameForRootFolder(this@UploadFilesActivity)) + } + setupToolbar() + setupActionBar() + setupToolbarSpinner() + uploadBehaviour.checkWritableFolder(currentDir) + addMenuProvider(menuProvider, this) + onBackPressedDispatcher.addCallback(this, onBackPressedCallback) + + Log_OC.d(TAG, "onCreate() end") + } + + private fun restoreState(savedInstanceState: Bundle?) { + intent.extras?.let { extras -> + isFolderPickerMode = extras.getBoolean(KEY_LOCAL_FOLDER_PICKER_MODE, false) + requestCode = extras.getInt(REQUEST_CODE_KEY) + isWithinEncryptedFolder = extras.getBoolean(ENCRYPTED_FOLDER_KEY, false) + } + + if (savedInstanceState != null) { + val defaultPath = Environment.getExternalStorageDirectory().absolutePath + currentDir = File(savedInstanceState.getString(KEY_DIRECTORY_PATH, defaultPath)) + isAllSelected = savedInstanceState.getBoolean(KEY_ALL_SELECTED, false) + isWithinEncryptedFolder = savedInstanceState.getBoolean(ENCRYPTED_FOLDER_KEY, false) + return + } + + val lastUploadFrom = preferences.uploadFromLocalLastPath + currentDir = if (lastUploadFrom.isEmpty()) { + Environment.getExternalStorageDirectory() + } else { + generateSequence(File(lastUploadFrom)) { it.parentFile }.first { it.exists() } + } + } + + private fun setupButtons() { + if (isFolderPickerMode) { + binding.uploadOptions.visibility = View.GONE + binding.uploadFilesBtnUpload.setText(R.string.uploader_btn_alternative_text) + } + + viewThemeUtils.material.colorMaterialButtonPrimaryOutlined(binding.uploadFilesBtnCancel) + binding.uploadFilesBtnCancel.setOnClickListener(this) + + viewThemeUtils.material.colorMaterialButtonPrimaryFilled(binding.uploadFilesBtnUpload) + binding.uploadFilesBtnUpload.setOnClickListener(this) + binding.uploadFilesBtnUpload.isEnabled = isFolderPickerMode + } + + private fun setupActionBar() { + binding.uploadFilesToolbar.sortListButtonGroup.visibility = View.VISIBLE + binding.uploadFilesToolbar.switchGridViewButton.visibility = View.GONE + + supportActionBar?.let { actionBar -> + actionBar.setHomeButtonEnabled(true) + actionBar.setDisplayHomeAsUpEnabled(currentDir != null) + actionBar.setDisplayShowTitleEnabled(false) + viewThemeUtils.files.themeActionBar(this, actionBar) + } + } + + private fun setupToolbarSpinner() { + mToolbarSpinner.visibility = View.VISIBLE + mToolbarSpinner.adapter = directories + mToolbarSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { + override fun onItemSelected(parent: AdapterView<*>, view: View, position: Int, id: Long) { + repeat(position) { onBackPressedDispatcher.onBackPressed() } + if (position != 0) { + mToolbarSpinner.setSelection(0) + } + } + + override fun onNothingSelected(parent: AdapterView<*>) = Unit + } + } + + private fun cancelAndFinish() { + setResult(RESULT_CANCELED) + finish() + } + + private fun handleHomePressed() { + val isRoot = isRoot + if (isRoot && !checkStoragePermission(this)) { + cancelAndFinish() + return + } + + if (currentDir?.parentFile == null) return + + if (isRoot) cancelAndFinish() else onBackPressedDispatcher.onBackPressed() + } + + override fun onSortingOrderChosen(selection: FileSortOrder?) { + val sortOrder = selection ?: return + preferences.setSortOrder(FileSortOrder.Type.localFileListView, sortOrder) + fileListFragment.sortFiles(sortOrder) + } + + private fun closeSearch() { + val searchView = searchView ?: return + searchView.setQuery("", false) + fileListFragment.onClose() + searchView.onActionViewCollapsed() + setDrawerIndicatorEnabled(isDrawerIndicatorAvailable) + } + + private fun navigateToParentDirectory() { + if (directories.count <= SINGLE_DIR) { + finish() + return + } + + val isParentUnreadable = currentDir?.parentFile?.canRead() == false + if (currentDir == null || isParentUnreadable) return + + directories.remove(directories.getItem(0)) + fileListFragment.onNavigateUp() + currentDir = fileListFragment.currentDirectory + uploadBehaviour.checkWritableFolder(currentDir) + + if (currentDir?.parentFile == null) { + supportActionBar?.setDisplayHomeAsUpEnabled(false) + } + + if (!isFolderPickerMode) { + setSelectAllMenuItem(selectAllMenuItem, false) + } + } + + override fun onSaveInstanceState(outState: Bundle) { + super.onSaveInstanceState(outState) + currentDir?.let { outState.putString(KEY_DIRECTORY_PATH, it.absolutePath) } + outState.putBoolean(KEY_ALL_SELECTED, optionsMenu?.findItem(R.id.action_select_all)?.isChecked == true) + Log_OC.d(TAG, "onSaveInstanceState() end") + } + + private fun updateUploadButtonActive() { + binding.uploadFilesBtnUpload.isEnabled = fileListFragment.checkedFilesCount > 0 || isFolderPickerMode + } + + private fun setSelectAllMenuItem(selectAll: MenuItem?, checked: Boolean) { + val item = selectAll ?: return + item.isChecked = checked + if (checked) { + item.setIcon(R.drawable.ic_select_none) + } else { + item.icon = viewThemeUtils.platform.tintDrawable(this, R.drawable.ic_select_all, ColorRole.PRIMARY) + } + updateUploadButtonActive() + } + + override fun onCheckAvailableSpaceStart() { + if (requestCode == FileDisplayActivity.REQUEST_CODE__SELECT_FILES_FROM_FILE_SYSTEM) { + dialogs.showWaitDialog() + } + } + + override fun onCheckAvailableSpaceFinish(hasEnoughSpaceAvailable: Boolean, vararg filesToUpload: String) { + dialogs.dismissWaitDialog() + + if (!hasEnoughSpaceAvailable) { + dialogs.showQueryToMoveDialog(this) + return + } + + val data = Intent() + if (requestCode == FileDisplayActivity.REQUEST_CODE__UPLOAD_FROM_CAMERA) { + data.putExtra(EXTRA_CHOSEN_FILES, arrayOf(filesToUpload[0])) + setResult(RESULT_OK_AND_DELETE, data) + preferences.uploaderBehaviour = FileUploadWorker.LOCAL_BEHAVIOUR_DELETE + } else { + data.putExtra(EXTRA_CHOSEN_FILES, filesToUpload) + data.putExtra(LOCAL_BASE_PATH, currentDir?.absolutePath) + uploadBehaviour.resultCode?.let { setResult(it, data) } + uploadBehaviour.saveSelection() + } + finish() + } + + override fun chosenPath(path: String) { + val directory = File(path) + fileListFragment.listDirectory(directory) + onDirectoryClick(directory) + + currentDir = directory + directories.clear() + directories.addDirectoryHierarchy(currentDir) + } + + override fun onDirectoryClick(directory: File?) { + if (directory == null) return + + if (!isFolderPickerMode) { + setSelectAllMenuItem(selectAllMenuItem, false) + } + + require(directory.isDirectory) { "Only directories may be pushed!" } + directories.insert(directory.name, 0) + currentDir = directory + uploadBehaviour.checkWritableFolder(currentDir) + + supportActionBar?.setDisplayHomeAsUpEnabled(true) + } + + override fun onFileClick(file: File?) { + updateUploadButtonActive() + val isAllChecked = fileListFragment.checkedFilesCount == fileListFragment.filesCount + setSelectAllMenuItem(selectAllMenuItem, isAllChecked) + } + + override fun onClick(v: View) { + when (v.id) { + R.id.upload_files_btn_cancel -> cancelAndFinish() + R.id.upload_files_btn_upload -> if (checkStoragePermission(this)) upload() + } + } + + @Suppress("SpreadOperator", "DEPRECATION") + private fun upload() { + currentDir?.let { preferences.uploadFromLocalLastPath = it.absolutePath } + + if (isFolderPickerMode) { + finishWithChosenFolder() + return + } + + fileListFragment.collectCheckedFilePaths { chosenFiles -> + if (chosenFiles.size > FileUploadHelper.MAX_FILE_COUNT) { + FileUploadHelper.instance().showFileUploadLimitMessage(this) + return@collectCheckedFilePaths + } + + chosenFilePaths = chosenFiles + CheckAvailableSpaceTask(this, *chosenFiles).execute(uploadBehaviour.isMoveSelected) + } + } + + private fun finishWithChosenFolder() { + val data = Intent() + currentDir?.let { data.putExtra(EXTRA_CHOSEN_FILES, it.absolutePath) } + setResult(RESULT_OK, data) + + if (isGivenLocalPathHasEnabledParent) { + dialogs.showSubFolderWarningDialog { finish() } + } else { + finish() + } + } + + override fun onConfirmation(callerTag: String?) { + Log_OC.d(TAG, "Positive button in dialog was clicked; dialog tag is $callerTag") + + val chosenFilePaths = chosenFilePaths + if (callerTag != QUERY_TO_MOVE_DIALOG_TAG || chosenFilePaths == null) return + + val data = Intent().apply { + putExtra(EXTRA_CHOSEN_FILES, chosenFilePaths) + putExtra(LOCAL_BASE_PATH, currentDir?.absolutePath) + } + setResult(RESULT_OK_AND_MOVE, data) + finish() + } + + override fun onNeutral(callerTag: String?) { + Log_OC.d(TAG, "Phantom neutral button in dialog was clicked; dialog tag is $callerTag") + } + + override fun onCancel(callerTag: String?) { + Log_OC.d(TAG, "Negative button in dialog was clicked; dialog tag is $callerTag") + } + + override fun onStart() { + super.onStart() + val accountOnCreation = accountOnCreation + if (accountOnCreation == null || accountOnCreation != account) { + cancelAndFinish() + } + } + + override fun onStop() { + dialogs.dismissStoragePathPicker() + super.onStop() + } + + fun setupStoragePermissionWarningBanner() { + fileListFragment.setupStoragePermissionWarningBanner() + } + + private fun ArrayAdapter.addDirectoryHierarchy(directory: File?) { + generateSequence(directory) { it.parentFile } + .takeWhile { it.parentFile != null } + .forEach { add(it.name) } + add(File.separator) + } + + companion object { + private const val PREFIX = "com.owncloud.android.ui.activity.UploadFilesActivity." + private const val KEY_ALL_SELECTED = PREFIX + "KEY_ALL_SELECTED" + const val KEY_LOCAL_FOLDER_PICKER_MODE = PREFIX + "LOCAL_FOLDER_PICKER_MODE" + const val LOCAL_BASE_PATH = PREFIX + "LOCAL_BASE_PATH" + const val EXTRA_CHOSEN_FILES = PREFIX + "EXTRA_CHOSEN_FILES" + const val KEY_DIRECTORY_PATH = PREFIX + "KEY_DIRECTORY_PATH" + + const val RESULT_OK_AND_DELETE = 3 + const val RESULT_OK_AND_DO_NOTHING = 2 + const val RESULT_OK_AND_MOVE = RESULT_FIRST_USER + const val REQUEST_CODE_KEY = "requestCode" + + private const val SINGLE_DIR = 1 + private const val ENCRYPTED_FOLDER_KEY = "encrypted_folder" + private const val LOCAL_FILES_LIST_TAG = "local_files_list" + private const val ROOT_DIR = "/storage/emulated/0" + private const val TAG = "UploadFilesActivity" + + @JvmStatic + fun startUploadActivityForResult( + activity: Activity, + user: User?, + requestCode: Int, + isWithinEncryptedFolder: Boolean + ) { + val intent = Intent(activity, UploadFilesActivity::class.java).apply { + putExtra(FileActivity.EXTRA_USER, user) + putExtra(REQUEST_CODE_KEY, requestCode) + putExtra(ENCRYPTED_FOLDER_KEY, isWithinEncryptedFolder) + } + activity.startActivityForResult(intent, requestCode) + } + } +} diff --git a/app/src/main/java/com/owncloud/android/ui/activity/uploadfiles/UploadBehaviourSelector.kt b/app/src/main/java/com/owncloud/android/ui/activity/uploadfiles/UploadBehaviourSelector.kt new file mode 100644 index 000000000000..b81558580f0e --- /dev/null +++ b/app/src/main/java/com/owncloud/android/ui/activity/uploadfiles/UploadBehaviourSelector.kt @@ -0,0 +1,76 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2026 Alper Ozturk + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +package com.owncloud.android.ui.activity.uploadfiles + +import android.widget.ArrayAdapter +import androidx.appcompat.app.AppCompatActivity +import com.nextcloud.client.preferences.AppPreferences +import com.owncloud.android.R +import com.owncloud.android.databinding.UploadFilesLayoutBinding +import com.owncloud.android.ui.activity.UploadFilesActivity +import com.owncloud.android.utils.FileUtil +import java.io.File + +class UploadBehaviourSelector( + private val activity: AppCompatActivity, + private val binding: UploadFilesLayoutBinding, + private val preferences: AppPreferences +) { + private val spinner get() = binding.uploadFilesSpinnerBehaviour + + val isMoveSelected: Boolean + get() = spinner.selectedItemPosition == POSITION_MOVE + + val resultCode: Int? + get() = when (spinner.selectedItemPosition) { + POSITION_MOVE -> UploadFilesActivity.RESULT_OK_AND_MOVE + POSITION_ONLY_UPLOAD -> UploadFilesActivity.RESULT_OK_AND_DO_NOTHING + POSITION_DELETE -> UploadFilesActivity.RESULT_OK_AND_DELETE + else -> null + } + + fun setup(rootFolderName: String) { + val behaviours = listOf( + activity.getString(R.string.uploader_upload_files_behaviour_move_to_nextcloud_folder, rootFolderName), + activity.getString(R.string.uploader_upload_files_behaviour_only_upload), + activity.getString(R.string.uploader_upload_files_behaviour_upload_and_delete_from_source) + ) + + spinner.adapter = ArrayAdapter(activity, android.R.layout.simple_spinner_item, behaviours).apply { + setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) + } + spinner.setSelection(preferences.uploaderBehaviour) + } + + fun saveSelection() { + preferences.uploaderBehaviour = spinner.selectedItemPosition + } + + fun checkWritableFolder(folder: File?) { + FileUtil.isFolderWritable(folder, activity.lifecycle) { canWriteIntoFolder -> + spinner.isEnabled = canWriteIntoFolder + + val behaviourLabel = activity.getString(R.string.uploader_upload_files_behaviour) + if (canWriteIntoFolder) { + binding.uploadFilesUploadFilesBehaviourText.text = behaviourLabel + spinner.setSelection(preferences.uploaderBehaviour) + } else { + spinner.setSelection(POSITION_ONLY_UPLOAD) + val notWritableLabel = activity.getString(R.string.uploader_upload_files_behaviour_not_writable) + binding.uploadFilesUploadFilesBehaviourText.text = + listOf(behaviourLabel, notWritableLabel).joinToString(LABEL_SEPARATOR) + } + } + } + + companion object { + private const val POSITION_MOVE = 0 + private const val POSITION_ONLY_UPLOAD = 1 + private const val POSITION_DELETE = 2 + private const val LABEL_SEPARATOR = " " + } +} diff --git a/app/src/main/java/com/owncloud/android/ui/activity/uploadfiles/UploadFilesDialogs.kt b/app/src/main/java/com/owncloud/android/ui/activity/uploadfiles/UploadFilesDialogs.kt new file mode 100644 index 000000000000..98154f15772b --- /dev/null +++ b/app/src/main/java/com/owncloud/android/ui/activity/uploadfiles/UploadFilesDialogs.kt @@ -0,0 +1,90 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2026 Alper Ozturk + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +package com.owncloud.android.ui.activity.uploadfiles + +import androidx.appcompat.app.AppCompatActivity +import androidx.fragment.app.DialogFragment +import com.nextcloud.utils.extensions.isDialogFragmentReady +import com.owncloud.android.R +import com.owncloud.android.ui.dialog.ConfirmationDialogFragment +import com.owncloud.android.ui.dialog.ConfirmationDialogFragment.ConfirmationDialogFragmentListener +import com.owncloud.android.ui.dialog.IndeterminateProgressDialog +import com.owncloud.android.ui.dialog.LocalStoragePathPickerDialogFragment + +class UploadFilesDialogs(private val activity: AppCompatActivity) { + + private var waitDialog: DialogFragment? = null + private var storagePathPickerDialog: LocalStoragePathPickerDialogFragment? = null + + fun showStoragePathPicker() { + val transaction = activity.supportFragmentManager.beginTransaction().addToBackStack(null) + storagePathPickerDialog = LocalStoragePathPickerDialogFragment.newInstance().also { + it.show(transaction, LocalStoragePathPickerDialogFragment.LOCAL_STORAGE_PATH_PICKER_FRAGMENT) + } + } + + fun dismissStoragePathPicker() { + storagePathPickerDialog?.dismissAllowingStateLoss() + } + + fun showWaitDialog() { + waitDialog = IndeterminateProgressDialog.newInstance(R.string.wait_a_moment, false).also { + it.show(activity.supportFragmentManager, WAIT_DIALOG_TAG) + } + } + + fun dismissWaitDialog() { + val dialog = waitDialog ?: return + if (!activity.isDialogFragmentReady(dialog)) return + + dialog.dismiss() + waitDialog = null + } + + fun showQueryToMoveDialog(listener: ConfirmationDialogFragmentListener) { + val dialog = ConfirmationDialogFragment.newInstance( + messageResId = R.string.upload_query_move_foreign_files, + messageArguments = arrayOf(activity.getString(R.string.app_name)), + titleResId = NO_TITLE, + positiveButtonTextId = R.string.common_yes, + negativeButtonTextId = R.string.common_no, + neutralButtonTextId = NO_BUTTON + ) + dialog.setOnConfirmationListener(listener) + dialog.show(activity.supportFragmentManager, QUERY_TO_MOVE_DIALOG_TAG) + } + + fun showSubFolderWarningDialog(onConfirmed: () -> Unit) { + val dialog = ConfirmationDialogFragment.newInstance( + messageResId = R.string.auto_upload_sub_folder_warning, + messageArguments = null, + titleResId = R.string.sync_duplication, + titleIconId = R.drawable.ic_info, + positiveButtonTextId = R.string.sync_anyway, + negativeButtonTextId = R.string.common_cancel, + neutralButtonTextId = NO_BUTTON + ) + + dialog.setOnConfirmationListener(object : ConfirmationDialogFragmentListener { + override fun onConfirmation(callerTag: String?) = onConfirmed() + override fun onNeutral(callerTag: String?) = Unit + override fun onCancel(callerTag: String?) = Unit + }) + + if (activity.isDialogFragmentReady(dialog)) { + dialog.show(activity.supportFragmentManager, SUB_FOLDER_WARNING_DIALOG_TAG) + } + } + + companion object { + const val QUERY_TO_MOVE_DIALOG_TAG = "QUERY_TO_MOVE" + private const val SUB_FOLDER_WARNING_DIALOG_TAG = "SUB_FOLDER_WARNING_DIALOG" + private const val WAIT_DIALOG_TAG = "WAIT" + private const val NO_TITLE = 0 + private const val NO_BUTTON = -1 + } +}