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;
}