diff --git a/opencloudApp/src/main/AndroidManifest.xml b/opencloudApp/src/main/AndroidManifest.xml index 38c990ad1a..4ced565012 100644 --- a/opencloudApp/src/main/AndroidManifest.xml +++ b/opencloudApp/src/main/AndroidManifest.xml @@ -111,6 +111,23 @@ android:configChanges="orientation|screenSize" android:theme="@style/Theme.openCloud.Toolbar.Drawer" android:windowSoftInputMode="adjustPan" /> + + + + + + + + + + (layoutId) + if (view != null) { + Snackbar.make(view, message, duration).show() + } else { + // The requested layout may not be attached yet (e.g. during activity creation). + // Fall back to a Toast, which only needs a Context, so we never crash. + Toast.makeText(this, message, Toast.LENGTH_LONG).show() + } } fun Activity.showErrorInToast( diff --git a/opencloudApp/src/main/java/eu/opencloud/android/extensions/FileMenuOptionExt.kt b/opencloudApp/src/main/java/eu/opencloud/android/extensions/FileMenuOptionExt.kt index 57491071e2..a3669ca26a 100644 --- a/opencloudApp/src/main/java/eu/opencloud/android/extensions/FileMenuOptionExt.kt +++ b/opencloudApp/src/main/java/eu/opencloud/android/extensions/FileMenuOptionExt.kt @@ -40,6 +40,7 @@ fun FileMenuOption.toResId() = FileMenuOption.SEND -> R.id.action_send_file FileMenuOption.SET_AV_OFFLINE -> R.id.action_set_available_offline FileMenuOption.UNSET_AV_OFFLINE -> R.id.action_unset_available_offline + FileMenuOption.ADD_TO_HOME_SCREEN -> R.id.action_add_to_home } fun FileMenuOption.toStringResId() = @@ -59,6 +60,7 @@ fun FileMenuOption.toStringResId() = FileMenuOption.SEND -> R.string.actionbar_send_file FileMenuOption.SET_AV_OFFLINE -> R.string.set_available_offline FileMenuOption.UNSET_AV_OFFLINE -> R.string.unset_available_offline + FileMenuOption.ADD_TO_HOME_SCREEN -> R.string.add_to_home_screen } fun FileMenuOption.toDrawableResId() = @@ -78,4 +80,5 @@ fun FileMenuOption.toDrawableResId() = FileMenuOption.SEND -> R.drawable.ic_send_white FileMenuOption.SET_AV_OFFLINE -> R.drawable.ic_action_set_available_offline FileMenuOption.UNSET_AV_OFFLINE -> R.drawable.ic_action_unset_available_offline + FileMenuOption.ADD_TO_HOME_SCREEN -> R.drawable.ic_action_add_to_home } diff --git a/opencloudApp/src/main/java/eu/opencloud/android/presentation/files/addtohomescreen/AddToHomeScreenDialogFragment.kt b/opencloudApp/src/main/java/eu/opencloud/android/presentation/files/addtohomescreen/AddToHomeScreenDialogFragment.kt new file mode 100644 index 0000000000..79875fb4ef --- /dev/null +++ b/opencloudApp/src/main/java/eu/opencloud/android/presentation/files/addtohomescreen/AddToHomeScreenDialogFragment.kt @@ -0,0 +1,131 @@ +package eu.opencloud.android.presentation.files.addtohomescreen + +import android.app.Dialog +import android.os.Bundle +import android.view.WindowManager +import android.widget.EditText +import androidx.appcompat.app.AlertDialog +import androidx.coordinatorlayout.widget.CoordinatorLayout +import androidx.core.widget.doOnTextChanged +import androidx.fragment.app.DialogFragment +import com.google.android.material.textfield.TextInputLayout +import eu.opencloud.android.R +import eu.opencloud.android.domain.files.model.OCFile +import eu.opencloud.android.presentation.files.filelist.MainFileListFragment.Companion.forbiddenChars +import eu.opencloud.android.utils.PreferenceUtils + +class AddToHomeScreenDialogFragment : DialogFragment() { + + private lateinit var folder: OCFile + private lateinit var listener: AddToHomeScreenListener + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + folder = requireArguments().getParcelable(ARG_FOLDER)!! + listener = parentFragment as AddToHomeScreenListener + } + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + val inflater = requireActivity().layoutInflater + val view = inflater.inflate(R.layout.edit_box_dialog, null) + + view.filterTouchesWhenObscured = + PreferenceUtils.shouldDisallowTouchesWithOtherVisibleWindows(context) + + val coordinatorLayout: CoordinatorLayout = requireActivity().findViewById(R.id.coordinator_layout) + coordinatorLayout.filterTouchesWhenObscured = + PreferenceUtils.shouldDisallowTouchesWithOtherVisibleWindows(context) + + val inputText: EditText = view.findViewById(R.id.user_input) + val inputLayout: TextInputLayout = view.findViewById(R.id.edit_box_input_text_layout) + + inputText.setText(folder.fileName) + inputText.selectAll() + inputText.requestFocus() + + val builder = AlertDialog.Builder(requireActivity()) + builder.setView(view) + .setPositiveButton(R.string.add_to_home_screen_dialog_add_button) { dialog, _ -> + val name = inputText.text.toString().trim() + if (name.isNotBlank()) { + listener.onAddToHomeScreen(name, folder) + } + dialog.dismiss() + } + .setNegativeButton(android.R.string.cancel, null) + .setTitle(R.string.add_to_home_screen_dialog_title) + + val alertDialog = builder.create() + + alertDialog.setOnShowListener { + val okButton = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE) + okButton.isEnabled = inputText.text.isNullOrBlank().not() + + okButton.setOnClickListener { + val name = inputText.text.toString().trim() + val error = when { + name.isBlank() -> getString(R.string.add_to_home_screen_dialog_error_empty) + name.length > FolderShortcutHelper.MAX_LONG_LABEL_LENGTH -> String.format( + getString(R.string.add_to_home_screen_dialog_error_length_max), + FolderShortcutHelper.MAX_LONG_LABEL_LENGTH + ) + forbiddenChars.any { name.contains(it) } -> getString(R.string.filename_forbidden_characters) + else -> null + } + + if (error == null) { + listener.onAddToHomeScreen(name, folder) + alertDialog.dismiss() + } else { + inputLayout.error = error + } + } + } + + inputText.doOnTextChanged { text, _, _, _ -> + val okButton = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE) + var error: String? = null + + if (text.isNullOrBlank()) { + okButton.isEnabled = false + error = getString(R.string.add_to_home_screen_dialog_error_empty) + } else if (text.length > FolderShortcutHelper.MAX_LONG_LABEL_LENGTH) { + error = String.format( + getString(R.string.add_to_home_screen_dialog_error_length_max), + FolderShortcutHelper.MAX_LONG_LABEL_LENGTH + ) + } else if (forbiddenChars.any { text.contains(it) }) { + error = getString(R.string.filename_forbidden_characters) + } else { + okButton.isEnabled = true + } + + if (error != null) { + okButton.isEnabled = false + inputLayout.error = error + } else { + inputLayout.error = null + } + } + + alertDialog.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE) + return alertDialog + } + + interface AddToHomeScreenListener { + fun onAddToHomeScreen(shortcutName: String, folder: OCFile) + } + + companion object { + const val TAG = "ADD_TO_HOME_SCREEN_DIALOG" + private const val ARG_FOLDER = "ARG_FOLDER" + + @JvmStatic + fun newInstance(folder: OCFile): AddToHomeScreenDialogFragment = + AddToHomeScreenDialogFragment().apply { + arguments = Bundle().apply { + putParcelable(ARG_FOLDER, folder) + } + } + } +} diff --git a/opencloudApp/src/main/java/eu/opencloud/android/presentation/files/addtohomescreen/FolderShortcutHelper.kt b/opencloudApp/src/main/java/eu/opencloud/android/presentation/files/addtohomescreen/FolderShortcutHelper.kt new file mode 100644 index 0000000000..dabccf7412 --- /dev/null +++ b/opencloudApp/src/main/java/eu/opencloud/android/presentation/files/addtohomescreen/FolderShortcutHelper.kt @@ -0,0 +1,79 @@ +package eu.opencloud.android.presentation.files.addtohomescreen + +import android.app.PendingIntent +import android.content.Context +import android.content.Intent +import android.content.pm.ShortcutInfo +import android.content.pm.ShortcutManager +import android.graphics.drawable.Icon +import android.os.Build +import android.widget.Toast +import androidx.annotation.RequiresApi +import eu.opencloud.android.R +import eu.opencloud.android.domain.files.model.OCFile + +object FolderShortcutHelper { + + const val EXTRA_SHORTCUT_FOLDER_REMOTE_ID = "SHORTCUT_FOLDER_REMOTE_ID" + const val EXTRA_SHORTCUT_FOLDER_REMOTE_PATH = "SHORTCUT_FOLDER_REMOTE_PATH" + const val EXTRA_SHORTCUT_FOLDER_SPACE_ID = "SHORTCUT_FOLDER_SPACE_ID" + const val EXTRA_SHORTCUT_FOLDER_ACCOUNT = "SHORTCUT_FOLDER_ACCOUNT" + const val ACTION_OPEN_SHORTCUT = + "eu.opencloud.android.ui.activity.action.OPEN_SHORTCUT" + const val ACTION_PIN_SHORTCUT_RESULT = + "eu.opencloud.android.presentation.files.action.PIN_SHORTCUT_RESULT" + + const val MAX_SHORT_LABEL_LENGTH = 10 + const val MAX_LONG_LABEL_LENGTH = 25 + + fun createPinnedShortcut(context: Context, folder: OCFile, shortcutName: String) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + createPinnedShortcutApi26(context, folder, shortcutName) + } else { + Toast.makeText(context, context.getString(R.string.add_to_home_screen_shortcut_not_supported), Toast.LENGTH_SHORT).show() + } + } + + @RequiresApi(Build.VERSION_CODES.O) + private fun createPinnedShortcutApi26(context: Context, folder: OCFile, shortcutName: String) { + val shortcutManager = context.getSystemService(ShortcutManager::class.java) + + if (shortcutManager?.isRequestPinShortcutSupported != true) { + Toast.makeText(context, context.getString(R.string.add_to_home_screen_shortcut_not_supported), Toast.LENGTH_SHORT).show() + return + } + + val shortcutId = "folder_${folder.id}" + + val shortcutIntent = Intent(context, ShortcutTrampolineActivity::class.java).apply { + action = ACTION_OPEN_SHORTCUT + putExtra(EXTRA_SHORTCUT_FOLDER_REMOTE_ID, folder.remoteId) + putExtra(EXTRA_SHORTCUT_FOLDER_REMOTE_PATH, folder.remotePath) + putExtra(EXTRA_SHORTCUT_FOLDER_SPACE_ID, folder.spaceId) + putExtra(EXTRA_SHORTCUT_FOLDER_ACCOUNT, folder.owner) + flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK + } + + val shortcut = ShortcutInfo.Builder(context, shortcutId) + .setShortLabel(shortcutName.truncateForLabel(MAX_SHORT_LABEL_LENGTH)) + .setLongLabel(shortcutName.truncateForLabel(MAX_LONG_LABEL_LENGTH)) + .setIcon(Icon.createWithResource(context, R.mipmap.icon)) + .setIntent(shortcutIntent) + .build() + + val pinResultCallback = PendingIntent.getBroadcast( + context, + 0, + Intent(context, ShortcutPinResultReceiver::class.java).setAction(ACTION_PIN_SHORTCUT_RESULT), + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE + ) + + val requestAccepted = shortcutManager.requestPinShortcut(shortcut, pinResultCallback.intentSender) + if (!requestAccepted) { + Toast.makeText(context, context.getString(R.string.add_to_home_screen_shortcut_failed), Toast.LENGTH_SHORT).show() + } + } + + private fun String.truncateForLabel(maxLength: Int): String = + if (length > maxLength) take(maxLength - 1) + "…" else this +} diff --git a/opencloudApp/src/main/java/eu/opencloud/android/presentation/files/addtohomescreen/ShortcutPinResultReceiver.kt b/opencloudApp/src/main/java/eu/opencloud/android/presentation/files/addtohomescreen/ShortcutPinResultReceiver.kt new file mode 100644 index 0000000000..8b139517f0 --- /dev/null +++ b/opencloudApp/src/main/java/eu/opencloud/android/presentation/files/addtohomescreen/ShortcutPinResultReceiver.kt @@ -0,0 +1,19 @@ +package eu.opencloud.android.presentation.files.addtohomescreen + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.widget.Toast +import eu.opencloud.android.R + +class ShortcutPinResultReceiver : BroadcastReceiver() { + + override fun onReceive(context: Context, intent: Intent) { + if (intent.action != FolderShortcutHelper.ACTION_PIN_SHORTCUT_RESULT) return + Toast.makeText( + context, + context.getString(R.string.add_to_home_screen_shortcut_added), + Toast.LENGTH_SHORT + ).show() + } +} diff --git a/opencloudApp/src/main/java/eu/opencloud/android/presentation/files/addtohomescreen/ShortcutTrampolineActivity.kt b/opencloudApp/src/main/java/eu/opencloud/android/presentation/files/addtohomescreen/ShortcutTrampolineActivity.kt new file mode 100644 index 0000000000..4f9968787b --- /dev/null +++ b/opencloudApp/src/main/java/eu/opencloud/android/presentation/files/addtohomescreen/ShortcutTrampolineActivity.kt @@ -0,0 +1,34 @@ +package eu.opencloud.android.presentation.files.addtohomescreen + +import android.app.Activity +import android.content.Intent +import android.os.Bundle +import eu.opencloud.android.ui.activity.FileDisplayActivity + +class ShortcutTrampolineActivity : Activity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + forwardShortcutIntent() + finish() + } + + private fun forwardShortcutIntent() { + val shortcutIntent = intent ?: return + if (shortcutIntent.action != FolderShortcutHelper.ACTION_OPEN_SHORTCUT) return + + val target = Intent(this, FileDisplayActivity::class.java).apply { + action = FolderShortcutHelper.ACTION_OPEN_SHORTCUT + copyStringExtra(shortcutIntent, FolderShortcutHelper.EXTRA_SHORTCUT_FOLDER_REMOTE_ID) + copyStringExtra(shortcutIntent, FolderShortcutHelper.EXTRA_SHORTCUT_FOLDER_REMOTE_PATH) + copyStringExtra(shortcutIntent, FolderShortcutHelper.EXTRA_SHORTCUT_FOLDER_SPACE_ID) + copyStringExtra(shortcutIntent, FolderShortcutHelper.EXTRA_SHORTCUT_FOLDER_ACCOUNT) + flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK + } + startActivity(target) + } + + private fun Intent.copyStringExtra(source: Intent, key: String) { + source.getStringExtra(key)?.let { putExtra(key, it) } + } +} diff --git a/opencloudApp/src/main/java/eu/opencloud/android/presentation/files/filelist/MainFileListFragment.kt b/opencloudApp/src/main/java/eu/opencloud/android/presentation/files/filelist/MainFileListFragment.kt index 21acbf63bb..c0d426a126 100644 --- a/opencloudApp/src/main/java/eu/opencloud/android/presentation/files/filelist/MainFileListFragment.kt +++ b/opencloudApp/src/main/java/eu/opencloud/android/presentation/files/filelist/MainFileListFragment.kt @@ -107,6 +107,8 @@ import eu.opencloud.android.presentation.files.SortType import eu.opencloud.android.presentation.files.ViewType import eu.opencloud.android.presentation.files.createfolder.CreateFolderDialogFragment import eu.opencloud.android.presentation.files.createshortcut.CreateShortcutDialogFragment +import eu.opencloud.android.presentation.files.addtohomescreen.AddToHomeScreenDialogFragment +import eu.opencloud.android.presentation.files.addtohomescreen.FolderShortcutHelper import eu.opencloud.android.presentation.files.operations.FileOperation import eu.opencloud.android.presentation.files.operations.FileOperationsViewModel import eu.opencloud.android.presentation.files.removefile.RemoveFilesDialogFragment @@ -138,7 +140,8 @@ class MainFileListFragment : Fragment(), SortDialogListener, SortOptionsView.CreateFolderListener, SortOptionsView.SortOptionsListener, - CreateShortcutDialogFragment.CreateShortcutListener { + CreateShortcutDialogFragment.CreateShortcutListener, + AddToHomeScreenDialogFragment.AddToHomeScreenListener { private val mainFileListViewModel by viewModel { parametersOf( @@ -737,6 +740,13 @@ class MainFileListFragment : Fragment(), FileMenuOption.UNSET_AV_OFFLINE -> { fileOperationsViewModel.performOperation(FileOperation.UnsetFilesAsAvailableOffline(listOf(file))) } + + FileMenuOption.ADD_TO_HOME_SCREEN -> { + if (file.isFolder) { + val dialog = AddToHomeScreenDialogFragment.newInstance(file) + dialog.show(childFragmentManager, DIALOG_ADD_TO_HOME_SCREEN) + } + } } dialog.hide() dialog.dismiss() @@ -1276,6 +1286,10 @@ class MainFileListFragment : Fragment(), uploadActions?.uploadShortcutFileFromApp(arrayOf(shortcutFilePath)) } + override fun onAddToHomeScreen(shortcutName: String, folder: OCFile) { + FolderShortcutHelper.createPinnedShortcut(requireContext(), folder, shortcutName) + } + override fun onFolderNameSet(newFolderName: String, parentFolder: OCFile) { fileOperationsViewModel.performOperation(FileOperation.CreateFolder(newFolderName, parentFolder)) fileOperationsViewModel.createFolder.observe(viewLifecycleOwner, Event.EventObserver { uiResult: UIResult -> @@ -1434,6 +1448,16 @@ class MainFileListFragment : Fragment(), true } + R.id.action_add_to_home -> { + if (singleFile.isFolder) { + val dialog = AddToHomeScreenDialogFragment.newInstance(singleFile) + dialog.show(childFragmentManager, DIALOG_ADD_TO_HOME_SCREEN) + } + fileListAdapter.clearSelection() + updateActionModeAfterTogglingSelected() + true + } + else -> { false } @@ -1633,6 +1657,7 @@ class MainFileListFragment : Fragment(), private const val DIALOG_CREATE_FOLDER = "DIALOG_CREATE_FOLDER" private const val DIALOG_CREATE_SHORTCUT = "DIALOG_CREATE_SHORTCUT" + private const val DIALOG_ADD_TO_HOME_SCREEN = "DIALOG_ADD_TO_HOME_SCREEN" private const val FILE_DOCXF_EXTENSION = "docxf" diff --git a/opencloudApp/src/main/java/eu/opencloud/android/ui/activity/FileDisplayActivity.kt b/opencloudApp/src/main/java/eu/opencloud/android/ui/activity/FileDisplayActivity.kt index 9515048ae1..1dc8115aa4 100644 --- a/opencloudApp/src/main/java/eu/opencloud/android/ui/activity/FileDisplayActivity.kt +++ b/opencloudApp/src/main/java/eu/opencloud/android/ui/activity/FileDisplayActivity.kt @@ -201,6 +201,7 @@ class FileDisplayActivity : FileActivity(), private var isLightUser = false private var isMultiPersonal = false + private var shortcutFolderToNavigate: OCFile? = null override fun onCreate(savedInstanceState: Bundle?) { Timber.v("onCreate() start") @@ -254,6 +255,8 @@ class FileDisplayActivity : FileActivity(), val view = binding.root setContentView(view) + handleShortcutIntent() + // setup toolbar setupRootToolbar( isSearchEnabled = true, @@ -310,6 +313,18 @@ class FileDisplayActivity : FileActivity(), Timber.v("onCreate() end") } + override fun onNewIntent(intent: Intent) { + super.onNewIntent(intent) + setIntent(intent) + handleShortcutIntent() + } + + override fun onResumeFragments() { + super.onResumeFragments() + // New intents can arrive while fragment state is saved. Navigate after it is safe to commit. + navigateToShortcutFolder() + } + private fun checkNotificationPermission() { // Ask for permission only in case it's api >= 33 and notifications are not granted. if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU || @@ -348,7 +363,12 @@ class FileDisplayActivity : FileActivity(), }) isLightUser = manageAccountsViewModel.checkUserLight(account.name) isMultiPersonal = capabilitiesViewModel.checkMultiPersonal() - navigateTo(fileListOption, initialState = true) + + if (shortcutFolderToNavigate != null) { + navigateToShortcutFolder() + } else { + navigateTo(fileListOption, initialState = true) + } } @@ -1966,6 +1986,72 @@ class FileDisplayActivity : FileActivity(), } } + private fun handleShortcutIntent() { + val sc = eu.opencloud.android.presentation.files.addtohomescreen.FolderShortcutHelper + if (intent?.action != sc.ACTION_OPEN_SHORTCUT) return + + val shortcutRemotePath = intent?.getStringExtra(sc.EXTRA_SHORTCUT_FOLDER_REMOTE_PATH) + val shortcutSpaceId = intent?.getStringExtra(sc.EXTRA_SHORTCUT_FOLDER_SPACE_ID) + val shortcutAccountName = intent?.getStringExtra(sc.EXTRA_SHORTCUT_FOLDER_ACCOUNT) + + shortcutFolderToNavigate = null + intent?.removeExtra(sc.EXTRA_SHORTCUT_FOLDER_REMOTE_ID) + intent?.removeExtra(sc.EXTRA_SHORTCUT_FOLDER_REMOTE_PATH) + intent?.removeExtra(sc.EXTRA_SHORTCUT_FOLDER_SPACE_ID) + intent?.removeExtra(sc.EXTRA_SHORTCUT_FOLDER_ACCOUNT) + + if (shortcutRemotePath != null && + !shortcutAccountName.isNullOrBlank() && + shortcutAccountName != account?.name + ) { + val targetAccount = eu.opencloud.android.presentation.authentication.AccountUtils + .getOpenCloudAccountByName(this, shortcutAccountName) + if (targetAccount != null) { + MainApp.initDependencyInjection() + // Recreate account-scoped state instead of delivering another intent to this instance. + finish() + startActivity(Intent(this, FileDisplayActivity::class.java).apply { + action = sc.ACTION_OPEN_SHORTCUT + putExtra(EXTRA_ACCOUNT, targetAccount) + putExtra(sc.EXTRA_SHORTCUT_FOLDER_REMOTE_PATH, shortcutRemotePath) + putExtra(sc.EXTRA_SHORTCUT_FOLDER_SPACE_ID, shortcutSpaceId) + putExtra(sc.EXTRA_SHORTCUT_FOLDER_ACCOUNT, shortcutAccountName) + flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK + }) + } else { + showMessageInSnackbar( + R.id.list_layout, + getString(R.string.add_to_home_screen_shortcut_folder_missing) + ) + } + return + } + + if (shortcutRemotePath != null && account != null) { + val file = storageManager.getFileByPath(shortcutRemotePath, shortcutSpaceId) + if (file != null) { + shortcutFolderToNavigate = file + } else { + showMessageInSnackbar( + R.id.list_layout, + getString(R.string.add_to_home_screen_shortcut_folder_missing) + ) + } + } + } + + private fun navigateToShortcutFolder() { + val folder = shortcutFolderToNavigate ?: return + shortcutFolderToNavigate = null + fileListOption = FileListOption.ALL_FILES + setFile(folder) + cleanSecondFragment() + initAndShowListOfFiles(fileListOption) + refreshListOfFilesFragment() + updateToolbar(folder) + setCheckedItemAtBottomBar(getMenuItemForFileListOption(fileListOption)) + } + private fun onDeepLinkManaged() { collectLatestLifecycleFlow(fileOperationsViewModel.deepLinkFlow) { it?.getContentIfNotHandled()?.let { uiResult -> diff --git a/opencloudApp/src/main/java/eu/opencloud/android/usecases/files/FilterFileMenuOptionsUseCase.kt b/opencloudApp/src/main/java/eu/opencloud/android/usecases/files/FilterFileMenuOptionsUseCase.kt index 416e567b61..7554d853ff 100644 --- a/opencloudApp/src/main/java/eu/opencloud/android/usecases/files/FilterFileMenuOptionsUseCase.kt +++ b/opencloudApp/src/main/java/eu/opencloud/android/usecases/files/FilterFileMenuOptionsUseCase.kt @@ -37,27 +37,43 @@ class FilterFileMenuOptionsUseCase( private val getSpaceWithSpecialsByIdForAccountUseCase: GetSpaceWithSpecialsByIdForAccountUseCase, ) : BaseUseCase, FilterFileMenuOptionsUseCase.Params>() { override fun run(params: Params): MutableList { - val optionsToShow = mutableListOf() val files = params.files - if (files.isEmpty()) { return mutableListOf() } + val state = buildMenuState(params) + return buildMenuOptions(files, params, state) + } + private data class MenuState( + val isAnyFileSynchronizing: Boolean, + val isAnyFileVideoStreaming: Boolean, + val hasRenamePermission: Boolean, + val hasMovePermission: Boolean, + val hasRemovePermission: Boolean, + val hasResharePermission: Boolean, + val isPersonalSpace: Boolean, + val resharingAllowed: Boolean, + ) + + private fun buildMenuState(params: Params): MenuState { + val files = params.files val filesSyncInfo = params.filesSyncInfo val capability = capabilityRepository.getStoredCapabilities(params.accountName) - val space = getSpaceWithSpecialsByIdForAccountUseCase(GetSpaceWithSpecialsByIdForAccountUseCase.Params( - spaceId = files.first().spaceId, - accountName = params.accountName, - )) - + val space = getSpaceWithSpecialsByIdForAccountUseCase( + GetSpaceWithSpecialsByIdForAccountUseCase.Params( + spaceId = files.first().spaceId, + accountName = params.accountName, + ) + ) val isAnyFileSynchronizing: Boolean = if (filesSyncInfo.isEmpty()) { anyFileSynchronizingLookingIntoWorkers(files, params.accountName) } else { anyFileSynchronizingLookingIIntoFilesSyncInfo(filesSyncInfo) } val isAnyFileVideoPreviewing = params.isAnyFileVideoPreviewing - val isAnyFileVideoStreaming = isAnyFileVideoPreviewing && !anyFileDownloaded(files) + val isAnyFileVideoStreaming = + isAnyFileVideoPreviewing && !anyFileDownloaded(files) val hasRenamePermission: Boolean = if (isSingleSelection(files)) { files.first().hasRenamePermission } else { @@ -71,86 +87,110 @@ class FilterFileMenuOptionsUseCase( false } val isPersonalSpace = space?.isPersonal ?: true - val resharingAllowed = capability?.let { !anyFileSharedWithMe(files) || it.filesSharingResharing.isTrue } ?: false - val displaySelectAll = params.displaySelectAll - val displaySelectInverse = params.displaySelectInverse - val onlyAvailableOfflineFiles = params.onlyAvailableOfflineFiles - val onlySharedByLinkFiles = params.onlySharedByLinkFiles - val shareViaLinkAllowed = params.shareViaLinkAllowed - val shareWithUsersAllowed = params.shareWithUsersAllowed - val sendAllowed = params.sendAllowed - - val noSyncAndPreviewing = !isAnyFileSynchronizing && !isAnyFileVideoPreviewing - val noSyncAndStreaming = !isAnyFileSynchronizing && !isAnyFileVideoStreaming - val shareViaLinkOrWithUsersAllowed = shareViaLinkAllowed || shareWithUsersAllowed - val noFilesDownloadedOrIsSingleFile = allFilesDownloaded(files) || isSingleFile(files) - - // Select all - if (displaySelectAll) { + val resharingAllowed = capability?.let { + !anyFileSharedWithMe(files) || it.filesSharingResharing.isTrue + } ?: false + return MenuState( + isAnyFileSynchronizing = isAnyFileSynchronizing, + isAnyFileVideoStreaming = isAnyFileVideoStreaming, + hasRenamePermission = hasRenamePermission, + hasMovePermission = hasMovePermission, + hasRemovePermission = hasRemovePermission, + hasResharePermission = hasResharePermission, + isPersonalSpace = isPersonalSpace, + resharingAllowed = resharingAllowed, + ) + } + + private fun buildMenuOptions( + files: List, + params: Params, + state: MenuState, + ): MutableList { + val optionsToShow = mutableListOf() + val noSyncAndPreviewing = + !state.isAnyFileSynchronizing && !params.isAnyFileVideoPreviewing + val noSyncAndStreaming = + !state.isAnyFileSynchronizing && !state.isAnyFileVideoStreaming + val shareViaLinkOrWithUsersAllowed = + params.shareViaLinkAllowed || params.shareWithUsersAllowed + val noFilesDownloadedOrIsSingleFile = + allFilesDownloaded(files) || isSingleFile(files) + + if (params.displaySelectAll) { optionsToShow.add(FileMenuOption.SELECT_ALL) } - // Select inverse - if (displaySelectInverse) { + if (params.displaySelectInverse) { optionsToShow.add(FileMenuOption.SELECT_INVERSE) } - // Share - if (!onlyAvailableOfflineFiles && shareViaLinkOrWithUsersAllowed && resharingAllowed && - isPersonalSpace && hasResharePermission) { + if (!params.onlyAvailableOfflineFiles && shareViaLinkOrWithUsersAllowed && + state.resharingAllowed && state.isPersonalSpace && + state.hasResharePermission + ) { optionsToShow.add(FileMenuOption.SHARE) } - // Open with (different to preview!) - if (!isAnyFileSynchronizing && isSingleFile(files)) { + if (!state.isAnyFileSynchronizing && isSingleFile(files)) { optionsToShow.add(FileMenuOption.OPEN_WITH) } - // Download - if (noSyncAndPreviewing && !onlyAvailableOfflineFiles && !onlySharedByLinkFiles && - !anyFolder(files) && !anyFileDownloaded(files)) { + if (noSyncAndPreviewing && !params.onlyAvailableOfflineFiles && + !params.onlySharedByLinkFiles && !anyFolder(files) && + !anyFileDownloaded(files) + ) { optionsToShow.add(FileMenuOption.DOWNLOAD) } - // Synchronize - if (!isAnyFileSynchronizing && !onlyAvailableOfflineFiles && !onlySharedByLinkFiles && - (anyFileDownloaded(files) || anyFolder(files))) { + if (!state.isAnyFileSynchronizing && !params.onlyAvailableOfflineFiles && + !params.onlySharedByLinkFiles && + (anyFileDownloaded(files) || anyFolder(files)) + ) { optionsToShow.add(FileMenuOption.SYNC) } - // Cancel sync - if (isAnyFileSynchronizing && !onlyAvailableOfflineFiles && !onlySharedByLinkFiles && !anyAvailableOfflineFile(files)) { + if (state.isAnyFileSynchronizing && !params.onlyAvailableOfflineFiles && + !params.onlySharedByLinkFiles && + !anyAvailableOfflineFile(files) + ) { optionsToShow.add(FileMenuOption.CANCEL_SYNC) } - // Rename - if (noSyncAndPreviewing && !onlyAvailableOfflineFiles && !onlySharedByLinkFiles && - hasRenamePermission) { + if (noSyncAndPreviewing && !params.onlyAvailableOfflineFiles && + !params.onlySharedByLinkFiles && state.hasRenamePermission + ) { optionsToShow.add(FileMenuOption.RENAME) } - // Move - if (noSyncAndPreviewing && !onlyAvailableOfflineFiles && !onlySharedByLinkFiles && - hasMovePermission) { + if (noSyncAndPreviewing && !params.onlyAvailableOfflineFiles && + !params.onlySharedByLinkFiles && state.hasMovePermission + ) { optionsToShow.add(FileMenuOption.MOVE) } - // Copy - if (noSyncAndPreviewing && !onlyAvailableOfflineFiles && !onlySharedByLinkFiles) { + if (noSyncAndPreviewing && !params.onlyAvailableOfflineFiles && + !params.onlySharedByLinkFiles + ) { optionsToShow.add(FileMenuOption.COPY) } - // Send - if (noSyncAndStreaming && !onlyAvailableOfflineFiles && !anyFolder(files) && - noFilesDownloadedOrIsSingleFile && sendAllowed) { + if (noSyncAndStreaming && !params.onlyAvailableOfflineFiles && + !anyFolder(files) && noFilesDownloadedOrIsSingleFile && + params.sendAllowed + ) { optionsToShow.add(FileMenuOption.SEND) } - // Set as available offline - if (!isAnyFileSynchronizing && anyNotAvailableOfflineFile(files) && !isAnyFileVideoStreaming) { + if (!state.isAnyFileSynchronizing && + anyNotAvailableOfflineFile(files) && + !state.isAnyFileVideoStreaming + ) { optionsToShow.add(FileMenuOption.SET_AV_OFFLINE) } - // Unset as available offline - if (anyAvailableOfflineFile(files) && !isAnyFileVideoStreaming) { + if (anyAvailableOfflineFile(files) && !state.isAnyFileVideoStreaming) { optionsToShow.add(FileMenuOption.UNSET_AV_OFFLINE) } - // Details if (isSingleFile(files)) { optionsToShow.add(FileMenuOption.DETAILS) } - // Remove - if (!isAnyFileSynchronizing && !onlyAvailableOfflineFiles && !onlySharedByLinkFiles && hasRemovePermission) { + if (!state.isAnyFileSynchronizing && !params.onlyAvailableOfflineFiles && + !params.onlySharedByLinkFiles && state.hasRemovePermission + ) { optionsToShow.add(FileMenuOption.REMOVE) } + if (isSingleSelection(files) && anyFolder(files)) { + optionsToShow.add(FileMenuOption.ADD_TO_HOME_SCREEN) + } return optionsToShow } diff --git a/opencloudApp/src/main/res/drawable/ic_action_add_to_home.xml b/opencloudApp/src/main/res/drawable/ic_action_add_to_home.xml new file mode 100644 index 0000000000..45624f5f44 --- /dev/null +++ b/opencloudApp/src/main/res/drawable/ic_action_add_to_home.xml @@ -0,0 +1,9 @@ + + + diff --git a/opencloudApp/src/main/res/menu/file_actions_menu.xml b/opencloudApp/src/main/res/menu/file_actions_menu.xml index c5ad8eeeec..c9c0a625a4 100644 --- a/opencloudApp/src/main/res/menu/file_actions_menu.xml +++ b/opencloudApp/src/main/res/menu/file_actions_menu.xml @@ -110,5 +110,11 @@ android:icon="@drawable/ic_action_delete_white" app:showAsAction="ifRoom" android:orderInCategory="1" /> + diff --git a/opencloudApp/src/main/res/values/strings.xml b/opencloudApp/src/main/res/values/strings.xml index a28f5cf4d8..54d8a8894c 100644 --- a/opencloudApp/src/main/res/values/strings.xml +++ b/opencloudApp/src/main/res/values/strings.xml @@ -866,4 +866,14 @@ Added text labels on bottom bar Text labels were added and default active indicator is used to show which section is selected on the bottom bar + Add to Home screen + Add to Home screen + Add + The name cannot be empty + Shortcut added to Home screen + Add Shortcut to Home screen is not supported + Could not add shortcut to Home screen + This folder is no longer available. It may have been removed or renamed. + Shortcut name must not be longer than %d characters. + diff --git a/opencloudDomain/src/main/java/eu/opencloud/android/domain/files/model/FileMenuOption.kt b/opencloudDomain/src/main/java/eu/opencloud/android/domain/files/model/FileMenuOption.kt index 3ea6b6cd36..8983a045db 100644 --- a/opencloudDomain/src/main/java/eu/opencloud/android/domain/files/model/FileMenuOption.kt +++ b/opencloudDomain/src/main/java/eu/opencloud/android/domain/files/model/FileMenuOption.kt @@ -22,5 +22,6 @@ package eu.opencloud.android.domain.files.model enum class FileMenuOption { SELECT_ALL, SELECT_INVERSE, DOWNLOAD, RENAME, MOVE, COPY, REMOVE, OPEN_WITH, - SYNC, CANCEL_SYNC, SHARE, DETAILS, SEND, SET_AV_OFFLINE, UNSET_AV_OFFLINE; + SYNC, CANCEL_SYNC, SHARE, DETAILS, SEND, SET_AV_OFFLINE, UNSET_AV_OFFLINE, + ADD_TO_HOME_SCREEN; }