Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -598,6 +598,9 @@
android:launchMode="singleTop"
android:theme="@style/Theme.ownCloud.Dialog.NoTitle"
android:windowSoftInputMode="adjustResize" />
<activity
android:name=".ui.activity.AlbumsPickerActivity"
android:exported="false" />
<activity
android:name=".ui.activity.ShareActivity"
android:exported="false"
Expand Down
20 changes: 20 additions & 0 deletions app/src/main/java/com/nextcloud/client/di/ComponentsModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import com.nextcloud.ui.ImageDetailFragment;
import com.nextcloud.ui.SetOnlineStatusBottomSheet;
import com.nextcloud.ui.SetStatusMessageBottomSheet;
import com.nextcloud.ui.albumItemActions.AlbumItemActionsBottomSheet;
import com.nextcloud.ui.composeActivity.ComposeActivity;
import com.nextcloud.ui.fileactions.FileActionsBottomSheet;
import com.nextcloud.ui.trashbinFileActions.TrashbinFileActionsBottomSheet;
Expand Down Expand Up @@ -82,6 +83,7 @@
import com.owncloud.android.ui.dialog.ChooseTemplateDialogFragment;
import com.owncloud.android.ui.dialog.ConfirmationDialogFragment;
import com.owncloud.android.ui.dialog.ConflictsResolveDialog;
import com.owncloud.android.ui.dialog.CreateAlbumDialogFragment;
import com.owncloud.android.ui.dialog.CreateFolderDialogFragment;
import com.owncloud.android.ui.dialog.ExpirationDatePickerDialogFragment;
import com.owncloud.android.ui.dialog.IndeterminateProgressDialog;
Expand Down Expand Up @@ -114,6 +116,9 @@
import com.owncloud.android.ui.fragment.OCFileListFragment;
import com.owncloud.android.ui.fragment.SharedListFragment;
import com.owncloud.android.ui.fragment.UnifiedSearchFragment;
import com.owncloud.android.ui.fragment.albums.AlbumItemsFragment;
import com.owncloud.android.ui.fragment.albums.AlbumsFragment;
import com.owncloud.android.ui.activity.AlbumsPickerActivity;
import com.owncloud.android.ui.fragment.contactsbackup.BackupFragment;
import com.owncloud.android.ui.fragment.contactsbackup.BackupListFragment;
import com.owncloud.android.ui.preview.FileDownloadFragment;
Expand Down Expand Up @@ -505,4 +510,19 @@ abstract class ComponentsModule {

@ContributesAndroidInjector
abstract SetStatusMessageBottomSheet setStatusMessageBottomSheet();

@ContributesAndroidInjector
abstract AlbumsPickerActivity albumsPickerActivity();

@ContributesAndroidInjector
abstract CreateAlbumDialogFragment createAlbumDialogFragment();

@ContributesAndroidInjector
abstract AlbumsFragment albumsFragment();

@ContributesAndroidInjector
abstract AlbumItemsFragment albumItemsFragment();

@ContributesAndroidInjector
abstract AlbumItemActionsBottomSheet albumItemActionsBottomSheet();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2025 TSI-mc <surinder.kumar@t-systems.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

package com.nextcloud.ui.albumItemActions

import androidx.annotation.DrawableRes
import androidx.annotation.IdRes
import androidx.annotation.StringRes
import com.owncloud.android.R

enum class AlbumItemAction(@IdRes val id: Int, @StringRes val title: Int, @DrawableRes val icon: Int? = null) {
RENAME_ALBUM(R.id.action_rename_file, R.string.album_rename, R.drawable.ic_edit),
DELETE_ALBUM(R.id.action_delete, R.string.album_delete, R.drawable.ic_delete);

companion object {
/**
* All file actions, in the order they should be displayed
*/
@JvmField
val SORTED_VALUES = listOf(
RENAME_ALBUM,
DELETE_ALBUM
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2025 TSI-mc <surinder.kumar@t-systems.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

package com.nextcloud.ui.albumItemActions

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.IdRes
import androidx.core.os.bundleOf
import androidx.core.view.isEmpty
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.setFragmentResult
import androidx.lifecycle.LifecycleOwner
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import com.nextcloud.client.di.Injectable
import com.owncloud.android.databinding.FileActionsBottomSheetBinding
import com.owncloud.android.databinding.FileActionsBottomSheetItemBinding
import com.owncloud.android.utils.theme.ViewThemeUtils
import javax.inject.Inject

class AlbumItemActionsBottomSheet : BottomSheetDialogFragment(), Injectable {

@Inject
lateinit var viewThemeUtils: ViewThemeUtils

private var _binding: FileActionsBottomSheetBinding? = null
val binding
get() = _binding!!

fun interface ResultListener {
fun onResult(@IdRes actionId: Int)
}

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
_binding = FileActionsBottomSheetBinding.inflate(inflater, container, false)

val bottomSheetDialog = dialog as BottomSheetDialog
bottomSheetDialog.behavior.state = BottomSheetBehavior.STATE_EXPANDED
bottomSheetDialog.behavior.skipCollapsed = true
return binding.root
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.bottomSheetHeader.visibility = View.GONE
binding.bottomSheetLoading.visibility = View.GONE
displayActions()
}

override fun onDestroyView() {
super.onDestroyView()
_binding = null
}

fun setResultListener(
fragmentManager: FragmentManager,
lifecycleOwner: LifecycleOwner,
listener: ResultListener
): AlbumItemActionsBottomSheet {
fragmentManager.setFragmentResultListener(REQUEST_KEY, lifecycleOwner) { _, result ->
@IdRes val actionId = result.getInt(RESULT_KEY_ACTION_ID, -1)
if (actionId != -1) {
listener.onResult(actionId)
}
}
return this
}

private fun displayActions() {
if (binding.fileActionsList.isEmpty()) {
AlbumItemAction.SORTED_VALUES.forEach { action ->
val view = inflateActionView(action)
binding.fileActionsList.addView(view)
}
}
}

private fun inflateActionView(action: AlbumItemAction): View {
val itemBinding = FileActionsBottomSheetItemBinding.inflate(layoutInflater, binding.fileActionsList, false)
.apply {
root.setOnClickListener {
dispatchActionClick(action.id)
}
text.setText(action.title)
if (action.icon != null) {
icon.setImageResource(action.icon)
}
}
return itemBinding.root
}

private fun dispatchActionClick(id: Int?) {
if (id != null) {
setFragmentResult(REQUEST_KEY, bundleOf(RESULT_KEY_ACTION_ID to id))
parentFragmentManager.clearFragmentResultListener(REQUEST_KEY)
dismiss()
}
}

companion object {
private const val REQUEST_KEY = "REQUEST_KEY_ACTION"
private const val RESULT_KEY_ACTION_ID = "RESULT_KEY_ACTION_ID"

@JvmStatic
fun newInstance(): AlbumItemActionsBottomSheet {
return AlbumItemActionsBottomSheet()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,5 @@
* Type for virtual folders
*/
public enum VirtualFolderType {
FAVORITE, GALLERY, NONE
FAVORITE, GALLERY, ALBUM, NONE
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2025 TSI-mc <surinder.kumar@t-systems.com>
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
*/
package com.owncloud.android.operations

import android.content.Context
import com.nextcloud.client.account.User
import com.owncloud.android.MainApp
import com.owncloud.android.datamodel.FileDataStorageManager
import com.owncloud.android.datamodel.OCFile
import com.owncloud.android.lib.common.OwnCloudClient
import com.owncloud.android.lib.common.operations.RemoteOperation
import com.owncloud.android.lib.common.operations.RemoteOperationResult
import com.owncloud.android.lib.common.utils.Log_OC
import com.owncloud.android.lib.resources.files.ReadFileRemoteOperation
import com.owncloud.android.lib.resources.files.SearchRemoteOperation
import com.owncloud.android.lib.resources.files.model.RemoteFile
import com.owncloud.android.operations.common.SyncOperation
import com.owncloud.android.utils.FileStorageUtils

/**
* fetch OCFile meta data if not present in local db
*
* @see com.owncloud.android.ui.asynctasks.FetchRemoteFileTask reference for this operation
*
* @param ocFile file for which metadata has to retrieve
* @param removeFileFromDb if you want to remove ocFile from local db to avoid duplicate entries for same fileId
*/
class FetchRemoteFileOperation(
private val context: Context,
private val user: User,
private val ocFile: OCFile,
private val removeFileFromDb: Boolean = false,
storageManager: FileDataStorageManager,
) : SyncOperation(storageManager) {

@Deprecated("Deprecated in Java")
override fun run(client: OwnCloudClient?): RemoteOperationResult<*>? {
val searchRemoteOperation = SearchRemoteOperation(
ocFile.localId.toString(),
SearchRemoteOperation.SearchType.FILE_ID_SEARCH,
false,
storageManager.getCapability(user)
)
val remoteOperationResult: RemoteOperationResult<List<RemoteFile>> =
searchRemoteOperation.execute(user, context)

if (remoteOperationResult.isSuccess && remoteOperationResult.resultData != null) {
if (remoteOperationResult.resultData.isEmpty()) {
Log_OC.e(TAG, "No remote file found with id: ${ocFile.localId}.")
return remoteOperationResult
}
val remotePath = (remoteOperationResult.resultData[0]).remotePath

val operation = ReadFileRemoteOperation(remotePath)
val result = operation.execute(user, context)

if (!result.isSuccess) {
val exception = result.exception
val message =
"Fetching file " + remotePath + " fails with: " + result.getLogMessage(MainApp.getAppContext())
Log_OC.e(TAG, exception?.message ?: message)

return result
}

val remoteFile = result.data[0] as RemoteFile

// remove file from local db
if (removeFileFromDb) {
storageManager.removeFile(ocFile, true, true)
}

var ocFile = FileStorageUtils.fillOCFile(remoteFile)
FileStorageUtils.searchForLocalFileInDefaultPath(ocFile, user.accountName)
ocFile = storageManager.saveFileWithParent(ocFile, context)

// also sync folder content
val toSync: OCFile? = if (ocFile?.isFolder == true) {
ocFile
} else {
ocFile?.parentId?.let { storageManager.getFileById(it) }
}

val currentSyncTime = System.currentTimeMillis()
val refreshFolderOperation: RemoteOperation<Any> = RefreshFolderOperation(
toSync,
currentSyncTime,
true,
true,
storageManager,
user,
context
)
val refreshOperationResult = refreshFolderOperation.execute(user, context)

// set the fetched ocFile to resultData to be handled at ui end
refreshOperationResult.resultData = ocFile

return refreshOperationResult
}
return remoteOperationResult
}

companion object {
private val TAG = FetchRemoteFileOperation::class.java.simpleName
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2025 TSI-mc <surinder.kumar@t-systems.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.owncloud.android.operations.albums

import com.owncloud.android.datamodel.FileDataStorageManager
import com.owncloud.android.datamodel.OCFile
import com.owncloud.android.lib.common.OwnCloudClient
import com.owncloud.android.lib.common.operations.RemoteOperationResult
import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode
import com.owncloud.android.operations.UploadFileOperation
import com.owncloud.android.operations.common.SyncOperation

/**
* Constructor
*
* @param srcPath Remote path of the [OCFile] to move.
* @param targetParentPath Path to the folder where the file will be copied into.
*/
class CopyFileToAlbumOperation(
private val srcPath: String,
private var targetParentPath: String,
storageManager: FileDataStorageManager
) :
SyncOperation(storageManager) {
init {
if (!targetParentPath.endsWith(OCFile.PATH_SEPARATOR)) {
this.targetParentPath += OCFile.PATH_SEPARATOR
}
}

/**
* Performs the operation.
*
* @param client Client object to communicate with the remote ownCloud server.
*/
@Deprecated("Deprecated in Java")
@Suppress("NestedBlockDepth")
override fun run(client: OwnCloudClient): RemoteOperationResult<Any> {
/** 1. check copy validity */
val result: RemoteOperationResult<Any>

if (targetParentPath.startsWith(srcPath)) {
result = RemoteOperationResult<Any>(ResultCode.INVALID_COPY_INTO_DESCENDANT)
} else {
val file = storageManager.getFileByPath(srcPath)
if (file == null) {
result = RemoteOperationResult(ResultCode.FILE_NOT_FOUND)
} else {
/** 2. remote copy */
var targetPath = "$targetParentPath${file.fileName}"
if (file.isFolder) {
targetPath += OCFile.PATH_SEPARATOR
}

// auto rename, to allow copy
if (targetPath == srcPath) {
if (file.isFolder) {
targetPath = "$targetParentPath${file.fileName}"
}
targetPath = UploadFileOperation.getNewAvailableRemotePath(client, targetPath, null, false)

if (file.isFolder) {
targetPath += OCFile.PATH_SEPARATOR
}
}

result = CopyFileToAlbumRemoteOperation(srcPath, targetPath).execute(client)
}
}
return result
}
}
Loading