diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
index ad4cb549ba..b44283c2bf 100644
--- a/.idea/codeStyles/Project.xml
+++ b/.idea/codeStyles/Project.xml
@@ -58,8 +58,6 @@
-
-
diff --git a/app/src/main/java/com/nextcloud/client/di/ComponentsModule.java b/app/src/main/java/com/nextcloud/client/di/ComponentsModule.java
index 05be30305d..9dd4315b3a 100644
--- a/app/src/main/java/com/nextcloud/client/di/ComponentsModule.java
+++ b/app/src/main/java/com/nextcloud/client/di/ComponentsModule.java
@@ -109,6 +109,7 @@ import com.owncloud.android.ui.fragment.FileDetailSharingFragment;
import com.owncloud.android.ui.fragment.FileDetailsSharingProcessFragment;
import com.owncloud.android.ui.fragment.GalleryFragment;
import com.owncloud.android.ui.fragment.GalleryFragmentBottomSheetDialog;
+import com.owncloud.android.ui.fragment.GroupfolderListFragment;
import com.owncloud.android.ui.fragment.LocalFileListFragment;
import com.owncloud.android.ui.fragment.OCFileListBottomSheetDialog;
import com.owncloud.android.ui.fragment.OCFileListFragment;
@@ -462,4 +463,7 @@ abstract class ComponentsModule {
@ContributesAndroidInjector
abstract DocumentScanActivity documentScanActivity();
+
+ @ContributesAndroidInjector
+ abstract GroupfolderListFragment groupfolderListFragment();
}
diff --git a/app/src/main/java/com/owncloud/android/ui/activity/DrawerActivity.java b/app/src/main/java/com/owncloud/android/ui/activity/DrawerActivity.java
index 30b88ba073..21302c84c9 100644
--- a/app/src/main/java/com/owncloud/android/ui/activity/DrawerActivity.java
+++ b/app/src/main/java/com/owncloud/android/ui/activity/DrawerActivity.java
@@ -100,6 +100,7 @@ import com.owncloud.android.ui.events.DummyDrawerEvent;
import com.owncloud.android.ui.events.SearchEvent;
import com.owncloud.android.ui.fragment.FileDetailsSharingProcessFragment;
import com.owncloud.android.ui.fragment.GalleryFragment;
+import com.owncloud.android.ui.fragment.GroupfolderListFragment;
import com.owncloud.android.ui.fragment.OCFileListFragment;
import com.owncloud.android.ui.fragment.SharedListFragment;
import com.owncloud.android.ui.preview.PreviewTextStringFragment;
@@ -422,6 +423,7 @@ public abstract class DrawerActivity extends ToolbarActivity
if (this instanceof FileDisplayActivity &&
!(((FileDisplayActivity) this).getLeftFragment() instanceof GalleryFragment) &&
!(((FileDisplayActivity) this).getLeftFragment() instanceof SharedListFragment) &&
+ !(((FileDisplayActivity) this).getLeftFragment() instanceof GroupfolderListFragment) &&
!(((FileDisplayActivity) this).getLeftFragment() instanceof PreviewTextStringFragment)) {
showFiles(false);
((FileDisplayActivity) this).browseToRoot();
@@ -465,6 +467,13 @@ public abstract class DrawerActivity extends ToolbarActivity
startSharedSearch(menuItem);
} else if (itemId == R.id.nav_recently_modified) {
startRecentlyModifiedSearch(menuItem);
+ } else if (itemId == R.id.nav_groupfolders) {
+ MainApp.showOnlyFilesOnDevice(false);
+ Intent intent = new Intent(getApplicationContext(), FileDisplayActivity.class);
+ intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ intent.setAction(FileDisplayActivity.LIST_GROUPFOLDERS);
+ intent.putExtra(FileDisplayActivity.DRAWER_MENU_ID, menuItem.getItemId());
+ startActivity(intent);
} else {
if (menuItem.getItemId() >= MENU_ITEM_EXTERNAL_LINK &&
menuItem.getItemId() <= MENU_ITEM_EXTERNAL_LINK + 100) {
diff --git a/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.java b/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.java
index f838d3a6f4..541e1e9845 100644
--- a/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.java
+++ b/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.java
@@ -99,13 +99,13 @@ import com.owncloud.android.ui.asynctasks.FetchRemoteFileTask;
import com.owncloud.android.ui.dialog.SendShareDialog;
import com.owncloud.android.ui.dialog.SortingOrderDialogFragment;
import com.owncloud.android.ui.dialog.StoragePermissionDialogFragment;
-import com.owncloud.android.ui.events.ChangeMenuEvent;
import com.owncloud.android.ui.events.SearchEvent;
import com.owncloud.android.ui.events.SyncEventFinished;
import com.owncloud.android.ui.events.TokenPushEvent;
import com.owncloud.android.ui.fragment.FileDetailFragment;
import com.owncloud.android.ui.fragment.FileFragment;
import com.owncloud.android.ui.fragment.GalleryFragment;
+import com.owncloud.android.ui.fragment.GroupfolderListFragment;
import com.owncloud.android.ui.fragment.OCFileListFragment;
import com.owncloud.android.ui.fragment.SearchType;
import com.owncloud.android.ui.fragment.SharedListFragment;
@@ -164,6 +164,7 @@ public class FileDisplayActivity extends FileActivity
public static final String RESTART = "RESTART";
public static final String ALL_FILES = "ALL_FILES";
+ public static final String LIST_GROUPFOLDERS = "LIST_GROUPFOLDERS";
public static final String PHOTO_SEARCH = "PHOTO_SEARCH";
public static final int SINGLE_USER_SIZE = 1;
public static final String OPEN_FILE = "NC_OPEN_FILE";
@@ -551,6 +552,11 @@ public class FileDisplayActivity extends FileActivity
setLeftFragment(new OCFileListFragment());
getSupportFragmentManager().executePendingTransactions();
browseToRoot();
+ } else if (LIST_GROUPFOLDERS.equals(intent.getAction())) {
+ Log_OC.d(this, "Switch to list groupfolders fragment");
+
+ setLeftFragment(new GroupfolderListFragment());
+ getSupportFragmentManager().executePendingTransactions();
}
}
diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/GroupfolderListAdapter.kt b/app/src/main/java/com/owncloud/android/ui/adapter/GroupfolderListAdapter.kt
new file mode 100644
index 0000000000..d44f8459bc
--- /dev/null
+++ b/app/src/main/java/com/owncloud/android/ui/adapter/GroupfolderListAdapter.kt
@@ -0,0 +1,90 @@
+/*
+ *
+ * Nextcloud Android client application
+ *
+ * @author Tobias Kaminsky
+ * Copyright (C) 2023 Tobias Kaminsky
+ * Copyright (C) 2023 Nextcloud GmbH
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.owncloud.android.ui.adapter
+
+import android.content.Context
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.appcompat.content.res.AppCompatResources
+import androidx.recyclerview.widget.RecyclerView
+import com.nextcloud.android.lib.resources.groupfolders.Groupfolder
+import com.owncloud.android.R
+import com.owncloud.android.databinding.ListItemBinding
+import com.owncloud.android.ui.interfaces.GroupfolderListInterface
+import com.owncloud.android.utils.theme.ViewThemeUtils
+import java.io.File
+
+class GroupfolderListAdapter(
+ val context: Context,
+ val viewThemeUtils: ViewThemeUtils,
+ val groupfolderListInterface: GroupfolderListInterface
+) :
+ RecyclerView.Adapter() {
+ lateinit var list: List
+
+ private val folderIcon = viewThemeUtils.platform.tintPrimaryDrawable(
+ context,
+ AppCompatResources.getDrawable(
+ context,
+ R.drawable.folder_shared_users
+ )
+ )
+
+ fun setData(result: Map) {
+ list = result.values.sortedBy { it.mountPoint }
+ }
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
+ return OCFileListItemViewHolder(
+ ListItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
+ )
+ }
+
+ override fun getItemCount(): Int {
+ return list.size
+ }
+
+ override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
+ val groupfolder = list[position]
+ val listHolder = holder as OCFileListItemViewHolder
+
+ val file = File("/" + groupfolder.mountPoint)
+
+ listHolder.apply {
+ fileName.text = file.name
+ fileSize.text = file.parentFile?.path ?: "/"
+ fileSizeSeparator.visibility = View.GONE
+ lastModification.visibility = View.GONE
+ checkbox.visibility = View.GONE
+ overflowMenu.visibility = View.GONE
+ shared.visibility = View.GONE
+ localFileIndicator.visibility = View.GONE
+ favorite.visibility = View.GONE
+
+ thumbnail.setImageDrawable(folderIcon)
+
+ itemLayout.setOnClickListener { groupfolderListInterface.onFolderClick(groupfolder.mountPoint) }
+ }
+ }
+}
diff --git a/app/src/main/java/com/owncloud/android/ui/asynctasks/GroupfoldersSearchTask.kt b/app/src/main/java/com/owncloud/android/ui/asynctasks/GroupfoldersSearchTask.kt
new file mode 100644
index 0000000000..76cc9cae67
--- /dev/null
+++ b/app/src/main/java/com/owncloud/android/ui/asynctasks/GroupfoldersSearchTask.kt
@@ -0,0 +1,72 @@
+/*
+ * Nextcloud Android client application
+ *
+ * @author Tobias Kaminsky
+ * Copyright (C) 2023 Tobias Kaminsky
+ * Copyright (C) 2023 Nextcloud GmbH
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package com.owncloud.android.ui.asynctasks
+
+import android.os.AsyncTask
+import com.nextcloud.android.lib.resources.groupfolders.GetGroupfoldersRemoteOperation
+import com.nextcloud.android.lib.resources.groupfolders.Groupfolder
+import com.nextcloud.client.account.User
+import com.owncloud.android.datamodel.FileDataStorageManager
+import com.owncloud.android.ui.fragment.GroupfolderListFragment
+import java.lang.ref.WeakReference
+
+class GroupfoldersSearchTask(
+ fragment: GroupfolderListFragment,
+ private val user: User,
+ storageManager: FileDataStorageManager
+) : AsyncTask>() {
+ private val fragmentWeakReference: WeakReference
+ private val storageManager: FileDataStorageManager
+
+ init {
+ fragmentWeakReference = WeakReference(fragment)
+ this.storageManager = storageManager
+ }
+
+ override fun doInBackground(vararg voids: Void): Map {
+ if (fragmentWeakReference.get() == null) {
+ return HashMap()
+ }
+ val fragment = fragmentWeakReference.get()
+ return if (isCancelled) {
+ HashMap()
+ } else {
+ val searchRemoteOperation = GetGroupfoldersRemoteOperation()
+ if (fragment?.context != null) {
+ val result = searchRemoteOperation.executeNextcloudClient(
+ user,
+ fragment.requireContext()
+ )
+ if (result.isSuccess) {
+ result.resultData
+ } else {
+ HashMap()
+ }
+ } else {
+ HashMap()
+ }
+ }
+ }
+
+ override fun onPostExecute(result: Map) {
+ fragmentWeakReference.get()?.setData(result)
+ }
+}
diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/GroupfolderListFragment.kt b/app/src/main/java/com/owncloud/android/ui/fragment/GroupfolderListFragment.kt
new file mode 100644
index 0000000000..d2766d5257
--- /dev/null
+++ b/app/src/main/java/com/owncloud/android/ui/fragment/GroupfolderListFragment.kt
@@ -0,0 +1,193 @@
+/*
+ * Nextcloud Android client application
+ *
+ * @author Tobias Kaminsky
+ * Copyright (C) 2023 Tobias Kaminsky
+ * Copyright (C) 2023 Nextcloud GmbH
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package com.owncloud.android.ui.fragment
+
+import android.annotation.SuppressLint
+import android.os.Bundle
+import android.os.Handler
+import android.view.View
+import androidx.lifecycle.lifecycleScope
+import androidx.recyclerview.widget.GridLayoutManager
+import com.nextcloud.android.lib.resources.groupfolders.Groupfolder
+import com.nextcloud.client.di.Injectable
+import com.nextcloud.client.logger.Logger
+import com.owncloud.android.R
+import com.owncloud.android.datamodel.OCFile
+import com.owncloud.android.lib.common.utils.Log_OC
+import com.owncloud.android.lib.resources.files.ReadFileRemoteOperation
+import com.owncloud.android.lib.resources.files.model.RemoteFile
+import com.owncloud.android.ui.activity.FileDisplayActivity
+import com.owncloud.android.ui.adapter.GroupfolderListAdapter
+import com.owncloud.android.ui.asynctasks.GroupfoldersSearchTask
+import com.owncloud.android.ui.interfaces.GroupfolderListInterface
+import com.owncloud.android.utils.DisplayUtils
+import com.owncloud.android.utils.FileStorageUtils
+import com.owncloud.android.utils.theme.ViewThemeUtils
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+import javax.inject.Inject
+
+/**
+ * A Fragment that lists groupfolders
+ */
+class GroupfolderListFragment : OCFileListFragment(), Injectable, GroupfolderListInterface {
+
+ lateinit var adapter: GroupfolderListAdapter
+
+ @Inject
+ lateinit var logger: Logger
+
+ @Inject
+ lateinit var viewThemeUtils: ViewThemeUtils
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ searchFragment = true
+ }
+
+ override fun onActivityCreated(savedInstanceState: Bundle?) {
+ super.onActivityCreated(savedInstanceState)
+
+ currentSearchType = SearchType.GROUPFOLDER
+ menuItemAddRemoveValue = MenuItemAddRemove.REMOVE_GRID_AND_SORT
+ requireActivity().invalidateOptionsMenu()
+
+ search()
+ }
+
+ override fun setAdapter(args: Bundle?) {
+ adapter = GroupfolderListAdapter(requireContext(), viewThemeUtils, this)
+ setRecyclerViewAdapter(adapter)
+
+ val layoutManager = GridLayoutManager(context, 1)
+ recyclerView.layoutManager = layoutManager
+ }
+
+ private fun search() {
+ GroupfoldersSearchTask(
+ this,
+ accountManager.user,
+ mContainerActivity.storageManager
+ ).execute()
+ }
+
+ override fun onResume() {
+ super.onResume()
+ Handler().post {
+ if (activity is FileDisplayActivity) {
+ val fileDisplayActivity = activity as FileDisplayActivity
+ fileDisplayActivity.updateActionBarTitleAndHomeButtonByString(
+ getString(R.string.drawer_item_groupfolders)
+ )
+ fileDisplayActivity.setMainFabVisible(false)
+ }
+ }
+ }
+
+ @SuppressLint("NotifyDataSetChanged")
+ fun setData(result: Map) {
+ adapter.setData(result)
+ adapter.notifyDataSetChanged()
+ }
+
+ private suspend fun fetchFileData(partialFile: OCFile): OCFile? {
+ return withContext(Dispatchers.IO) {
+ val user = accountManager.user
+ val fetchResult = ReadFileRemoteOperation(partialFile.remotePath).execute(user, context)
+ if (!fetchResult.isSuccess) {
+ logger.e(SHARED_TAG, "Error fetching file")
+ if (fetchResult.isException) {
+ logger.e(SHARED_TAG, "exception: ", fetchResult.exception)
+ }
+ null
+ } else {
+ val remoteFile = fetchResult.data[0] as RemoteFile
+ val file = FileStorageUtils.fillOCFile(remoteFile)
+ FileStorageUtils.searchForLocalFileInDefaultPath(file, user.accountName)
+ val savedFile = mContainerActivity.storageManager.saveFileWithParent(file, context)
+ savedFile.apply {
+ isSharedViaLink = partialFile.isSharedViaLink
+ isSharedWithSharee = partialFile.isSharedWithSharee
+ sharees = partialFile.sharees
+ }
+ }
+ }
+ }
+
+ private fun fetchFileAndRun(partialFile: OCFile, block: (file: OCFile) -> Unit) {
+ lifecycleScope.launch {
+ isLoading = true
+ val file = fetchFileData(partialFile)
+ isLoading = false
+ if (file != null) {
+ block(file)
+ } else {
+ DisplayUtils.showSnackMessage(requireActivity(), R.string.error_retrieving_file)
+ }
+ }
+ }
+
+ override fun onShareIconClick(file: OCFile) {
+ fetchFileAndRun(file) { fetched ->
+ super.onShareIconClick(fetched)
+ }
+ }
+
+ override fun showShareDetailView(file: OCFile) {
+ fetchFileAndRun(file) { fetched ->
+ super.showShareDetailView(fetched)
+ }
+ }
+
+ override fun showActivityDetailView(file: OCFile) {
+ fetchFileAndRun(file) { fetched ->
+ super.showActivityDetailView(fetched)
+ }
+ }
+
+ override fun onOverflowIconClicked(file: OCFile, view: View?) {
+ fetchFileAndRun(file) { fetched ->
+ super.onOverflowIconClicked(fetched, view)
+ }
+ }
+
+ override fun onItemClicked(file: OCFile) {
+ fetchFileAndRun(file) { fetched ->
+ super.onItemClicked(fetched)
+ }
+ }
+
+ override fun onLongItemClicked(file: OCFile): Boolean {
+ fetchFileAndRun(file) { fetched ->
+ super.onLongItemClicked(fetched)
+ }
+ return true
+ }
+
+ companion object {
+ private val SHARED_TAG = GroupfolderListFragment::class.java.simpleName
+ }
+
+ override fun onFolderClick(path: String) {
+ Log_OC.d("groupfolder", path)
+ }
+}
diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListSearchAsyncTask.kt b/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListSearchAsyncTask.kt
index 9482c6952d..d7b756c589 100644
--- a/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListSearchAsyncTask.kt
+++ b/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListSearchAsyncTask.kt
@@ -61,7 +61,13 @@ class OCFileListSearchAsyncTask(
}
fragment.setTitle()
- val remoteOperationResult = remoteOperation.execute(currentUser, fragment.context)
+ lateinit var remoteOperationResult: RemoteOperationResult>
+ try {
+ remoteOperationResult = remoteOperation.execute(currentUser, fragment.context)
+ } catch (e: UnsupportedOperationException) {
+ remoteOperationResult = remoteOperation.executeNextcloudClient(currentUser, fragment.requireContext())
+ }
+
if (remoteOperationResult.hasSuccessfulResult() && !isCancelled && fragment.searchFragment) {
fragment.searchEvent = event
if (remoteOperationResult.resultData.isNullOrEmpty()) {
diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/SearchType.kt b/app/src/main/java/com/owncloud/android/ui/fragment/SearchType.kt
index 580fceed8a..e27d3d886d 100644
--- a/app/src/main/java/com/owncloud/android/ui/fragment/SearchType.kt
+++ b/app/src/main/java/com/owncloud/android/ui/fragment/SearchType.kt
@@ -13,5 +13,6 @@ enum class SearchType : Parcelable {
RECENTLY_MODIFIED_SEARCH,
// not a real filter, but nevertheless
- SHARED_FILTER
+ SHARED_FILTER,
+ GROUPFOLDER
}
diff --git a/app/src/main/java/com/owncloud/android/ui/interfaces/GroupfolderListInterface.kt b/app/src/main/java/com/owncloud/android/ui/interfaces/GroupfolderListInterface.kt
new file mode 100644
index 0000000000..cc56b7475a
--- /dev/null
+++ b/app/src/main/java/com/owncloud/android/ui/interfaces/GroupfolderListInterface.kt
@@ -0,0 +1,27 @@
+/*
+ *
+ * Nextcloud Android client application
+ *
+ * @author Tobias Kaminsky
+ * Copyright (C) 2023 Tobias Kaminsky
+ * Copyright (C) 2023 Nextcloud GmbH
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.owncloud.android.ui.interfaces
+
+interface GroupfolderListInterface {
+ fun onFolderClick(path: String)
+}
diff --git a/build.gradle b/build.gradle
index 160459c4eb..ff9965cf68 100644
--- a/build.gradle
+++ b/build.gradle
@@ -8,7 +8,7 @@ buildscript {
daggerVersion = "2.45"
markwonVersion = "4.6.2"
prismVersion = "2.0.0"
- androidLibraryVersion = "master-SNAPSHOT"
+ androidLibraryVersion = "groupfolders-SNAPSHOT"
mockitoVersion = "4.11.0"
mockitoKotlinVersion = "4.1.0"
mockkVersion = "1.13.3"