diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index a098c8ff9..afb39eef3 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -170,7 +170,11 @@
+ android:theme="@style/AppTheme" />
+
+
() {
+ Single.fromCallable(new Callable() {
@Override
- public ReadFilesystemOperation call() {
- return new ReadFilesystemOperation(okHttpClient, activeUser, url, 0);
+ public LegacyReadFilesystemOperation call() {
+ return new LegacyReadFilesystemOperation(okHttpClient, activeUser, url, 0);
}
}).observeOn(Schedulers.io())
- .subscribe(new SingleObserver() {
+ .subscribe(new SingleObserver() {
@Override
public void onSubscribe(@NonNull Disposable d) {
// unused atm
}
@Override
- public void onSuccess(@NonNull ReadFilesystemOperation readFilesystemOperation) {
+ public void onSuccess(@NonNull LegacyReadFilesystemOperation readFilesystemOperation) {
DavResponse davResponse = readFilesystemOperation.readRemotePath();
if (davResponse.data != null) {
List browserFileList = (List) davResponse.data;
diff --git a/app/src/main/java/com/nextcloud/talk/components/filebrowser/operations/DavListing.java b/app/src/main/java/com/nextcloud/talk/components/filebrowser/operations/DavListing.java
index dc69a1422..f368d0b4c 100644
--- a/app/src/main/java/com/nextcloud/talk/components/filebrowser/operations/DavListing.java
+++ b/app/src/main/java/com/nextcloud/talk/components/filebrowser/operations/DavListing.java
@@ -26,7 +26,7 @@ import android.util.Log;
import com.nextcloud.talk.components.filebrowser.interfaces.ListingInterface;
import com.nextcloud.talk.components.filebrowser.models.DavResponse;
-import com.nextcloud.talk.components.filebrowser.webdav.ReadFilesystemOperation;
+import com.nextcloud.talk.components.filebrowser.webdav.LegacyReadFilesystemOperation;
import com.nextcloud.talk.models.database.UserEntity;
import java.util.concurrent.Callable;
@@ -50,20 +50,20 @@ public class DavListing extends ListingAbstractClass {
@Override
public void getFiles(String path, UserEntity currentUser, @Nullable OkHttpClient okHttpClient) {
- Single.fromCallable(new Callable() {
+ Single.fromCallable(new Callable() {
@Override
- public ReadFilesystemOperation call() {
- return new ReadFilesystemOperation(okHttpClient, currentUser, path, 1);
+ public LegacyReadFilesystemOperation call() {
+ return new LegacyReadFilesystemOperation(okHttpClient, currentUser, path, 1);
}
}).subscribeOn(Schedulers.io())
- .subscribe(new SingleObserver() {
+ .subscribe(new SingleObserver() {
@Override
public void onSubscribe(@NonNull Disposable d) {
}
@Override
- public void onSuccess(@NonNull ReadFilesystemOperation readFilesystemOperation) {
+ public void onSuccess(@NonNull LegacyReadFilesystemOperation readFilesystemOperation) {
davResponse = readFilesystemOperation.readRemotePath();
try {
listingInterface.listingResult(davResponse);
diff --git a/app/src/main/java/com/nextcloud/talk/components/filebrowser/webdav/ReadFilesystemOperation.java b/app/src/main/java/com/nextcloud/talk/components/filebrowser/webdav/LegacyReadFilesystemOperation.java
similarity index 96%
rename from app/src/main/java/com/nextcloud/talk/components/filebrowser/webdav/ReadFilesystemOperation.java
rename to app/src/main/java/com/nextcloud/talk/components/filebrowser/webdav/LegacyReadFilesystemOperation.java
index ed8e978cf..35766e07f 100644
--- a/app/src/main/java/com/nextcloud/talk/components/filebrowser/webdav/ReadFilesystemOperation.java
+++ b/app/src/main/java/com/nextcloud/talk/components/filebrowser/webdav/LegacyReadFilesystemOperation.java
@@ -40,14 +40,15 @@ import kotlin.jvm.functions.Function2;
import okhttp3.HttpUrl;
import okhttp3.OkHttpClient;
-public class ReadFilesystemOperation {
+@Deprecated
+public class LegacyReadFilesystemOperation {
private static final String TAG = "ReadFilesystemOperation";
private final OkHttpClient okHttpClient;
private final String url;
private final int depth;
private final String basePath;
- public ReadFilesystemOperation(OkHttpClient okHttpClient, UserEntity currentUser, String path, int depth) {
+ public LegacyReadFilesystemOperation(OkHttpClient okHttpClient, UserEntity currentUser, String path, int depth) {
OkHttpClient.Builder okHttpClientBuilder = okHttpClient.newBuilder();
okHttpClientBuilder.followRedirects(false);
okHttpClientBuilder.followSslRedirects(false);
diff --git a/app/src/main/java/com/nextcloud/talk/components/filebrowser/webdav/ReadFilesystemOperation.kt b/app/src/main/java/com/nextcloud/talk/components/filebrowser/webdav/ReadFilesystemOperation.kt
new file mode 100644
index 000000000..b0d86c0ea
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/components/filebrowser/webdav/ReadFilesystemOperation.kt
@@ -0,0 +1,184 @@
+/*
+ * Nextcloud Talk application
+ *
+ * @author Andy Scherzinger
+ * @author Mario Danic
+ * Copyright (C) 2022 Andy Scherzinger
+ * Copyright (C) 2017-2019 Mario Danic
+ *
+ * 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.nextcloud.talk.components.filebrowser.webdav
+
+import android.net.Uri
+import android.text.TextUtils
+import android.util.Log
+import at.bitfire.dav4jvm.DavResource
+import at.bitfire.dav4jvm.Property
+import at.bitfire.dav4jvm.Response
+import at.bitfire.dav4jvm.Response.HrefRelation
+import at.bitfire.dav4jvm.exception.DavException
+import at.bitfire.dav4jvm.property.DisplayName
+import at.bitfire.dav4jvm.property.GetContentType
+import at.bitfire.dav4jvm.property.GetLastModified
+import at.bitfire.dav4jvm.property.ResourceType
+import com.nextcloud.talk.components.filebrowser.models.DavResponse
+import com.nextcloud.talk.components.filebrowser.models.properties.NCEncrypted
+import com.nextcloud.talk.components.filebrowser.models.properties.NCPermission
+import com.nextcloud.talk.components.filebrowser.models.properties.NCPreview
+import com.nextcloud.talk.components.filebrowser.models.properties.OCFavorite
+import com.nextcloud.talk.components.filebrowser.models.properties.OCId
+import com.nextcloud.talk.components.filebrowser.models.properties.OCSize
+import com.nextcloud.talk.dagger.modules.RestModule.MagicAuthenticator
+import com.nextcloud.talk.models.database.UserEntity
+import com.nextcloud.talk.remotefilebrowser.model.RemoteFileBrowserItem
+import com.nextcloud.talk.utils.ApiUtils
+import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
+import okhttp3.OkHttpClient
+import java.io.File
+import java.io.IOException
+
+class ReadFilesystemOperation(okHttpClient: OkHttpClient, currentUser: UserEntity, path: String, depth: Int) {
+ private val okHttpClient: OkHttpClient
+ private val url: String
+ private val depth: Int
+ private val basePath: String
+ fun readRemotePath(): DavResponse {
+ val davResponse = DavResponse()
+ val memberElements: MutableList = ArrayList()
+ val rootElement = arrayOfNulls(1)
+ val remoteFiles: MutableList = ArrayList()
+ try {
+ DavResource(
+ okHttpClient,
+ url.toHttpUrlOrNull()!!
+ ).propfind(
+ depth = depth,
+ reqProp = DavUtils.getAllPropSet()
+ ) { response: Response, hrefRelation: HrefRelation? ->
+ davResponse.setResponse(response)
+ when (hrefRelation) {
+ HrefRelation.MEMBER -> memberElements.add(response)
+ HrefRelation.SELF -> rootElement[0] = response
+ HrefRelation.OTHER -> {}
+ else -> {}
+ }
+ Unit
+ }
+ } catch (e: IOException) {
+ Log.w(TAG, "Error reading remote path")
+ } catch (e: DavException) {
+ Log.w(TAG, "Error reading remote path")
+ }
+ remoteFiles.add(
+ getModelFromResponse(
+ rootElement[0]!!,
+ rootElement[0]!!
+ .href
+ .toString()
+ .substring(basePath.length)
+ )
+ )
+ for (memberElement in memberElements) {
+ remoteFiles.add(
+ getModelFromResponse(
+ memberElement,
+ memberElement
+ .href
+ .toString()
+ .substring(basePath.length)
+ )
+ )
+ }
+ davResponse.setData(remoteFiles)
+ return davResponse
+ }
+
+ companion object {
+ private const val TAG = "ReadFilesystemOperation"
+ }
+
+ init {
+ val okHttpClientBuilder: OkHttpClient.Builder = okHttpClient.newBuilder()
+ okHttpClientBuilder.followRedirects(false)
+ okHttpClientBuilder.followSslRedirects(false)
+ okHttpClientBuilder.authenticator(
+ MagicAuthenticator(
+ ApiUtils.getCredentials(
+ currentUser.username,
+ currentUser.token
+ ),
+ "Authorization"
+ )
+ )
+ this.okHttpClient = okHttpClientBuilder.build()
+ basePath = currentUser.baseUrl + DavUtils.DAV_PATH + currentUser.userId
+ url = basePath + path
+ this.depth = depth
+ }
+
+ private fun getModelFromResponse(response: Response, remotePath: String): RemoteFileBrowserItem {
+ val remoteFileBrowserItem = RemoteFileBrowserItem()
+ remoteFileBrowserItem.path = Uri.decode(remotePath)
+ remoteFileBrowserItem.displayName = Uri.decode(File(remotePath).name)
+ val properties = response.properties
+ for (property in properties) {
+ mapPropertyToBrowserFile(property, remoteFileBrowserItem)
+ }
+ if (remoteFileBrowserItem.permissions != null && remoteFileBrowserItem.permissions!!.contains("R")) {
+ remoteFileBrowserItem.isAllowedToReShare = true
+ }
+ if (TextUtils.isEmpty(remoteFileBrowserItem.mimeType) && !remoteFileBrowserItem.isFile) {
+ remoteFileBrowserItem.mimeType = "inode/directory"
+ }
+
+ return remoteFileBrowserItem
+ }
+
+ @Suppress("Detekt.ComplexMethod")
+ private fun mapPropertyToBrowserFile(property: Property, remoteFileBrowserItem: RemoteFileBrowserItem) {
+ when (property) {
+ is OCId -> {
+ remoteFileBrowserItem.remoteId = property.ocId
+ }
+ is ResourceType -> {
+ remoteFileBrowserItem.isFile = !property.types.contains(ResourceType.COLLECTION)
+ }
+ is GetLastModified -> {
+ remoteFileBrowserItem.modifiedTimestamp = property.lastModified
+ }
+ is GetContentType -> {
+ remoteFileBrowserItem.mimeType = property.type
+ }
+ is OCSize -> {
+ remoteFileBrowserItem.size = property.ocSize
+ }
+ is NCPreview -> {
+ remoteFileBrowserItem.hasPreview = property.isNcPreview
+ }
+ is OCFavorite -> {
+ remoteFileBrowserItem.isFavorite = property.isOcFavorite
+ }
+ is DisplayName -> {
+ remoteFileBrowserItem.displayName = property.displayName
+ }
+ is NCEncrypted -> {
+ remoteFileBrowserItem.isEncrypted = property.isNcEncrypted
+ }
+ is NCPermission -> {
+ remoteFileBrowserItem.permissions = property.ncPermission
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/com/nextcloud/talk/controllers/ProfileController.kt b/app/src/main/java/com/nextcloud/talk/controllers/ProfileController.kt
index 19aa363e0..cfcd00202 100644
--- a/app/src/main/java/com/nextcloud/talk/controllers/ProfileController.kt
+++ b/app/src/main/java/com/nextcloud/talk/controllers/ProfileController.kt
@@ -29,7 +29,6 @@ import android.graphics.BitmapFactory
import android.graphics.Color
import android.net.Uri
import android.os.Bundle
-import android.os.Environment
import android.text.Editable
import android.text.TextUtils
import android.text.TextWatcher
@@ -48,8 +47,6 @@ import androidx.core.graphics.drawable.DrawableCompat
import androidx.core.view.ViewCompat
import androidx.recyclerview.widget.RecyclerView
import autodagger.AutoInjector
-import com.bluelinelabs.conductor.RouterTransaction
-import com.bluelinelabs.conductor.changehandler.VerticalChangeHandler
import com.github.dhaval2404.imagepicker.ImagePicker
import com.github.dhaval2404.imagepicker.ImagePicker.Companion.getError
import com.github.dhaval2404.imagepicker.ImagePicker.Companion.getFile
@@ -59,7 +56,6 @@ import com.nextcloud.talk.api.NcApi
import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
import com.nextcloud.talk.components.filebrowser.controllers.BrowserController.BrowserType
-import com.nextcloud.talk.components.filebrowser.controllers.BrowserForAvatarController
import com.nextcloud.talk.controllers.base.NewBaseController
import com.nextcloud.talk.controllers.util.viewBinding
import com.nextcloud.talk.databinding.ControllerProfileBinding
@@ -71,11 +67,15 @@ import com.nextcloud.talk.models.json.userprofile.Scope
import com.nextcloud.talk.models.json.userprofile.UserProfileData
import com.nextcloud.talk.models.json.userprofile.UserProfileFieldsOverall
import com.nextcloud.talk.models.json.userprofile.UserProfileOverall
+import com.nextcloud.talk.remotefilebrowser.activities.RemoteFileBrowserActivity
import com.nextcloud.talk.ui.dialog.ScopeDialog
import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.DisplayUtils
+import com.nextcloud.talk.utils.FileUtils
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_BROWSER_TYPE
+import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_MIME_TYPE_FILTER
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_TOKEN
+import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_SINGLE_SELECTION
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_USER_ENTITY
import com.nextcloud.talk.utils.database.user.UserUtils
import io.reactivex.Observer
@@ -496,12 +496,22 @@ class ProfileController : NewBaseController(R.layout.controller_profile) {
KEY_USER_ENTITY,
Parcels.wrap(UserEntity::class.java, currentUser)
)
+ bundle.putBoolean(KEY_SINGLE_SELECTION, true)
+ bundle.putString(KEY_MIME_TYPE_FILTER, "image/")
bundle.putString(KEY_ROOM_TOKEN, "123")
+
+ val avatarIntent = Intent(activity, RemoteFileBrowserActivity::class.java)
+ avatarIntent.putExtras(bundle)
+
+ startActivityForResult(avatarIntent, RemoteFileBrowserActivity.REQUEST_CODE_SELECT_AVATAR)
+
+ /*
router.pushController(
RouterTransaction.with(BrowserForAvatarController(bundle, this))
.pushChangeHandler(VerticalChangeHandler())
.popChangeHandler(VerticalChangeHandler())
)
+ */
}
fun handleAvatar(remotePath: String?) {
@@ -526,10 +536,7 @@ class ProfileController : NewBaseController(R.layout.controller_profile) {
private fun saveBitmapAndPassToImagePicker(bitmap: Bitmap) {
var file: File? = null
try {
- file = File.createTempFile(
- "avatar", "png",
- Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM)
- )
+ file = FileUtils.getTempCacheFile(context!!, "avatar/avatar.png")
try {
FileOutputStream(file).use { out -> bitmap.compress(Bitmap.CompressFormat.PNG, FULL_QUALITY, out) }
} catch (e: IOException) {
@@ -555,6 +562,7 @@ class ProfileController : NewBaseController(R.layout.controller_profile) {
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (resultCode == Activity.RESULT_OK) {
+
uploadAvatar(getFile(data))
} else if (resultCode == ImagePicker.RESULT_ERROR) {
Toast.makeText(activity, getError(data), Toast.LENGTH_SHORT).show()
diff --git a/app/src/main/java/com/nextcloud/talk/dagger/modules/RepositoryModule.kt b/app/src/main/java/com/nextcloud/talk/dagger/modules/RepositoryModule.kt
index 0e62a8645..267ab6b41 100644
--- a/app/src/main/java/com/nextcloud/talk/dagger/modules/RepositoryModule.kt
+++ b/app/src/main/java/com/nextcloud/talk/dagger/modules/RepositoryModule.kt
@@ -22,6 +22,8 @@
package com.nextcloud.talk.dagger.modules
import com.nextcloud.talk.api.NcApi
+import com.nextcloud.talk.remotefilebrowser.repositories.RemoteFileBrowserItemsRepository
+import com.nextcloud.talk.remotefilebrowser.repositories.RemoteFileBrowserItemsRepositoryImpl
import com.nextcloud.talk.repositories.unifiedsearch.UnifiedSearchRepository
import com.nextcloud.talk.repositories.unifiedsearch.UnifiedSearchRepositoryImpl
import com.nextcloud.talk.shareditems.repositories.SharedItemsRepository
@@ -29,6 +31,7 @@ import com.nextcloud.talk.shareditems.repositories.SharedItemsRepositoryImpl
import com.nextcloud.talk.utils.database.user.CurrentUserProvider
import dagger.Module
import dagger.Provides
+import okhttp3.OkHttpClient
@Module
class RepositoryModule {
@@ -41,4 +44,10 @@ class RepositoryModule {
fun provideUnifiedSearchRepository(ncApi: NcApi, userProvider: CurrentUserProvider): UnifiedSearchRepository {
return UnifiedSearchRepositoryImpl(ncApi, userProvider)
}
+
+ @Provides
+ fun provideRemoteFileBrowserItemsRepository(okHttpClient: OkHttpClient, userProvider: CurrentUserProvider):
+ RemoteFileBrowserItemsRepository {
+ return RemoteFileBrowserItemsRepositoryImpl(okHttpClient, userProvider)
+ }
}
diff --git a/app/src/main/java/com/nextcloud/talk/dagger/modules/ViewModelModule.kt b/app/src/main/java/com/nextcloud/talk/dagger/modules/ViewModelModule.kt
index b0f7170d8..f2356d0ab 100644
--- a/app/src/main/java/com/nextcloud/talk/dagger/modules/ViewModelModule.kt
+++ b/app/src/main/java/com/nextcloud/talk/dagger/modules/ViewModelModule.kt
@@ -23,6 +23,7 @@ package com.nextcloud.talk.dagger.modules
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
+import com.nextcloud.talk.remotefilebrowser.viewmodels.RemoteFileBrowserItemsViewModel
import com.nextcloud.talk.messagesearch.MessageSearchViewModel
import com.nextcloud.talk.shareditems.viewmodels.SharedItemsViewModel
import dagger.Binds
@@ -59,4 +60,9 @@ abstract class ViewModelModule {
@IntoMap
@ViewModelKey(MessageSearchViewModel::class)
abstract fun messageSearchViewModel(viewModel: MessageSearchViewModel): ViewModel
+
+ @Binds
+ @IntoMap
+ @ViewModelKey(RemoteFileBrowserItemsViewModel::class)
+ abstract fun remoteFileBrowserItemsViewModel(viewModel: RemoteFileBrowserItemsViewModel): ViewModel
}
diff --git a/app/src/main/java/com/nextcloud/talk/remotefilebrowser/activities/RemoteFileBrowserActivity.kt b/app/src/main/java/com/nextcloud/talk/remotefilebrowser/activities/RemoteFileBrowserActivity.kt
new file mode 100644
index 000000000..47f777c7c
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/remotefilebrowser/activities/RemoteFileBrowserActivity.kt
@@ -0,0 +1,345 @@
+/*
+ * Nextcloud Talk application
+ *
+ * @author Andy Scherzinger
+ * Copyright (C) 2022 Andy Scherzinger
+ *
+ * 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.nextcloud.talk.remotefilebrowser.activities
+
+import android.app.Activity
+import android.content.Intent
+import android.os.Bundle
+import android.util.Log
+import android.view.Menu
+import android.view.MenuItem
+import android.view.View
+import androidx.appcompat.app.AppCompatActivity
+import androidx.core.content.res.ResourcesCompat
+import androidx.fragment.app.DialogFragment
+import androidx.lifecycle.ViewModelProvider
+import androidx.recyclerview.widget.GridLayoutManager
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
+import autodagger.AutoInjector
+import com.nextcloud.talk.R
+import com.nextcloud.talk.application.NextcloudTalkApplication
+import com.nextcloud.talk.databinding.ActivityRemoteFileBrowserBinding
+import com.nextcloud.talk.interfaces.SelectionInterface
+import com.nextcloud.talk.remotefilebrowser.adapters.RemoteFileBrowserItemsAdapter
+import com.nextcloud.talk.remotefilebrowser.model.RemoteFileBrowserItem
+import com.nextcloud.talk.remotefilebrowser.viewmodels.RemoteFileBrowserItemsViewModel
+import com.nextcloud.talk.ui.dialog.SortingOrderDialogFragment
+import com.nextcloud.talk.utils.DisplayUtils
+import com.nextcloud.talk.utils.FileSortOrder
+import com.nextcloud.talk.utils.FileSortOrderNew
+import com.nextcloud.talk.utils.database.user.UserUtils
+import com.nextcloud.talk.utils.preferences.AppPreferences
+import net.orange_box.storebox.listeners.OnPreferenceValueChangedListener
+import java.io.File
+import java.util.Collections
+import java.util.TreeSet
+import javax.inject.Inject
+
+@AutoInjector(NextcloudTalkApplication::class)
+class RemoteFileBrowserActivity : AppCompatActivity(), SelectionInterface, SwipeRefreshLayout.OnRefreshListener {
+
+ @Inject
+ lateinit var viewModelFactory: ViewModelProvider.Factory
+
+ @Inject
+ lateinit var appPreferences: AppPreferences
+
+ @Inject
+ lateinit var userUtils: UserUtils
+
+ private lateinit var binding: ActivityRemoteFileBrowserBinding
+ private lateinit var viewModel: RemoteFileBrowserItemsViewModel
+
+ private var filesSelectionDoneMenuItem: MenuItem? = null
+
+ private val selectedPaths: MutableSet = Collections.synchronizedSet(TreeSet())
+ private var currentPath: String = "/"
+
+ private var browserItems: List = emptyList()
+ private var adapter: RemoteFileBrowserItemsAdapter? = null
+
+ private var sortingChangeListener: OnPreferenceValueChangedListener? = null
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this)
+
+ binding = ActivityRemoteFileBrowserBinding.inflate(layoutInflater)
+ setSupportActionBar(binding.remoteFileBrowserItemsToolbar)
+ setContentView(binding.root)
+
+ DisplayUtils.applyColorToStatusBar(
+ this,
+ ResourcesCompat.getColor(
+ resources, R.color.appbar, null
+ )
+ )
+ DisplayUtils.applyColorToNavigationBar(
+ this.window,
+ ResourcesCompat.getColor(resources, R.color.bg_default, null)
+ )
+
+ supportActionBar?.title = "current patch"
+ supportActionBar?.setDisplayHomeAsUpEnabled(true)
+
+ initViewModel()
+
+ binding.swipeRefreshList.setOnRefreshListener(this)
+ binding.swipeRefreshList.setColorSchemeResources(R.color.colorPrimary)
+ binding.swipeRefreshList.setProgressBackgroundColorSchemeResource(R.color.refresh_spinner_background)
+
+ appPreferences.registerSortingChangeListener(
+ SortingChangeListener(this).also {
+ sortingChangeListener = it
+ }
+ )
+
+ viewModel.loadItems(currentPath)
+ }
+
+ private fun initViewModel() {
+ viewModel = ViewModelProvider(this, viewModelFactory)[RemoteFileBrowserItemsViewModel::class.java]
+
+ viewModel.viewState.observe(this) { state ->
+ clearEmptyLoading()
+ when (state) {
+ is RemoteFileBrowserItemsViewModel.LoadingItemsState, RemoteFileBrowserItemsViewModel.InitialState -> {
+ showLoading()
+ }
+
+ is RemoteFileBrowserItemsViewModel.NoRemoteFileItemsState -> {
+ showEmpty()
+ }
+
+ is RemoteFileBrowserItemsViewModel.LoadedState -> {
+ val remoteFileBrowserItems = state.items
+ Log.d(TAG, "Items received: $remoteFileBrowserItems")
+
+ // TODO make shwoGrid based on preferences
+ val showGrid = false
+ val layoutManager = if (showGrid) {
+ GridLayoutManager(this, SPAN_COUNT)
+ } else {
+ LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
+ }
+
+ // TODO make mimeTypeSelectionFilter a bundled arg for the activity
+ val mimeTypeSelectionFilter = "image/"
+ val adapter = RemoteFileBrowserItemsAdapter(
+ showGrid = showGrid,
+ mimeTypeSelectionFilter = mimeTypeSelectionFilter,
+ userEntity = userUtils.currentUser!!,
+ selectionInterface = this
+ ) { remoteFileBrowserItem ->
+ onItemClicked(remoteFileBrowserItem)
+ }
+ .apply {
+ items = if (remoteFileBrowserItems.size > 1) {
+ remoteFileBrowserItems.subList(1, remoteFileBrowserItems.size)
+ } else {
+ ArrayList()
+ }
+ browserItems = items
+ }
+
+ binding.recyclerView.adapter = adapter
+ binding.recyclerView.layoutManager = layoutManager
+ binding.recyclerView.setHasFixedSize(true)
+
+ showList()
+ }
+ }
+ }
+ }
+
+ override fun onCreateOptionsMenu(menu: Menu?): Boolean {
+ super.onCreateOptionsMenu(menu)
+ menuInflater.inflate(R.menu.menu_share_files, menu)
+ filesSelectionDoneMenuItem = menu?.findItem(R.id.files_selection_done)
+ filesSelectionDoneMenuItem?.isVisible = selectedPaths.size > 0
+ return true
+ }
+
+ private fun onItemClicked(remoteFileBrowserItem: RemoteFileBrowserItem) {
+ if ("inode/directory" == remoteFileBrowserItem.mimeType) {
+ currentPath = remoteFileBrowserItem.path!!
+ viewModel.loadItems(currentPath)
+ } else {
+ toggleBrowserItemSelection(remoteFileBrowserItem.path!!)
+ }
+ }
+
+ override fun onResume() {
+ super.onResume()
+
+ binding.pathNavigationBackButton.setOnClickListener { goBack() }
+ binding.sortButton.setOnClickListener { changeSorting() }
+
+ binding.sortButton.setText(
+ DisplayUtils.getSortOrderStringId(FileSortOrder.getFileSortOrder(appPreferences.sorting))
+ )
+
+ refreshCurrentPath()
+ }
+
+ fun changeSorting() {
+ val newFragment: DialogFragment = SortingOrderDialogFragment
+ .newInstance(FileSortOrder.getFileSortOrder(appPreferences.sorting))
+ newFragment.show(
+ supportFragmentManager,
+ SortingOrderDialogFragment.SORTING_ORDER_FRAGMENT
+ )
+ }
+
+ private fun goBack(): Boolean {
+ if (currentPath != "/") {
+ viewModel.loadItems(File(currentPath).parent!!)
+ }
+ return true
+ }
+
+ override fun onOptionsItemSelected(item: MenuItem): Boolean {
+ return when (item.itemId) {
+ android.R.id.home -> {
+ onBackPressed()
+ true
+ }
+ R.id.files_selection_done -> {
+ onFileSelectionDone()
+ true
+ }
+ else -> {
+ return super.onOptionsItemSelected(item)
+ }
+ }
+ }
+
+ private fun onFileSelectionDone() {
+ val data = Intent()
+ data.putStringArrayListExtra(EXTRA_SELECTED_PATHS, ArrayList(selectedPaths))
+ setResult(Activity.RESULT_OK, data)
+ finish()
+ }
+
+ private fun clearEmptyLoading() {
+ binding.emptyContainer.emptyListView.visibility = View.GONE
+ }
+
+ private fun showLoading() {
+ binding.emptyContainer.emptyListViewHeadline.text = getString(R.string.file_list_loading)
+ binding.emptyContainer.emptyListView.visibility = View.VISIBLE
+ binding.recyclerView.visibility = View.GONE
+ }
+
+ private fun showEmpty() {
+ binding.emptyContainer.emptyListViewHeadline.text = getString(R.string.nc_shared_items_empty)
+ binding.emptyContainer.emptyListView.visibility = View.VISIBLE
+ binding.recyclerView.visibility = View.GONE
+ }
+
+ private fun showList() {
+ binding.recyclerView.visibility = View.VISIBLE
+ }
+
+ override fun onRefresh() {
+ refreshCurrentPath()
+ }
+
+ private fun refreshCurrentPath() {
+ viewModel.loadItems(currentPath)
+ }
+
+ private fun shouldPathBeSelectedDueToParent(currentPath: String): Boolean {
+ var file = File(currentPath)
+ if (selectedPaths.size > 0 && file.parent != "/") {
+ while (file.parent != null) {
+ var parent = file.parent!!
+ if (File(file.parent!!).parent != null) {
+ parent += "/"
+ }
+ if (selectedPaths.contains(parent)) {
+ return true
+ }
+ file = File(file.parent!!)
+ }
+ }
+ return false
+ }
+
+ private fun checkAndRemoveAnySelectedParents(currentPath: String) {
+ var file = File(currentPath)
+ selectedPaths.remove(currentPath)
+ while (file.parent != null) {
+ selectedPaths.remove(file.parent!! + "/")
+ file = File(file.parent!!)
+ }
+ runOnUiThread {
+ binding.recyclerView.adapter!!.notifyDataSetChanged()
+ }
+ }
+
+ companion object {
+ private val TAG = RemoteFileBrowserActivity::class.simpleName
+ const val SPAN_COUNT: Int = 4
+ const val EXTRA_SELECTED_PATHS = "EXTRA_SELECTED_PATH"
+ const val REQUEST_CODE_SELECT_AVATAR = 22
+ }
+
+ override fun toggleBrowserItemSelection(path: String) {
+ if (selectedPaths.contains(path) || shouldPathBeSelectedDueToParent(path)) {
+ checkAndRemoveAnySelectedParents(path)
+ } else {
+ // TODO if it's a folder, remove all the children we added manually
+ selectedPaths.add(path)
+ }
+ filesSelectionDoneMenuItem?.isVisible = selectedPaths.size > 0
+ }
+
+ override fun isPathSelected(path: String): Boolean {
+ return selectedPaths.contains(path) || shouldPathBeSelectedDueToParent(path)
+ }
+
+ override fun shouldOnlySelectOneImageFile(): Boolean {
+ return true
+ }
+
+ @Suppress("Detekt.TooGenericExceptionCaught")
+ private class SortingChangeListener(private val activity: RemoteFileBrowserActivity) :
+ OnPreferenceValueChangedListener {
+ override fun onChanged(newValue: String) {
+ try {
+ val sortOrder = FileSortOrderNew.getFileSortOrder(newValue)
+
+ activity.binding.sortButton.setText(DisplayUtils.getSortOrderStringId(sortOrder))
+ activity.browserItems = sortOrder.sortCloudFiles(activity.browserItems)
+
+ activity.runOnUiThread {
+ activity.adapter!!.updateDataSet(activity.browserItems)
+ }
+ } catch (npe: NullPointerException) {
+ // view binding can be null
+ // since this is called asynchronously and UI might have been destroyed in the meantime
+ Log.i(TAG, "UI destroyed - view binding already gone")
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/com/nextcloud/talk/remotefilebrowser/adapters/RemoteFileBrowserItemsAdapter.kt b/app/src/main/java/com/nextcloud/talk/remotefilebrowser/adapters/RemoteFileBrowserItemsAdapter.kt
new file mode 100644
index 000000000..0af1015ee
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/remotefilebrowser/adapters/RemoteFileBrowserItemsAdapter.kt
@@ -0,0 +1,86 @@
+/*
+ * Nextcloud Talk application
+ *
+ * @author Andy Scherzinger
+ * Copyright (C) 2022 Andy Scherzinger
+ *
+ * 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.nextcloud.talk.remotefilebrowser.adapters
+
+import android.annotation.SuppressLint
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import androidx.recyclerview.widget.RecyclerView
+import com.nextcloud.talk.databinding.RvItemBrowserFileBinding
+import com.nextcloud.talk.interfaces.SelectionInterface
+import com.nextcloud.talk.models.database.UserEntity
+import com.nextcloud.talk.remotefilebrowser.model.RemoteFileBrowserItem
+
+class RemoteFileBrowserItemsAdapter(
+ private val showGrid: Boolean = false,
+ private val mimeTypeSelectionFilter: String? = null,
+ private val userEntity: UserEntity,
+ private val selectionInterface: SelectionInterface,
+ private val onItemClicked: (RemoteFileBrowserItem) -> Unit
+) : RecyclerView.Adapter() {
+
+ var items: List = emptyList()
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RemoteFileBrowserItemsViewHolder {
+
+ return if (showGrid) {
+ RemoteFileBrowserItemsListViewHolder(
+ RvItemBrowserFileBinding.inflate(
+ LayoutInflater.from(parent.context),
+ parent,
+ false
+ ),
+ mimeTypeSelectionFilter,
+ userEntity,
+ selectionInterface
+ ) {
+ onItemClicked(items[it])
+ }
+ } else {
+ RemoteFileBrowserItemsListViewHolder(
+ RvItemBrowserFileBinding.inflate(
+ LayoutInflater.from(parent.context),
+ parent,
+ false
+ ),
+ mimeTypeSelectionFilter,
+ userEntity,
+ selectionInterface
+ ) {
+ onItemClicked(items[it])
+ }
+ }
+ }
+
+ override fun onBindViewHolder(holder: RemoteFileBrowserItemsViewHolder, position: Int) {
+ holder.onBind(items[position])
+ }
+
+ override fun getItemCount(): Int {
+ return items.size
+ }
+
+ @SuppressLint("NotifyDataSetChanged")
+ fun updateDataSet(browserItems: List) {
+ items = browserItems
+ notifyDataSetChanged()
+ }
+}
diff --git a/app/src/main/java/com/nextcloud/talk/remotefilebrowser/adapters/RemoteFileBrowserItemsListViewHolder.kt b/app/src/main/java/com/nextcloud/talk/remotefilebrowser/adapters/RemoteFileBrowserItemsListViewHolder.kt
new file mode 100644
index 000000000..7317c2a3f
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/remotefilebrowser/adapters/RemoteFileBrowserItemsListViewHolder.kt
@@ -0,0 +1,153 @@
+/*
+ * Nextcloud Talk application
+ *
+ * @author Andy Scherzinger
+ * Copyright (C) 202 Andy Scherzinger
+ *
+ * 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.nextcloud.talk.remotefilebrowser.adapters
+
+import android.text.format.Formatter
+import android.util.Log
+import android.view.View
+import androidx.appcompat.content.res.AppCompatResources
+import autodagger.AutoInjector
+import com.facebook.drawee.backends.pipeline.Fresco
+import com.facebook.drawee.interfaces.DraweeController
+import com.facebook.drawee.view.SimpleDraweeView
+import com.nextcloud.talk.R
+import com.nextcloud.talk.application.NextcloudTalkApplication
+import com.nextcloud.talk.databinding.RvItemBrowserFileBinding
+import com.nextcloud.talk.interfaces.SelectionInterface
+import com.nextcloud.talk.models.database.UserEntity
+import com.nextcloud.talk.remotefilebrowser.model.RemoteFileBrowserItem
+import com.nextcloud.talk.utils.ApiUtils
+import com.nextcloud.talk.utils.DateUtils.getLocalDateTimeStringFromTimestamp
+import com.nextcloud.talk.utils.DisplayUtils
+import com.nextcloud.talk.utils.DrawableUtils.getDrawableResourceIdForMimeType
+
+@AutoInjector(NextcloudTalkApplication::class)
+class RemoteFileBrowserItemsListViewHolder(
+ override val binding: RvItemBrowserFileBinding,
+ mimeTypeSelectionFilter: String?,
+ currentUser: UserEntity,
+ selectionInterface: SelectionInterface,
+ onItemClicked: (Int) -> Unit
+) : RemoteFileBrowserItemsViewHolder(binding, mimeTypeSelectionFilter, currentUser, selectionInterface) {
+
+ override val fileIcon: SimpleDraweeView
+ get() = binding.fileIcon
+
+ private var selectable : Boolean = true
+ private var clickable : Boolean = true
+
+ init {
+ itemView.setOnClickListener {
+ if (clickable) {
+ onItemClicked(bindingAdapterPosition)
+ if (selectable) {
+ binding.selectFileCheckbox.toggle()
+ }
+ }
+ }
+ }
+
+ override fun onBind(item: RemoteFileBrowserItem) {
+
+ super.onBind(item)
+
+ binding.fileIcon.controller = null
+ if (!item.isAllowedToReShare || item.isEncrypted) {
+ binding.root.isEnabled = false
+ binding.root.alpha = DISABLED_ALPHA
+ } else {
+ binding.root.isEnabled = true
+ binding.root.alpha = ENABLED_ALPHA
+ }
+
+ binding.fileEncryptedImageView.visibility =
+ if (item.isEncrypted) {
+ View.VISIBLE
+ } else {
+ View.GONE
+ }
+
+ binding.fileFavoriteImageView.visibility =
+ if (item.isFavorite) {
+ View.VISIBLE
+ } else {
+ View.GONE
+ }
+
+ calculateSelectability(item)
+ calculateClickability(item, selectable)
+ setSelectability()
+
+ binding.fileIcon
+ .hierarchy
+ .setPlaceholderImage(
+ AppCompatResources.getDrawable(
+ binding.fileIcon.context, getDrawableResourceIdForMimeType(item.mimeType)
+ )
+ )
+
+ if (item.hasPreview) {
+ val path = ApiUtils.getUrlForFilePreviewWithRemotePath(
+ currentUser.baseUrl,
+ item.path,
+ binding.fileIcon.context.resources.getDimensionPixelSize(R.dimen.small_item_height)
+ )
+ if (path.isNotEmpty()) {
+ val draweeController: DraweeController = Fresco.newDraweeControllerBuilder()
+ .setAutoPlayAnimations(true)
+ .setImageRequest(DisplayUtils.getImageRequestForUrl(path, null))
+ .build()
+ binding.fileIcon.controller = draweeController
+ }
+ }
+
+ binding.filenameTextView.text = item.displayName
+ binding.fileModifiedInfo.text = String.format(
+ binding.fileModifiedInfo.context.getString(R.string.nc_last_modified),
+ Formatter.formatShortFileSize(binding.fileModifiedInfo.context, item.size),
+ getLocalDateTimeStringFromTimestamp(item.modifiedTimestamp)
+ )
+
+ binding.selectFileCheckbox.isChecked = selectionInterface.isPathSelected(item.path!!)
+ }
+
+ private fun setSelectability() {
+ if (selectable) {
+ binding.selectFileCheckbox.visibility = View.VISIBLE
+ } else {
+ binding.selectFileCheckbox.visibility = View.GONE
+ }
+ }
+
+ private fun calculateSelectability(item: RemoteFileBrowserItem) {
+ selectable = item.isFile &&
+ (mimeTypeSelectionFilter == null || item.mimeType?.startsWith(mimeTypeSelectionFilter) == true)
+ }
+
+ private fun calculateClickability(item: RemoteFileBrowserItem, selectableItem: Boolean) {
+ clickable = selectableItem || "inode/directory" == item.mimeType
+ }
+
+ companion object {
+ private const val DISABLED_ALPHA: Float = 0.38f
+ private const val ENABLED_ALPHA: Float = 1.0f
+ }
+}
diff --git a/app/src/main/java/com/nextcloud/talk/remotefilebrowser/adapters/RemoteFileBrowserItemsViewHolder.kt b/app/src/main/java/com/nextcloud/talk/remotefilebrowser/adapters/RemoteFileBrowserItemsViewHolder.kt
new file mode 100644
index 000000000..4ebc5f7b9
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/remotefilebrowser/adapters/RemoteFileBrowserItemsViewHolder.kt
@@ -0,0 +1,53 @@
+/*
+ * Nextcloud Talk application
+ *
+ * @author Andy Scherzinger
+ * Copyright (C) 2022 Andy Scherzinger
+ *
+ * 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.nextcloud.talk.remotefilebrowser.adapters
+
+import android.graphics.drawable.Drawable
+import androidx.core.content.ContextCompat
+import androidx.recyclerview.widget.RecyclerView
+import androidx.viewbinding.ViewBinding
+import com.facebook.drawee.view.SimpleDraweeView
+import com.nextcloud.talk.interfaces.SelectionInterface
+import com.nextcloud.talk.models.database.UserEntity
+import com.nextcloud.talk.remotefilebrowser.model.RemoteFileBrowserItem
+import com.nextcloud.talk.utils.DrawableUtils
+
+abstract class RemoteFileBrowserItemsViewHolder(
+ open val binding: ViewBinding,
+ val mimeTypeSelectionFilter: String? = null,
+ val currentUser: UserEntity,
+ val selectionInterface: SelectionInterface,
+) : RecyclerView.ViewHolder(binding.root) {
+
+ abstract val fileIcon: SimpleDraweeView
+
+ open fun onBind(item: RemoteFileBrowserItem) {
+ fileIcon.hierarchy.setPlaceholderImage(staticImage(item.mimeType, fileIcon))
+ }
+
+ private fun staticImage(
+ mimeType: String?,
+ image: SimpleDraweeView
+ ): Drawable {
+ val drawableResourceId = DrawableUtils.getDrawableResourceIdForMimeType(mimeType)
+ return ContextCompat.getDrawable(image.context, drawableResourceId)!!
+ }
+}
diff --git a/app/src/main/java/com/nextcloud/talk/remotefilebrowser/model/RemoteFileBrowserItem.kt b/app/src/main/java/com/nextcloud/talk/remotefilebrowser/model/RemoteFileBrowserItem.kt
new file mode 100644
index 000000000..32e06fba8
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/remotefilebrowser/model/RemoteFileBrowserItem.kt
@@ -0,0 +1,49 @@
+/*
+ * Nextcloud Talk application
+ *
+ * @author Andy Scherzinger
+ * @author Mario Danic
+ * Copyright (C) 202 Andy Scherzinger
+ * Copyright (C) 2017-2018 Mario Danic
+ *
+ * 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.nextcloud.talk.remotefilebrowser.model
+
+import android.os.Parcelable
+import com.bluelinelabs.logansquare.annotation.JsonObject
+import kotlinx.android.parcel.Parcelize
+
+@Parcelize
+@JsonObject
+data class RemoteFileBrowserItem(
+ var path: String? = null,
+ var displayName: String? = null,
+ var mimeType: String? = null,
+ var modifiedTimestamp: Long = 0,
+ var size: Long = 0,
+ var isFile: Boolean = false,
+
+ // Used for remote files
+ var remoteId: String? = null,
+ var hasPreview: Boolean = false,
+ var isFavorite: Boolean = false,
+ var isEncrypted: Boolean = false,
+ var permissions: String? = null,
+ var isAllowedToReShare: Boolean = false
+) : Parcelable {
+ // This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject'
+ constructor() : this(null, null, null, 0, 0, false, null, false, false, false, null, false)
+}
diff --git a/app/src/main/java/com/nextcloud/talk/remotefilebrowser/repositories/RemoteFileBrowserItemsRepository.kt b/app/src/main/java/com/nextcloud/talk/remotefilebrowser/repositories/RemoteFileBrowserItemsRepository.kt
new file mode 100644
index 000000000..a2ea15009
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/remotefilebrowser/repositories/RemoteFileBrowserItemsRepository.kt
@@ -0,0 +1,29 @@
+/*
+ * Nextcloud Talk application
+ *
+ * @author Andy Scherzinger
+ * Copyright (C) 202 Andy Scherzinger
+ *
+ * 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.nextcloud.talk.remotefilebrowser.repositories
+
+import com.nextcloud.talk.remotefilebrowser.model.RemoteFileBrowserItem
+import io.reactivex.Observable
+
+interface RemoteFileBrowserItemsRepository {
+
+ fun listFolder(path: String): Observable>
+}
diff --git a/app/src/main/java/com/nextcloud/talk/remotefilebrowser/repositories/RemoteFileBrowserItemsRepositoryImpl.kt b/app/src/main/java/com/nextcloud/talk/remotefilebrowser/repositories/RemoteFileBrowserItemsRepositoryImpl.kt
new file mode 100644
index 000000000..c992462aa
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/remotefilebrowser/repositories/RemoteFileBrowserItemsRepositoryImpl.kt
@@ -0,0 +1,56 @@
+/*
+ * Nextcloud Talk application
+ *
+ * @author Andy Scherzinger
+ * Copyright (C) 202 Andy Scherzinger
+ *
+ * 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.nextcloud.talk.remotefilebrowser.repositories
+
+import com.nextcloud.talk.components.filebrowser.webdav.ReadFilesystemOperation
+import com.nextcloud.talk.models.database.UserEntity
+import com.nextcloud.talk.remotefilebrowser.model.RemoteFileBrowserItem
+import com.nextcloud.talk.utils.database.user.CurrentUserProvider
+import io.reactivex.Observable
+import okhttp3.OkHttpClient
+import javax.inject.Inject
+
+class RemoteFileBrowserItemsRepositoryImpl @Inject constructor(
+ private val okHttpClient: OkHttpClient,
+ private val userProvider: CurrentUserProvider
+) : RemoteFileBrowserItemsRepository {
+
+ private val userEntity: UserEntity
+ get() = userProvider.currentUser!!
+
+ override fun listFolder(path: String):
+ Observable> {
+ return Observable.fromCallable {
+ val operation =
+ ReadFilesystemOperation(
+ okHttpClient,
+ userEntity,
+ path,
+ 1
+ )
+ val davResponse = operation.readRemotePath()
+ if (davResponse.getData() != null) {
+ return@fromCallable davResponse.getData() as List
+ }
+ return@fromCallable emptyList()
+ }
+ }
+}
diff --git a/app/src/main/java/com/nextcloud/talk/remotefilebrowser/viewmodels/RemoteFileBrowserItemsViewModel.kt b/app/src/main/java/com/nextcloud/talk/remotefilebrowser/viewmodels/RemoteFileBrowserItemsViewModel.kt
new file mode 100644
index 000000000..f37edcfbb
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/remotefilebrowser/viewmodels/RemoteFileBrowserItemsViewModel.kt
@@ -0,0 +1,93 @@
+/*
+ * Nextcloud Talk application
+ *
+ * @author Andy Scherzinger
+ * Copyright (C) 202 Andy Scherzinger
+ *
+ * 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.nextcloud.talk.remotefilebrowser.viewmodels
+
+import android.util.Log
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+import com.nextcloud.talk.remotefilebrowser.model.RemoteFileBrowserItem
+import com.nextcloud.talk.remotefilebrowser.repositories.RemoteFileBrowserItemsRepository
+import io.reactivex.Observer
+import io.reactivex.android.schedulers.AndroidSchedulers
+import io.reactivex.disposables.Disposable
+import io.reactivex.schedulers.Schedulers
+import javax.inject.Inject
+
+class RemoteFileBrowserItemsViewModel @Inject constructor(
+ private val repository: RemoteFileBrowserItemsRepository
+) :
+ ViewModel() {
+
+ sealed interface ViewState
+ object InitialState : ViewState
+ object NoRemoteFileItemsState : ViewState
+ object LoadingItemsState : ViewState
+ class LoadedState(val items: List) : ViewState
+
+ private val _viewState: MutableLiveData = MutableLiveData(InitialState)
+
+ val viewState: LiveData
+ get() = _viewState
+
+ fun loadItems(path: String) {
+ _viewState.value = LoadingItemsState
+ repository.listFolder(path).subscribeOn(Schedulers.io())
+ ?.observeOn(AndroidSchedulers.mainThread())
+ ?.subscribe(RemoteFileBrowserItemsObserver())
+ }
+
+ inner class RemoteFileBrowserItemsObserver : Observer> {
+
+ var newRemoteFileBrowserItems: List? = null
+
+ override fun onSubscribe(d: Disposable) = Unit
+
+ override fun onNext(response: List) {
+ newRemoteFileBrowserItems = response
+ }
+
+ override fun onError(e: Throwable) {
+ Log.d(TAG, "An error occurred: $e")
+ }
+
+ override fun onComplete() {
+ if (newRemoteFileBrowserItems.isNullOrEmpty()) {
+ this@RemoteFileBrowserItemsViewModel._viewState.value = NoRemoteFileItemsState
+ } else {
+ setCurrentState(newRemoteFileBrowserItems!!)
+ }
+ }
+
+ private fun setCurrentState(items: List) {
+ when (this@RemoteFileBrowserItemsViewModel._viewState.value) {
+ is LoadedState, LoadingItemsState -> {
+ this@RemoteFileBrowserItemsViewModel._viewState.value = LoadedState(items)
+ }
+ else -> return
+ }
+ }
+ }
+
+ companion object {
+ private val TAG = RemoteFileBrowserItemsViewModel::class.simpleName
+ }
+}
diff --git a/app/src/main/java/com/nextcloud/talk/utils/DisplayUtils.java b/app/src/main/java/com/nextcloud/talk/utils/DisplayUtils.java
index b51b589e2..de2f682ee 100644
--- a/app/src/main/java/com/nextcloud/talk/utils/DisplayUtils.java
+++ b/app/src/main/java/com/nextcloud/talk/utils/DisplayUtils.java
@@ -636,6 +636,25 @@ public class DisplayUtils {
}
}
+ public static @StringRes
+ int getSortOrderStringId(FileSortOrderNew sortOrder) {
+ switch (sortOrder.name) {
+ case sort_z_to_a_id:
+ return R.string.menu_item_sort_by_name_z_a;
+ case sort_new_to_old_id:
+ return R.string.menu_item_sort_by_date_newest_first;
+ case sort_old_to_new_id:
+ return R.string.menu_item_sort_by_date_oldest_first;
+ case sort_big_to_small_id:
+ return R.string.menu_item_sort_by_size_biggest_first;
+ case sort_small_to_big_id:
+ return R.string.menu_item_sort_by_size_smallest_first;
+ case sort_a_to_z_id:
+ default:
+ return R.string.menu_item_sort_by_name_a_z;
+ }
+ }
+
/**
* calculates the relative time string based on the given modification timestamp.
*
diff --git a/app/src/main/java/com/nextcloud/talk/utils/FileSortOrderByDateNew.java b/app/src/main/java/com/nextcloud/talk/utils/FileSortOrderByDateNew.java
new file mode 100644
index 000000000..bcd2444e1
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/utils/FileSortOrderByDateNew.java
@@ -0,0 +1,52 @@
+/*
+ * Nextcloud Talk application
+ *
+ * @author Sven R. Kunze
+ * @author Andy Scherzinger
+ * Copyright (C) 2021 Andy Scherzinger
+ * Copyright (C) 2017 Sven R. Kunze
+ *
+ * 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.nextcloud.talk.utils;
+
+import com.nextcloud.talk.remotefilebrowser.model.RemoteFileBrowserItem;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Created by srkunze on 28.08.17.
+ */
+public class FileSortOrderByDateNew extends FileSortOrderNew {
+
+ FileSortOrderByDateNew(String name, boolean ascending) {
+ super(name, ascending);
+ }
+
+ /**
+ * Sorts list by Date.
+ *
+ * @param files list of files to sort
+ */
+ public List sortCloudFiles(List files) {
+ final int multiplier = isAscending ? 1 : -1;
+
+ Collections.sort(files, (o1, o2) ->
+ multiplier * Long.compare(o1.getModifiedTimestamp(), o2.getModifiedTimestamp()));
+
+ return super.sortCloudFiles(files);
+ }
+}
diff --git a/app/src/main/java/com/nextcloud/talk/utils/FileSortOrderByNameNew.java b/app/src/main/java/com/nextcloud/talk/utils/FileSortOrderByNameNew.java
new file mode 100644
index 000000000..7d36e6ef4
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/utils/FileSortOrderByNameNew.java
@@ -0,0 +1,63 @@
+/*
+ * Nextcloud Talk application
+ *
+ * @author Sven R. Kunze
+ * @author Andy Scherzinger
+ * Copyright (C) 2021 Andy Scherzinger
+ * Copyright (C) 2017 Sven R. Kunze
+ *
+ * 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.nextcloud.talk.utils;
+
+import com.nextcloud.talk.remotefilebrowser.model.RemoteFileBrowserItem;
+
+import java.util.Collections;
+import java.util.List;
+
+import third_parties.daveKoeller.AlphanumComparator;
+
+/**
+ * Created by srkunze on 28.08.17.
+ */
+public class FileSortOrderByNameNew extends FileSortOrderNew {
+
+ FileSortOrderByNameNew(String name, boolean ascending) {
+ super(name, ascending);
+ }
+
+ /**
+ * Sorts list by Name.
+ *
+ * @param files files to sort
+ */
+ @SuppressWarnings("Bx")
+ public List sortCloudFiles(List files) {
+ final int multiplier = isAscending ? 1 : -1;
+
+ Collections.sort(files, (o1, o2) -> {
+ if (!o1.isFile() && !o2.isFile()) {
+ return multiplier * new AlphanumComparator().compare(o1, o2);
+ } else if (!o1.isFile()) {
+ return -1;
+ } else if (!o2.isFile()) {
+ return 1;
+ }
+ return multiplier * new AlphanumComparator().compare(o1, o2);
+ });
+
+ return super.sortCloudFiles(files);
+ }
+}
diff --git a/app/src/main/java/com/nextcloud/talk/utils/FileSortOrderBySizeNew.java b/app/src/main/java/com/nextcloud/talk/utils/FileSortOrderBySizeNew.java
new file mode 100644
index 000000000..fc071c058
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/utils/FileSortOrderBySizeNew.java
@@ -0,0 +1,61 @@
+/*
+ * Nextcloud Talk application
+ *
+ * @author Sven R. Kunze
+ * @author Andy Scherzinger
+ * Copyright (C) 2021 Andy Scherzinger
+ * Copyright (C) 2017 Sven R. Kunze
+ *
+ * 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.nextcloud.talk.utils;
+
+import com.nextcloud.talk.remotefilebrowser.model.RemoteFileBrowserItem;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Sorts files by sizes
+ */
+public class FileSortOrderBySizeNew extends FileSortOrderNew {
+
+ FileSortOrderBySizeNew(String name, boolean ascending) {
+ super(name, ascending);
+ }
+
+ /**
+ * Sorts list by Size.
+ *
+ * @param files list of files to sort
+ */
+ public List sortCloudFiles(List files) {
+ final int multiplier = isAscending ? 1 : -1;
+
+ Collections.sort(files, (o1, o2) -> {
+ if (!o1.isFile() && !o2.isFile()) {
+ return multiplier * Long.compare(o1.getSize(), o2.getSize());
+ } else if (!o1.isFile()) {
+ return -1;
+ } else if (!o2.isFile()) {
+ return 1;
+ } else {
+ return multiplier * Long.compare(o1.getSize(), o2.getSize());
+ }
+ });
+
+ return super.sortCloudFiles(files);
+ }
+}
diff --git a/app/src/main/java/com/nextcloud/talk/utils/FileSortOrderNew.java b/app/src/main/java/com/nextcloud/talk/utils/FileSortOrderNew.java
new file mode 100644
index 000000000..876fa581b
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/utils/FileSortOrderNew.java
@@ -0,0 +1,108 @@
+/*
+ * Nextcloud Talk application
+ *
+ * @author Sven R. Kunze
+ * @author Andy Scherzinger
+ * Copyright (C) 2021 Andy Scherzinger
+ * Copyright (C) 2017 Sven R. Kunze
+ *
+ * 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.nextcloud.talk.utils;
+
+import android.text.TextUtils;
+
+import com.nextcloud.talk.components.filebrowser.adapters.items.BrowserFileItem;
+import com.nextcloud.talk.remotefilebrowser.model.RemoteFileBrowserItem;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import androidx.annotation.Nullable;
+
+/**
+ * Sort order
+ */
+public class FileSortOrderNew {
+ public static final String sort_a_to_z_id = "sort_a_to_z";
+ public static final String sort_z_to_a_id = "sort_z_to_a";
+ public static final String sort_old_to_new_id = "sort_old_to_new";
+ public static final String sort_new_to_old_id = "sort_new_to_old";
+ public static final String sort_small_to_big_id = "sort_small_to_big";
+ public static final String sort_big_to_small_id = "sort_big_to_small";
+
+ public static final FileSortOrderNew sort_a_to_z = new FileSortOrderByNameNew(sort_a_to_z_id, true);
+ public static final FileSortOrderNew sort_z_to_a = new FileSortOrderByNameNew(sort_z_to_a_id, false);
+ public static final FileSortOrderNew sort_old_to_new = new FileSortOrderByDateNew(sort_old_to_new_id, true);
+ public static final FileSortOrderNew sort_new_to_old = new FileSortOrderByDateNew(sort_new_to_old_id, false);
+ public static final FileSortOrderNew sort_small_to_big = new FileSortOrderBySizeNew(sort_small_to_big_id, true);
+ public static final FileSortOrderNew sort_big_to_small = new FileSortOrderBySizeNew(sort_big_to_small_id, false);
+
+ public static final Map sortOrders;
+
+ static {
+ HashMap temp = new HashMap<>();
+ temp.put(sort_a_to_z.name, sort_a_to_z);
+ temp.put(sort_z_to_a.name, sort_z_to_a);
+ temp.put(sort_old_to_new.name, sort_old_to_new);
+ temp.put(sort_new_to_old.name, sort_new_to_old);
+ temp.put(sort_small_to_big.name, sort_small_to_big);
+ temp.put(sort_big_to_small.name, sort_big_to_small);
+
+ sortOrders = Collections.unmodifiableMap(temp);
+ }
+
+ public String name;
+ public boolean isAscending;
+
+ public FileSortOrderNew(String name, boolean ascending) {
+ this.name = name;
+ isAscending = ascending;
+ }
+
+ public static FileSortOrderNew getFileSortOrder(@Nullable String key) {
+ if (TextUtils.isEmpty(key) || !sortOrders.containsKey(key)) {
+ return sort_a_to_z;
+ } else {
+ return sortOrders.get(key);
+ }
+ }
+
+ public List sortCloudFiles(List files) {
+ return sortCloudFilesByFavourite(files);
+ }
+
+ /**
+ * Sorts list by Favourites.
+ *
+ * @param files files to sort
+ */
+ public static List sortCloudFilesByFavourite(List files) {
+ Collections.sort(files, (o1, o2) -> {
+ if (o1.isFavorite() && o2.isFavorite()) {
+ return 0;
+ } else if (o1.isFavorite()) {
+ return -1;
+ } else if (o2.isFavorite()) {
+ return 1;
+ }
+ return 0;
+ });
+
+ return files;
+ }
+}
diff --git a/app/src/main/java/com/nextcloud/talk/utils/bundle/BundleKeys.kt b/app/src/main/java/com/nextcloud/talk/utils/bundle/BundleKeys.kt
index 02e1b0abe..dad594bdd 100644
--- a/app/src/main/java/com/nextcloud/talk/utils/bundle/BundleKeys.kt
+++ b/app/src/main/java/com/nextcloud/talk/utils/bundle/BundleKeys.kt
@@ -74,4 +74,6 @@ object BundleKeys {
val KEY_FORWARD_HIDE_SOURCE_ROOM = "KEY_FORWARD_HIDE_SOURCE_ROOM"
val KEY_SYSTEM_NOTIFICATION_ID = "KEY_SYSTEM_NOTIFICATION_ID"
const val KEY_MESSAGE_ID = "KEY_MESSAGE_ID"
+ const val KEY_SINGLE_SELECTION = "KEY_SINGLE_SELECTION"
+ const val KEY_MIME_TYPE_FILTER = "KEY_MIME_TYPE_FILTER"
}
diff --git a/app/src/main/res/layout/activity_remote_file_browser.xml b/app/src/main/res/layout/activity_remote_file_browser.xml
new file mode 100644
index 000000000..52db0abfc
--- /dev/null
+++ b/app/src/main/res/layout/activity_remote_file_browser.xml
@@ -0,0 +1,132 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+