Эх сурвалжийг харах

Merge pull request #2086 from nextcloud/chore/noid/RemoteBrowserMigration

Remote files browser migration
Andy Scherzinger 2 жил өмнө
parent
commit
c6f8c60550
48 өөрчлөгдсөн 1592 нэмэгдсэн , 1341 устгасан
  1. 5 1
      app/src/main/AndroidManifest.xml
  2. 0 209
      app/src/main/java/com/nextcloud/talk/components/filebrowser/adapters/items/BrowserFileItem.java
  3. 0 344
      app/src/main/java/com/nextcloud/talk/components/filebrowser/controllers/BrowserController.kt
  4. 0 59
      app/src/main/java/com/nextcloud/talk/components/filebrowser/controllers/BrowserForAvatarController.java
  5. 0 80
      app/src/main/java/com/nextcloud/talk/components/filebrowser/controllers/BrowserForSharingController.java
  6. 0 81
      app/src/main/java/com/nextcloud/talk/components/filebrowser/operations/DavListing.java
  7. 0 48
      app/src/main/java/com/nextcloud/talk/components/filebrowser/operations/ListingAbstractClass.java
  8. 1 1
      app/src/main/java/com/nextcloud/talk/components/filebrowser/webdav/ReadFilesystemOperation.java
  9. 179 0
      app/src/main/java/com/nextcloud/talk/components/filebrowser/webdav/ReadFolderListingOperation.kt
  10. 26 13
      app/src/main/java/com/nextcloud/talk/controllers/ChatController.kt
  11. 33 32
      app/src/main/java/com/nextcloud/talk/controllers/ProfileController.kt
  12. 9 0
      app/src/main/java/com/nextcloud/talk/dagger/modules/RepositoryModule.kt
  13. 6 0
      app/src/main/java/com/nextcloud/talk/dagger/modules/ViewModelModule.kt
  14. 3 5
      app/src/main/java/com/nextcloud/talk/remotefilebrowser/SelectionInterface.kt
  15. 260 0
      app/src/main/java/com/nextcloud/talk/remotefilebrowser/activities/RemoteFileBrowserActivity.kt
  16. 86 0
      app/src/main/java/com/nextcloud/talk/remotefilebrowser/adapters/RemoteFileBrowserItemsAdapter.kt
  17. 153 0
      app/src/main/java/com/nextcloud/talk/remotefilebrowser/adapters/RemoteFileBrowserItemsListViewHolder.kt
  18. 53 0
      app/src/main/java/com/nextcloud/talk/remotefilebrowser/adapters/RemoteFileBrowserItemsViewHolder.kt
  19. 44 0
      app/src/main/java/com/nextcloud/talk/remotefilebrowser/model/RemoteFileBrowserItem.kt
  20. 8 6
      app/src/main/java/com/nextcloud/talk/remotefilebrowser/repositories/RemoteFileBrowserItemsRepository.kt
  21. 56 0
      app/src/main/java/com/nextcloud/talk/remotefilebrowser/repositories/RemoteFileBrowserItemsRepositoryImpl.kt
  22. 230 0
      app/src/main/java/com/nextcloud/talk/remotefilebrowser/viewmodels/RemoteFileBrowserItemsViewModel.kt
  23. 1 2
      app/src/main/java/com/nextcloud/talk/ui/dialog/AttachmentDialog.kt
  24. 21 18
      app/src/main/java/com/nextcloud/talk/ui/dialog/SortingOrderDialogFragment.java
  25. 1 1
      app/src/main/java/com/nextcloud/talk/utils/DisplayUtils.java
  26. 0 108
      app/src/main/java/com/nextcloud/talk/utils/FileSortOrder.java
  27. 95 0
      app/src/main/java/com/nextcloud/talk/utils/FileSortOrder.kt
  28. 0 52
      app/src/main/java/com/nextcloud/talk/utils/FileSortOrderByDate.java
  29. 47 0
      app/src/main/java/com/nextcloud/talk/utils/FileSortOrderByDate.kt
  30. 0 63
      app/src/main/java/com/nextcloud/talk/utils/FileSortOrderByName.java
  31. 57 0
      app/src/main/java/com/nextcloud/talk/utils/FileSortOrderByName.kt
  32. 0 61
      app/src/main/java/com/nextcloud/talk/utils/FileSortOrderBySize.java
  33. 55 0
      app/src/main/java/com/nextcloud/talk/utils/FileSortOrderBySize.kt
  34. 17 0
      app/src/main/java/com/nextcloud/talk/utils/FileUtils.java
  35. 1 0
      app/src/main/java/com/nextcloud/talk/utils/bundle/BundleKeys.kt
  36. 0 1
      app/src/main/java/com/nextcloud/talk/utils/preferences/AppPreferences.java
  37. 0 10
      app/src/main/java/third_parties/daveKoeller/AlphanumComparator.java
  38. 1 1
      app/src/main/res/drawable/round_corner.xml
  39. 132 0
      app/src/main/res/layout/activity_remote_file_browser.xml
  40. 0 100
      app/src/main/res/layout/controller_browser.xml
  41. 6 6
      app/src/main/res/layout/controller_profile.xml
  42. 2 2
      app/src/main/res/layout/rv_item_browser_file.xml
  43. 0 28
      app/src/main/res/menu/file_browser_path.xml
  44. 0 1
      app/src/main/res/values/strings.xml
  45. 1 5
      app/src/main/res/values/styles.xml
  46. 1 1
      detekt.yml
  47. 1 1
      scripts/analysis/findbugs-results.txt
  48. 1 1
      scripts/analysis/lint-results.txt

+ 5 - 1
app/src/main/AndroidManifest.xml

@@ -170,7 +170,11 @@
 
 
         <activity
         <activity
             android:name=".shareditems.activities.SharedItemsActivity"
             android:name=".shareditems.activities.SharedItemsActivity"
-            android:theme="@style/AppTheme"/>
+            android:theme="@style/AppTheme" />
+
+        <activity
+            android:name=".remotefilebrowser.activities.RemoteFileBrowserActivity"
+            android:theme="@style/AppTheme" />
 
 
         <activity
         <activity
             android:name=".messagesearch.MessageSearchActivity"
             android:name=".messagesearch.MessageSearchActivity"

+ 0 - 209
app/src/main/java/com/nextcloud/talk/components/filebrowser/adapters/items/BrowserFileItem.java

@@ -1,209 +0,0 @@
-/*
- * Nextcloud Talk application
- *
- * @author Mario Danic
- * @author Andy Scherzinger
- * Copyright (C) 2022 Andy Scherzinger <info@andy-scherzinger.de>
- * Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
- *
- * 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 <http://www.gnu.org/licenses/>.
- */
-
-package com.nextcloud.talk.components.filebrowser.adapters.items;
-
-import android.content.Context;
-import android.text.format.Formatter;
-import android.view.View;
-import android.widget.CheckBox;
-import android.widget.Toast;
-
-import com.facebook.drawee.backends.pipeline.Fresco;
-import com.facebook.drawee.interfaces.DraweeController;
-import com.nextcloud.talk.R;
-import com.nextcloud.talk.application.NextcloudTalkApplication;
-import com.nextcloud.talk.components.filebrowser.models.BrowserFile;
-import com.nextcloud.talk.databinding.RvItemBrowserFileBinding;
-import com.nextcloud.talk.interfaces.SelectionInterface;
-import com.nextcloud.talk.models.database.UserEntity;
-import com.nextcloud.talk.utils.ApiUtils;
-import com.nextcloud.talk.utils.DateUtils;
-import com.nextcloud.talk.utils.DisplayUtils;
-import com.nextcloud.talk.utils.DrawableUtils;
-
-import java.util.List;
-
-import javax.inject.Inject;
-
-import androidx.appcompat.content.res.AppCompatResources;
-import autodagger.AutoInjector;
-import eu.davidea.flexibleadapter.FlexibleAdapter;
-import eu.davidea.flexibleadapter.items.AbstractFlexibleItem;
-import eu.davidea.flexibleadapter.items.IFilterable;
-import eu.davidea.flexibleadapter.items.IFlexible;
-import eu.davidea.viewholders.FlexibleViewHolder;
-
-@AutoInjector(NextcloudTalkApplication.class)
-public class BrowserFileItem extends AbstractFlexibleItem<BrowserFileItem.BrowserFileItemViewHolder> implements IFilterable<String> {
-    @Inject
-    Context context;
-    private final BrowserFile browserFile;
-    private final UserEntity activeUser;
-    private final SelectionInterface selectionInterface;
-    private boolean selected;
-
-    public BrowserFileItem(BrowserFile browserFile, UserEntity activeUser, SelectionInterface selectionInterface) {
-        this.browserFile = browserFile;
-        this.activeUser = activeUser;
-        this.selectionInterface = selectionInterface;
-        NextcloudTalkApplication.Companion.getSharedApplication().getComponentApplication().inject(this);
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (o instanceof BrowserFileItem) {
-            BrowserFileItem inItem = (BrowserFileItem) o;
-            return browserFile.getPath().equals(inItem.getModel().getPath());
-        }
-
-        return false;
-    }
-
-    public BrowserFile getModel() {
-        return browserFile;
-    }
-
-    @Override
-    public int getLayoutRes() {
-        return R.layout.rv_item_browser_file;
-    }
-
-    @Override
-    public BrowserFileItemViewHolder createViewHolder(View view, FlexibleAdapter<IFlexible> adapter) {
-        return new BrowserFileItemViewHolder(view, adapter);
-    }
-
-    private boolean isSelected() {
-        return selected;
-    }
-
-    private void setSelected(boolean selected) {
-        this.selected = selected;
-    }
-
-    @Override
-    public void bindViewHolder(FlexibleAdapter<IFlexible> adapter,
-                               BrowserFileItemViewHolder holder,
-                               int position,
-                               List<Object> payloads) {
-        holder.binding.fileIcon.setController(null);
-        if (!browserFile.isAllowedToReShare() || browserFile.isEncrypted()) {
-            holder.itemView.setEnabled(false);
-            holder.itemView.setAlpha(0.38f);
-        } else {
-            holder.itemView.setEnabled(true);
-            holder.itemView.setAlpha(1.0f);
-        }
-
-        if (browserFile.isEncrypted()) {
-            holder.binding.fileEncryptedImageView.setVisibility(View.VISIBLE);
-
-        } else {
-            holder.binding.fileEncryptedImageView.setVisibility(View.GONE);
-        }
-
-        if (browserFile.isFavorite()) {
-            holder.binding.fileFavoriteImageView.setVisibility(View.VISIBLE);
-        } else {
-            holder.binding.fileFavoriteImageView.setVisibility(View.GONE);
-        }
-
-        if (selectionInterface.shouldOnlySelectOneImageFile()) {
-            if (browserFile.isFile() && browserFile.getMimeType().startsWith("image/")) {
-                holder.binding.selectFileCheckbox.setVisibility(View.VISIBLE);
-            } else {
-                holder.binding.selectFileCheckbox.setVisibility(View.GONE);
-            }
-        } else {
-            holder.binding.selectFileCheckbox.setVisibility(View.VISIBLE);
-        }
-
-        if (context != null) {
-            holder
-                .binding
-                .fileIcon
-                .getHierarchy()
-                .setPlaceholderImage(
-                    AppCompatResources.getDrawable(
-                        context, DrawableUtils.INSTANCE.getDrawableResourceIdForMimeType(browserFile.getMimeType())));
-        }
-
-        if (browserFile.getHasPreview()) {
-            String path = ApiUtils.getUrlForFilePreviewWithRemotePath(activeUser.getBaseUrl(),
-                    browserFile.getPath(),
-                    context.getResources().getDimensionPixelSize(R.dimen.small_item_height));
-
-            if (path.length() > 0) {
-                DraweeController draweeController = Fresco.newDraweeControllerBuilder()
-                        .setAutoPlayAnimations(true)
-                        .setImageRequest(DisplayUtils.getImageRequestForUrl(path, null))
-                        .build();
-                holder.binding.fileIcon.setController(draweeController);
-            }
-        }
-
-        holder.binding.filenameTextView.setText(browserFile.getDisplayName());
-        holder.binding.fileModifiedInfo.setText(String.format(context.getString(R.string.nc_last_modified),
-                Formatter.formatShortFileSize(context, browserFile.getSize()),
-                DateUtils.INSTANCE.getLocalDateTimeStringFromTimestamp(browserFile.getModifiedTimestamp())));
-        setSelected(selectionInterface.isPathSelected(browserFile.getPath()));
-        holder.binding.selectFileCheckbox.setChecked(isSelected());
-
-        if (!browserFile.isEncrypted()) {
-            holder.binding.selectFileCheckbox.setOnClickListener(new View.OnClickListener() {
-                @Override
-                public void onClick(View v) {
-                    if (!browserFile.isAllowedToReShare()) {
-                        ((CheckBox) v).setChecked(false);
-                        Toast.makeText(
-                            context,
-                            context.getResources().getString(R.string.nc_file_browser_reshare_forbidden),
-                            Toast.LENGTH_LONG)
-                            .show();
-                    } else if (((CheckBox) v).isChecked() != isSelected()) {
-                        setSelected(((CheckBox) v).isChecked());
-                        selectionInterface.toggleBrowserItemSelection(browserFile.getPath());
-                    }
-                }
-            });
-        }
-
-        holder.binding.filenameTextView.setSelected(true);
-        holder.binding.fileModifiedInfo.setSelected(true);
-    }
-
-    @Override
-    public boolean filter(String constraint) {
-        return false;
-    }
-
-    static class BrowserFileItemViewHolder extends FlexibleViewHolder {
-
-        RvItemBrowserFileBinding binding;
-
-        BrowserFileItemViewHolder(View view, FlexibleAdapter adapter) {
-            super(view, adapter);
-            binding = RvItemBrowserFileBinding.bind(view);
-        }
-    }
-}

+ 0 - 344
app/src/main/java/com/nextcloud/talk/components/filebrowser/controllers/BrowserController.kt

@@ -1,344 +0,0 @@
-/*
- * Nextcloud Talk application
- *
- * @author Mario Danic
- * @author Andy Scherzinger
- * Copyright (C) 2021 Andy Scherzinger <info@andy-scherzinger.de>
- * Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
- *
- * 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 <http://www.gnu.org/licenses/>.
- */
-package com.nextcloud.talk.components.filebrowser.controllers
-
-import android.annotation.SuppressLint
-import android.os.Bundle
-import android.os.Parcelable
-import android.util.Log
-import android.view.Menu
-import android.view.MenuInflater
-import android.view.MenuItem
-import android.view.View
-import androidx.fragment.app.DialogFragment
-import androidx.recyclerview.widget.RecyclerView
-import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
-import autodagger.AutoInjector
-import com.nextcloud.talk.R
-import com.nextcloud.talk.activities.MainActivity
-import com.nextcloud.talk.application.NextcloudTalkApplication
-import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
-import com.nextcloud.talk.components.filebrowser.adapters.items.BrowserFileItem
-import com.nextcloud.talk.components.filebrowser.interfaces.ListingInterface
-import com.nextcloud.talk.components.filebrowser.models.BrowserFile
-import com.nextcloud.talk.components.filebrowser.models.DavResponse
-import com.nextcloud.talk.components.filebrowser.operations.DavListing
-import com.nextcloud.talk.components.filebrowser.operations.ListingAbstractClass
-import com.nextcloud.talk.controllers.base.NewBaseController
-import com.nextcloud.talk.controllers.util.viewBinding
-import com.nextcloud.talk.databinding.ControllerBrowserBinding
-import com.nextcloud.talk.interfaces.SelectionInterface
-import com.nextcloud.talk.models.database.UserEntity
-import com.nextcloud.talk.ui.dialog.SortingOrderDialogFragment
-import com.nextcloud.talk.utils.DisplayUtils
-import com.nextcloud.talk.utils.FileSortOrder
-import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_BROWSER_TYPE
-import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_USER_ENTITY
-import com.nextcloud.talk.utils.database.user.UserUtils
-import eu.davidea.flexibleadapter.FlexibleAdapter
-import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager
-import kotlinx.android.parcel.Parcelize
-import net.orange_box.storebox.listeners.OnPreferenceValueChangedListener
-import okhttp3.OkHttpClient
-import org.parceler.Parcels
-import java.io.File
-import java.util.ArrayList
-import java.util.Collections
-import java.util.TreeSet
-import javax.inject.Inject
-
-@AutoInjector(NextcloudTalkApplication::class)
-abstract class BrowserController(args: Bundle) :
-    NewBaseController(
-        R.layout.controller_browser,
-        args
-    ),
-    ListingInterface,
-    FlexibleAdapter.OnItemClickListener,
-    SwipeRefreshLayout.OnRefreshListener,
-    SelectionInterface {
-
-    private val binding: ControllerBrowserBinding by viewBinding(ControllerBrowserBinding::bind)
-
-    @JvmField
-    protected val selectedPaths: MutableSet<String>
-
-    @JvmField
-    @Inject
-    var userUtils: UserUtils? = null
-
-    @JvmField
-    @Inject
-    var okHttpClient: OkHttpClient? = null
-
-    @JvmField
-    protected var activeUser: UserEntity
-
-    private var filesSelectionDoneMenuItem: MenuItem? = null
-    private var layoutManager: RecyclerView.LayoutManager? = null
-    private var adapter: FlexibleAdapter<BrowserFileItem>? = null
-    private var recyclerViewItems: List<BrowserFileItem> = ArrayList()
-    private var listingAbstractClass: ListingAbstractClass? = null
-    private val browserType: BrowserType
-    private var currentPath: String
-
-    private var sortingChangeListener: OnPreferenceValueChangedListener<String>? = null
-
-    override fun onViewBound(view: View) {
-        super.onViewBound(view)
-        if (adapter == null) {
-            adapter = FlexibleAdapter(recyclerViewItems, context, false)
-        }
-
-        appPreferences!!.registerSortingChangeListener(
-            SortingChangeListener(this).also {
-                sortingChangeListener = it
-            }
-        )
-
-        changeEnabledStatusForBarItems(true)
-        prepareViews()
-    }
-
-    abstract fun onFileSelectionDone()
-    override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
-        super.onCreateOptionsMenu(menu, inflater)
-        inflater.inflate(R.menu.menu_share_files, menu)
-        filesSelectionDoneMenuItem = menu.findItem(R.id.files_selection_done)
-        filesSelectionDoneMenuItem?.isVisible = selectedPaths.size > 0
-    }
-
-    override fun onOptionsItemSelected(item: MenuItem): Boolean {
-        if (item.itemId == R.id.files_selection_done) {
-            onFileSelectionDone()
-            return true
-        }
-        return super.onOptionsItemSelected(item)
-    }
-
-    override fun onAttach(view: View) {
-        super.onAttach(view)
-
-        binding.pathNavigation.menu.findItem(R.id.action_back)?.setOnMenuItemClickListener { goBack() }
-        binding.sortButton.setOnClickListener { changeSorting() }
-
-        binding.sortButton.setText(
-            DisplayUtils.getSortOrderStringId(FileSortOrder.getFileSortOrder(appPreferences?.sorting))
-        )
-
-        refreshCurrentPath()
-    }
-
-    override fun onRefresh() {
-        refreshCurrentPath()
-    }
-
-    fun changeSorting() {
-        val newFragment: DialogFragment = SortingOrderDialogFragment
-            .newInstance(FileSortOrder.getFileSortOrder(appPreferences?.sorting))
-        newFragment.show(
-            (activity as MainActivity?)!!.supportFragmentManager,
-            SortingOrderDialogFragment.SORTING_ORDER_FRAGMENT
-        )
-    }
-
-    public override fun onDestroy() {
-        super.onDestroy()
-        listingAbstractClass!!.tearDown()
-    }
-
-    override val title: String
-        get() =
-            currentPath
-
-    fun goBack(): Boolean {
-        fetchPath(File(currentPath).parent)
-        return true
-    }
-
-    fun refreshCurrentPath(): Boolean {
-        fetchPath(currentPath)
-        return true
-    }
-
-    @SuppressLint("RestrictedApi")
-    private fun changeEnabledStatusForBarItems(shouldBeEnabled: Boolean) {
-        binding.pathNavigation.menu.findItem(R.id.action_back)?.isEnabled = shouldBeEnabled && currentPath != "/"
-    }
-
-    private fun fetchPath(path: String) {
-        listingAbstractClass!!.cancelAllJobs()
-        changeEnabledStatusForBarItems(false)
-        listingAbstractClass!!.getFiles(
-            path,
-            activeUser,
-            if (BrowserType.DAV_BROWSER == browserType) okHttpClient else null
-        )
-    }
-
-    override fun listingResult(davResponse: DavResponse) {
-        recyclerViewItems = ArrayList()
-        if (davResponse.getData() != null) {
-            val objectList = davResponse.getData() as List<BrowserFile>
-            currentPath = objectList[0].path!!
-            if (activity != null) {
-                activity!!.runOnUiThread { setTitle() }
-            }
-            for (i in 1 until objectList.size) {
-                (recyclerViewItems as ArrayList<BrowserFileItem>).add(BrowserFileItem(objectList[i], activeUser, this))
-            }
-        }
-
-        FileSortOrder.getFileSortOrder(appPreferences?.sorting).sortCloudFiles(recyclerViewItems)
-
-        if (activity != null) {
-            activity!!.runOnUiThread {
-                adapter!!.clear()
-                adapter!!.addItems(0, recyclerViewItems)
-                adapter!!.notifyDataSetChanged()
-                changeEnabledStatusForBarItems(true)
-            }
-        }
-
-        binding.swipeRefreshList.isRefreshing = false
-    }
-
-    private fun shouldPathBeSelectedDueToParent(currentPath: String): Boolean {
-        if (selectedPaths.size > 0) {
-            var file = File(currentPath)
-            if (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!!)
-        }
-        if (activity != null) {
-            activity!!.runOnUiThread {
-                adapter!!.notifyDataSetChanged()
-            }
-        }
-    }
-
-    override fun onItemClick(view: View, position: Int): Boolean {
-        val browserFile = (adapter!!.getItem(position) as BrowserFileItem).model
-        if ("inode/directory" == browserFile.mimeType) {
-            fetchPath(browserFile.path!!)
-            return true
-        }
-        return false
-    }
-
-    private fun prepareViews() {
-        if (activity != null) {
-            layoutManager = SmoothScrollLinearLayoutManager(activity)
-            binding.recyclerView.layoutManager = layoutManager
-            binding.recyclerView.setHasFixedSize(true)
-            binding.recyclerView.adapter = adapter
-            adapter!!.addListener(this)
-
-            binding.swipeRefreshList.setOnRefreshListener(this)
-            binding.swipeRefreshList.setColorSchemeResources(R.color.colorPrimary)
-            binding.swipeRefreshList.setProgressBackgroundColorSchemeResource(R.color.refresh_spinner_background)
-        }
-    }
-
-    @SuppressLint("RestrictedApi")
-    override fun toggleBrowserItemSelection(path: String) {
-        if (selectedPaths.contains(path) || shouldPathBeSelectedDueToParent(path)) {
-            checkAndRemoveAnySelectedParents(path)
-        } else {
-            // TOOD: 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)
-    }
-
-    abstract override fun shouldOnlySelectOneImageFile(): Boolean
-
-    @Parcelize
-    enum class BrowserType : Parcelable {
-        FILE_BROWSER, DAV_BROWSER
-    }
-
-    init {
-        setHasOptionsMenu(true)
-        sharedApplication!!.componentApplication.inject(this)
-        browserType = Parcels.unwrap(args.getParcelable(KEY_BROWSER_TYPE))
-        activeUser = Parcels.unwrap(args.getParcelable(KEY_USER_ENTITY))
-        currentPath = "/"
-        if (BrowserType.DAV_BROWSER == browserType) {
-            listingAbstractClass = DavListing(this)
-        } // else {
-        // listingAbstractClass = new LocalListing(this);
-        // }
-        selectedPaths = Collections.synchronizedSet(TreeSet())
-    }
-
-    @Suppress("Detekt.TooGenericExceptionCaught")
-    private class SortingChangeListener(private val browserController: BrowserController) :
-        OnPreferenceValueChangedListener<String> {
-        override fun onChanged(newValue: String) {
-            try {
-                val sortOrder = FileSortOrder.getFileSortOrder(newValue)
-
-                browserController.binding.sortButton.setText(DisplayUtils.getSortOrderStringId(sortOrder))
-                browserController.recyclerViewItems = sortOrder.sortCloudFiles(browserController.recyclerViewItems)
-
-                if (browserController.activity != null) {
-                    browserController.activity!!.runOnUiThread {
-                        browserController.adapter!!.updateDataSet(browserController.recyclerViewItems)
-                        browserController.changeEnabledStatusForBarItems(true)
-                    }
-                }
-            } catch (npe: NullPointerException) {
-                // view binding can be null
-                // since this is called asynchronously and UI might have been destroyed in the meantime
-                Log.i(BrowserController.TAG, "UI destroyed - view binding already gone")
-            }
-        }
-    }
-
-    companion object {
-        private const val TAG = "BrowserController"
-    }
-}

+ 0 - 59
app/src/main/java/com/nextcloud/talk/components/filebrowser/controllers/BrowserForAvatarController.java

@@ -1,59 +0,0 @@
-/*
- * Nextcloud Talk application
- *
- * @author Tobias Kaminsky
- * Copyright (C) 2021 Tobias Kaminsky <tobias.kaminsky@nextcloud.com>
- *
- * 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 <http://www.gnu.org/licenses/>.
- */
-
-package com.nextcloud.talk.components.filebrowser.controllers;
-
-import android.content.Intent;
-import android.os.Bundle;
-
-import com.nextcloud.talk.controllers.ProfileController;
-
-import androidx.annotation.Nullable;
-
-public class BrowserForAvatarController extends BrowserController {
-    private ProfileController controller;
-
-    public BrowserForAvatarController(Bundle args) {
-        super(args);
-    }
-
-    public BrowserForAvatarController(Bundle args, ProfileController controller) {
-        super(args);
-
-        this.controller = controller;
-    }
-
-    @Override
-    public void onFileSelectionDone() {
-        controller.handleAvatar(selectedPaths.iterator().next());
-
-        getRouter().popCurrentController();
-    }
-
-    @Override
-    public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
-        super.onActivityResult(requestCode, resultCode, data);
-    }
-
-    @Override
-    public boolean shouldOnlySelectOneImageFile() {
-        return true;
-    }
-}

+ 0 - 80
app/src/main/java/com/nextcloud/talk/components/filebrowser/controllers/BrowserForSharingController.java

@@ -1,80 +0,0 @@
-/*
- * Nextcloud Talk application
- *
- * @author Tobias Kaminsky
- * Copyright (C) 2021 Tobias Kaminsky <tobias.kaminsky@nextcloud.com>
- *
- * 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 <http://www.gnu.org/licenses/>.
- */
-
-package com.nextcloud.talk.components.filebrowser.controllers;
-
-import android.os.Bundle;
-
-import com.nextcloud.talk.jobs.ShareOperationWorker;
-import com.nextcloud.talk.utils.bundle.BundleKeys;
-
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.List;
-
-import androidx.work.Data;
-import androidx.work.OneTimeWorkRequest;
-import androidx.work.WorkManager;
-
-public class BrowserForSharingController extends BrowserController {
-    private final String roomToken;
-
-    public BrowserForSharingController(Bundle args) {
-        super(args);
-
-        roomToken = args.getString(BundleKeys.INSTANCE.getKEY_ROOM_TOKEN());
-    }
-
-    @Override
-    public void onFileSelectionDone() {
-        synchronized (selectedPaths) {
-            Iterator<String> iterator = selectedPaths.iterator();
-
-            List<String> paths = new ArrayList<>();
-            Data data;
-            OneTimeWorkRequest shareWorker;
-
-            while (iterator.hasNext()) {
-                String path = iterator.next();
-                paths.add(path);
-                iterator.remove();
-                if (paths.size() == 10 || !iterator.hasNext()) {
-                    data = new Data.Builder()
-                            .putLong(BundleKeys.INSTANCE.getKEY_INTERNAL_USER_ID(), activeUser.getId())
-                            .putString(BundleKeys.INSTANCE.getKEY_ROOM_TOKEN(), roomToken)
-                            .putStringArray(BundleKeys.INSTANCE.getKEY_FILE_PATHS(), paths.toArray(new String[0]))
-                            .build();
-                    shareWorker = new OneTimeWorkRequest.Builder(ShareOperationWorker.class)
-                            .setInputData(data)
-                            .build();
-                    WorkManager.getInstance().enqueue(shareWorker);
-                    paths = new ArrayList<>();
-                }
-            }
-        }
-
-        getRouter().popCurrentController();
-    }
-
-    @Override
-    public boolean shouldOnlySelectOneImageFile() {
-        return false;
-    }
-}

+ 0 - 81
app/src/main/java/com/nextcloud/talk/components/filebrowser/operations/DavListing.java

@@ -1,81 +0,0 @@
-/*
- * Nextcloud Talk application
- *
- * @author Mario Danic
- * @author Andy Scherzinger
- * Copyright (C) 2022 Andy Scherzinger <info@andy-scherzinger.de>
- * Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
- *
- * 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 <http://www.gnu.org/licenses/>.
- */
-
-package com.nextcloud.talk.components.filebrowser.operations;
-
-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.models.database.UserEntity;
-
-import java.util.concurrent.Callable;
-
-import androidx.annotation.Nullable;
-import io.reactivex.Single;
-import io.reactivex.SingleObserver;
-import io.reactivex.annotations.NonNull;
-import io.reactivex.disposables.Disposable;
-import io.reactivex.schedulers.Schedulers;
-import okhttp3.OkHttpClient;
-
-public class DavListing extends ListingAbstractClass {
-    private static final String TAG = DavListing.class.getSimpleName();
-
-    private DavResponse davResponse = new DavResponse();
-
-    public DavListing(ListingInterface listingInterface) {
-        super(listingInterface);
-    }
-
-    @Override
-    public void getFiles(String path, UserEntity currentUser, @Nullable OkHttpClient okHttpClient) {
-        Single.fromCallable(new Callable<ReadFilesystemOperation>() {
-            @Override
-            public ReadFilesystemOperation call() {
-                return new ReadFilesystemOperation(okHttpClient, currentUser, path, 1);
-            }
-        }).subscribeOn(Schedulers.io())
-                .subscribe(new SingleObserver<ReadFilesystemOperation>() {
-                    @Override
-                    public void onSubscribe(@NonNull Disposable d) {
-
-                    }
-
-                    @Override
-                    public void onSuccess(@NonNull ReadFilesystemOperation readFilesystemOperation) {
-                        davResponse = readFilesystemOperation.readRemotePath();
-                        try {
-                            listingInterface.listingResult(davResponse);
-                        } catch (NullPointerException npe) {
-                            Log.i(TAG, "Error loading remote folder - due to view already been terminated", npe);
-                        }
-                    }
-
-                    @Override
-                    public void onError(@NonNull Throwable e) {
-                        listingInterface.listingResult(davResponse);
-                    }
-                });
-    }
-}

+ 0 - 48
app/src/main/java/com/nextcloud/talk/components/filebrowser/operations/ListingAbstractClass.java

@@ -1,48 +0,0 @@
-/*
- * Nextcloud Talk application
- *
- * @author Mario Danic
- * Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
- *
- * 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 <http://www.gnu.org/licenses/>.
- */
-
-package com.nextcloud.talk.components.filebrowser.operations;
-
-import android.os.Handler;
-import androidx.annotation.Nullable;
-import com.nextcloud.talk.components.filebrowser.interfaces.ListingInterface;
-import com.nextcloud.talk.models.database.UserEntity;
-import okhttp3.OkHttpClient;
-
-public abstract class ListingAbstractClass {
-    Handler handler;
-    ListingInterface listingInterface;
-
-    ListingAbstractClass(ListingInterface listingInterface) {
-        handler = new Handler();
-        this.listingInterface = listingInterface;
-    }
-
-    public abstract void getFiles(String path, UserEntity currentUser, @Nullable OkHttpClient okHttpClient);
-
-    public void cancelAllJobs() {
-        handler.removeCallbacksAndMessages(null);
-    }
-
-    public void tearDown() {
-        cancelAllJobs();
-        handler = null;
-    }
-}

+ 1 - 1
app/src/main/java/com/nextcloud/talk/components/filebrowser/webdav/ReadFilesystemOperation.java

@@ -91,7 +91,7 @@ public class ReadFilesystemOperation {
                         }
                         }
                     });
                     });
         } catch (IOException | DavException e) {
         } catch (IOException | DavException e) {
-            Log.w("", "Error reading remote path");
+            Log.w(TAG, "Error reading remote path");
         }
         }
 
 
         remoteFiles.add(BrowserFile.Companion.getModelFromResponse(rootElement[0],
         remoteFiles.add(BrowserFile.Companion.getModelFromResponse(rootElement[0],

+ 179 - 0
app/src/main/java/com/nextcloud/talk/components/filebrowser/webdav/ReadFolderListingOperation.kt

@@ -0,0 +1,179 @@
+/*
+ * Nextcloud Talk application
+ *
+ * @author Andy Scherzinger
+ * @author Mario Danic
+ * Copyright (C) 2022 Andy Scherzinger <info@andy-scherzinger.de>
+ * Copyright (C) 2017-2019 Mario Danic <mario@lovelyhq.com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+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 ReadFolderListingOperation(okHttpClient: OkHttpClient, currentUser: UserEntity, path: String, depth: Int) {
+    private val okHttpClient: OkHttpClient
+    private val url: String
+    private val depth: Int
+    private val basePath: String
+
+    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
+    }
+
+    fun readRemotePath(): DavResponse {
+        val davResponse = DavResponse()
+        val memberElements: MutableList<Response> = ArrayList()
+        val rootElement = arrayOfNulls<Response>(1)
+        val remoteFiles: MutableList<RemoteFileBrowserItem> = 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")
+        }
+        for (memberElement in memberElements) {
+            remoteFiles.add(
+                getModelFromResponse(
+                    memberElement,
+                    memberElement
+                        .href
+                        .toString()
+                        .substring(basePath.length)
+                )
+            )
+        }
+        davResponse.setData(remoteFiles)
+        return davResponse
+    }
+
+    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(READ_PERMISSION)
+        ) {
+            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
+            }
+        }
+    }
+
+    companion object {
+        private const val TAG = "ReadFilesystemOperation"
+        private const val READ_PERMISSION = "R"
+    }
+}

+ 26 - 13
app/src/main/java/com/nextcloud/talk/controllers/ChatController.kt

@@ -91,7 +91,6 @@ import autodagger.AutoInjector
 import coil.load
 import coil.load
 import com.bluelinelabs.conductor.RouterTransaction
 import com.bluelinelabs.conductor.RouterTransaction
 import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler
 import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler
-import com.bluelinelabs.conductor.changehandler.VerticalChangeHandler
 import com.facebook.common.executors.UiThreadImmediateExecutorService
 import com.facebook.common.executors.UiThreadImmediateExecutorService
 import com.facebook.common.references.CloseableReference
 import com.facebook.common.references.CloseableReference
 import com.facebook.datasource.DataSource
 import com.facebook.datasource.DataSource
@@ -121,14 +120,13 @@ import com.nextcloud.talk.adapters.messages.VoiceMessageInterface
 import com.nextcloud.talk.api.NcApi
 import com.nextcloud.talk.api.NcApi
 import com.nextcloud.talk.application.NextcloudTalkApplication
 import com.nextcloud.talk.application.NextcloudTalkApplication
 import com.nextcloud.talk.callbacks.MentionAutocompleteCallback
 import com.nextcloud.talk.callbacks.MentionAutocompleteCallback
-import com.nextcloud.talk.components.filebrowser.controllers.BrowserController
-import com.nextcloud.talk.components.filebrowser.controllers.BrowserForSharingController
 import com.nextcloud.talk.controllers.base.NewBaseController
 import com.nextcloud.talk.controllers.base.NewBaseController
 import com.nextcloud.talk.controllers.util.viewBinding
 import com.nextcloud.talk.controllers.util.viewBinding
 import com.nextcloud.talk.databinding.ControllerChatBinding
 import com.nextcloud.talk.databinding.ControllerChatBinding
 import com.nextcloud.talk.events.UserMentionClickEvent
 import com.nextcloud.talk.events.UserMentionClickEvent
 import com.nextcloud.talk.events.WebSocketCommunicationEvent
 import com.nextcloud.talk.events.WebSocketCommunicationEvent
 import com.nextcloud.talk.jobs.DownloadFileToCacheWorker
 import com.nextcloud.talk.jobs.DownloadFileToCacheWorker
+import com.nextcloud.talk.jobs.ShareOperationWorker
 import com.nextcloud.talk.jobs.UploadAndShareFilesWorker
 import com.nextcloud.talk.jobs.UploadAndShareFilesWorker
 import com.nextcloud.talk.messagesearch.MessageSearchActivity
 import com.nextcloud.talk.messagesearch.MessageSearchActivity
 import com.nextcloud.talk.models.database.CapabilitiesUtil
 import com.nextcloud.talk.models.database.CapabilitiesUtil
@@ -143,6 +141,7 @@ import com.nextcloud.talk.models.json.conversations.RoomsOverall
 import com.nextcloud.talk.models.json.generic.GenericOverall
 import com.nextcloud.talk.models.json.generic.GenericOverall
 import com.nextcloud.talk.models.json.mention.Mention
 import com.nextcloud.talk.models.json.mention.Mention
 import com.nextcloud.talk.presenters.MentionAutocompletePresenter
 import com.nextcloud.talk.presenters.MentionAutocompletePresenter
+import com.nextcloud.talk.remotefilebrowser.activities.RemoteFileBrowserActivity
 import com.nextcloud.talk.shareditems.activities.SharedItemsActivity
 import com.nextcloud.talk.shareditems.activities.SharedItemsActivity
 import com.nextcloud.talk.ui.bottom.sheet.ProfileBottomSheet
 import com.nextcloud.talk.ui.bottom.sheet.ProfileBottomSheet
 import com.nextcloud.talk.ui.dialog.AttachmentDialog
 import com.nextcloud.talk.ui.dialog.AttachmentDialog
@@ -164,6 +163,8 @@ import com.nextcloud.talk.utils.UriUtils
 import com.nextcloud.talk.utils.bundle.BundleKeys
 import com.nextcloud.talk.utils.bundle.BundleKeys
 import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ACTIVE_CONVERSATION
 import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ACTIVE_CONVERSATION
 import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_CONVERSATION_NAME
 import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_CONVERSATION_NAME
+import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_FILE_PATHS
+import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_INTERNAL_USER_ID
 import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_ID
 import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_ID
 import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_TOKEN
 import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_TOKEN
 import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_USER_ENTITY
 import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_USER_ENTITY
@@ -1352,6 +1353,24 @@ class ChatController(args: Bundle) :
         }
         }
 
 
         when (requestCode) {
         when (requestCode) {
+            REQUEST_CODE_SELECT_REMOTE_FILES -> {
+                val pathList = intent?.getStringArrayListExtra(RemoteFileBrowserActivity.EXTRA_SELECTED_PATHS)
+                if (pathList?.size!! >= 1) {
+                    pathList
+                        .chunked(10)
+                        .forEach { paths ->
+                            val data = Data.Builder()
+                                .putLong(KEY_INTERNAL_USER_ID, conversationUser!!.id)
+                                .putString(KEY_ROOM_TOKEN, roomToken)
+                                .putStringArray(KEY_FILE_PATHS, paths.toTypedArray())
+                                .build()
+                            val worker = OneTimeWorkRequest.Builder(ShareOperationWorker::class.java)
+                                .setInputData(data)
+                                .build()
+                            WorkManager.getInstance().enqueue(worker)
+                        }
+                }
+            }
             REQUEST_CODE_CHOOSE_FILE -> {
             REQUEST_CODE_CHOOSE_FILE -> {
                 try {
                 try {
                     checkNotNull(intent)
                     checkNotNull(intent)
@@ -1606,16 +1625,9 @@ class ChatController(args: Bundle) :
         requestReadContacts()
         requestReadContacts()
     }
     }
 
 
-    fun showBrowserScreen(browserType: BrowserController.BrowserType) {
-        val bundle = Bundle()
-        bundle.putParcelable(BundleKeys.KEY_BROWSER_TYPE, Parcels.wrap<BrowserController.BrowserType>(browserType))
-        bundle.putParcelable(BundleKeys.KEY_USER_ENTITY, Parcels.wrap<UserEntity>(conversationUser))
-        bundle.putString(KEY_ROOM_TOKEN, roomToken)
-        router.pushController(
-            RouterTransaction.with(BrowserForSharingController(bundle))
-                .pushChangeHandler(VerticalChangeHandler())
-                .popChangeHandler(VerticalChangeHandler())
-        )
+    fun showBrowserScreen() {
+        val sharingFileBrowserIntent = Intent(activity, RemoteFileBrowserActivity::class.java)
+        startActivityForResult(sharingFileBrowserIntent, REQUEST_CODE_SELECT_REMOTE_FILES)
     }
     }
 
 
     fun showShareLocationScreen() {
     fun showShareLocationScreen() {
@@ -3142,6 +3154,7 @@ class ChatController(args: Bundle) :
         private const val REQUEST_READ_CONTACT_PERMISSION = 234
         private const val REQUEST_READ_CONTACT_PERMISSION = 234
         private const val REQUEST_CAMERA_PERMISSION = 223
         private const val REQUEST_CAMERA_PERMISSION = 223
         private const val REQUEST_CODE_PICK_CAMERA: Int = 333
         private const val REQUEST_CODE_PICK_CAMERA: Int = 333
+        private const val REQUEST_CODE_SELECT_REMOTE_FILES = 888
         private const val OBJECT_MESSAGE: String = "{object}"
         private const val OBJECT_MESSAGE: String = "{object}"
         private const val MINIMUM_VOICE_RECORD_DURATION: Int = 1000
         private const val MINIMUM_VOICE_RECORD_DURATION: Int = 1000
         private const val VOICE_RECORD_CANCEL_SLIDER_X: Int = -50
         private const val VOICE_RECORD_CANCEL_SLIDER_X: Int = -50

+ 33 - 32
app/src/main/java/com/nextcloud/talk/controllers/ProfileController.kt

@@ -29,7 +29,6 @@ import android.graphics.BitmapFactory
 import android.graphics.Color
 import android.graphics.Color
 import android.net.Uri
 import android.net.Uri
 import android.os.Bundle
 import android.os.Bundle
-import android.os.Environment
 import android.text.Editable
 import android.text.Editable
 import android.text.TextUtils
 import android.text.TextUtils
 import android.text.TextWatcher
 import android.text.TextWatcher
@@ -48,8 +47,6 @@ import androidx.core.graphics.drawable.DrawableCompat
 import androidx.core.view.ViewCompat
 import androidx.core.view.ViewCompat
 import androidx.recyclerview.widget.RecyclerView
 import androidx.recyclerview.widget.RecyclerView
 import autodagger.AutoInjector
 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
 import com.github.dhaval2404.imagepicker.ImagePicker.Companion.getError
 import com.github.dhaval2404.imagepicker.ImagePicker.Companion.getError
 import com.github.dhaval2404.imagepicker.ImagePicker.Companion.getFile
 import com.github.dhaval2404.imagepicker.ImagePicker.Companion.getFile
@@ -58,8 +55,6 @@ import com.nextcloud.talk.R
 import com.nextcloud.talk.api.NcApi
 import com.nextcloud.talk.api.NcApi
 import com.nextcloud.talk.application.NextcloudTalkApplication
 import com.nextcloud.talk.application.NextcloudTalkApplication
 import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
 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.base.NewBaseController
 import com.nextcloud.talk.controllers.util.viewBinding
 import com.nextcloud.talk.controllers.util.viewBinding
 import com.nextcloud.talk.databinding.ControllerProfileBinding
 import com.nextcloud.talk.databinding.ControllerProfileBinding
@@ -71,12 +66,12 @@ import com.nextcloud.talk.models.json.userprofile.Scope
 import com.nextcloud.talk.models.json.userprofile.UserProfileData
 import com.nextcloud.talk.models.json.userprofile.UserProfileData
 import com.nextcloud.talk.models.json.userprofile.UserProfileFieldsOverall
 import com.nextcloud.talk.models.json.userprofile.UserProfileFieldsOverall
 import com.nextcloud.talk.models.json.userprofile.UserProfileOverall
 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.ui.dialog.ScopeDialog
 import com.nextcloud.talk.utils.ApiUtils
 import com.nextcloud.talk.utils.ApiUtils
 import com.nextcloud.talk.utils.DisplayUtils
 import com.nextcloud.talk.utils.DisplayUtils
-import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_BROWSER_TYPE
-import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_TOKEN
-import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_USER_ENTITY
+import com.nextcloud.talk.utils.FileUtils
+import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_MIME_TYPE_FILTER
 import com.nextcloud.talk.utils.database.user.UserUtils
 import com.nextcloud.talk.utils.database.user.UserUtils
 import io.reactivex.Observer
 import io.reactivex.Observer
 import io.reactivex.android.schedulers.AndroidSchedulers
 import io.reactivex.android.schedulers.AndroidSchedulers
@@ -86,7 +81,6 @@ import okhttp3.MediaType.Companion.toMediaTypeOrNull
 import okhttp3.MultipartBody
 import okhttp3.MultipartBody
 import okhttp3.RequestBody
 import okhttp3.RequestBody
 import okhttp3.ResponseBody
 import okhttp3.ResponseBody
-import org.parceler.Parcels
 import retrofit2.Call
 import retrofit2.Call
 import retrofit2.Callback
 import retrofit2.Callback
 import retrofit2.Response
 import retrofit2.Response
@@ -199,7 +193,7 @@ class ProfileController : NewBaseController(R.layout.controller_profile) {
         currentUser = userUtils.currentUser
         currentUser = userUtils.currentUser
         val credentials = ApiUtils.getCredentials(currentUser!!.username, currentUser!!.token)
         val credentials = ApiUtils.getCredentials(currentUser!!.username, currentUser!!.token)
         binding.avatarUpload.setOnClickListener { sendSelectLocalFileIntent() }
         binding.avatarUpload.setOnClickListener { sendSelectLocalFileIntent() }
-        binding.avatarChoose.setOnClickListener { showBrowserScreen(BrowserType.DAV_BROWSER) }
+        binding.avatarChoose.setOnClickListener { showBrowserScreen() }
         binding.avatarDelete.setOnClickListener {
         binding.avatarDelete.setOnClickListener {
             ncApi.deleteAvatar(
             ncApi.deleteAvatar(
                 credentials,
                 credentials,
@@ -486,22 +480,14 @@ class ProfileController : NewBaseController(R.layout.controller_profile) {
         startActivityForResult(intent, 1)
         startActivityForResult(intent, 1)
     }
     }
 
 
-    private fun showBrowserScreen(browserType: BrowserType) {
+    private fun showBrowserScreen() {
         val bundle = Bundle()
         val bundle = Bundle()
-        bundle.putParcelable(
-            KEY_BROWSER_TYPE,
-            Parcels.wrap(BrowserType::class.java, browserType)
-        )
-        bundle.putParcelable(
-            KEY_USER_ENTITY,
-            Parcels.wrap(UserEntity::class.java, currentUser)
-        )
-        bundle.putString(KEY_ROOM_TOKEN, "123")
-        router.pushController(
-            RouterTransaction.with(BrowserForAvatarController(bundle, this))
-                .pushChangeHandler(VerticalChangeHandler())
-                .popChangeHandler(VerticalChangeHandler())
-        )
+        bundle.putString(KEY_MIME_TYPE_FILTER, "image/")
+
+        val avatarIntent = Intent(activity, RemoteFileBrowserActivity::class.java)
+        avatarIntent.putExtras(bundle)
+
+        startActivityForResult(avatarIntent, REQUEST_CODE_SELECT_REMOTE_FILES)
     }
     }
 
 
     fun handleAvatar(remotePath: String?) {
     fun handleAvatar(remotePath: String?) {
@@ -526,9 +512,13 @@ class ProfileController : NewBaseController(R.layout.controller_profile) {
     private fun saveBitmapAndPassToImagePicker(bitmap: Bitmap) {
     private fun saveBitmapAndPassToImagePicker(bitmap: Bitmap) {
         var file: File? = null
         var file: File? = null
         try {
         try {
-            file = File.createTempFile(
-                "avatar", "png",
-                Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM)
+            FileUtils.removeTempCacheFile(
+                this.context!!,
+                AVATAR_PATH
+            )
+            file = FileUtils.getTempCacheFile(
+                this.context!!,
+                AVATAR_PATH
             )
             )
             try {
             try {
                 FileOutputStream(file).use { out -> bitmap.compress(Bitmap.CompressFormat.PNG, FULL_QUALITY, out) }
                 FileOutputStream(file).use { out -> bitmap.compress(Bitmap.CompressFormat.PNG, FULL_QUALITY, out) }
@@ -553,13 +543,22 @@ class ProfileController : NewBaseController(R.layout.controller_profile) {
         startActivityForResult(intent, REQUEST_CODE_IMAGE_PICKER)
         startActivityForResult(intent, REQUEST_CODE_IMAGE_PICKER)
     }
     }
 
 
-    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+    override fun onActivityResult(requestCode: Int, resultCode: Int, intent: Intent?) {
         if (resultCode == Activity.RESULT_OK) {
         if (resultCode == Activity.RESULT_OK) {
-            uploadAvatar(getFile(data))
+            if (requestCode == REQUEST_CODE_IMAGE_PICKER) {
+                uploadAvatar(getFile(intent))
+            } else if (requestCode == REQUEST_CODE_SELECT_REMOTE_FILES) {
+                val pathList = intent?.getStringArrayListExtra(RemoteFileBrowserActivity.EXTRA_SELECTED_PATHS)
+                if (pathList?.size!! >= 1) {
+                    handleAvatar(pathList[0])
+                }
+            } else {
+                Log.w(TAG, "Unknown intent request code")
+            }
         } else if (resultCode == ImagePicker.RESULT_ERROR) {
         } else if (resultCode == ImagePicker.RESULT_ERROR) {
-            Toast.makeText(activity, getError(data), Toast.LENGTH_SHORT).show()
+            Toast.makeText(activity, getError(intent), Toast.LENGTH_SHORT).show()
         } else {
         } else {
-            Toast.makeText(activity, "Task Cancelled", Toast.LENGTH_SHORT).show()
+            Log.i(TAG, "Task Cancelled")
         }
         }
     }
     }
 
 
@@ -809,6 +808,8 @@ class ProfileController : NewBaseController(R.layout.controller_profile) {
 
 
     companion object {
     companion object {
         private const val TAG: String = "ProfileController"
         private const val TAG: String = "ProfileController"
+        private const val AVATAR_PATH = "photos/avatar.png"
+        private const val REQUEST_CODE_SELECT_REMOTE_FILES = 22
         private const val DEFAULT_CACHE_SIZE: Int = 20
         private const val DEFAULT_CACHE_SIZE: Int = 20
         private const val DEFAULT_RETRIES: Long = 3
         private const val DEFAULT_RETRIES: Long = 3
         private const val MAX_SIZE: Int = 1024
         private const val MAX_SIZE: Int = 1024

+ 9 - 0
app/src/main/java/com/nextcloud/talk/dagger/modules/RepositoryModule.kt

@@ -22,6 +22,8 @@
 package com.nextcloud.talk.dagger.modules
 package com.nextcloud.talk.dagger.modules
 
 
 import com.nextcloud.talk.api.NcApi
 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.UnifiedSearchRepository
 import com.nextcloud.talk.repositories.unifiedsearch.UnifiedSearchRepositoryImpl
 import com.nextcloud.talk.repositories.unifiedsearch.UnifiedSearchRepositoryImpl
 import com.nextcloud.talk.shareditems.repositories.SharedItemsRepository
 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 com.nextcloud.talk.utils.database.user.CurrentUserProvider
 import dagger.Module
 import dagger.Module
 import dagger.Provides
 import dagger.Provides
+import okhttp3.OkHttpClient
 
 
 @Module
 @Module
 class RepositoryModule {
 class RepositoryModule {
@@ -41,4 +44,10 @@ class RepositoryModule {
     fun provideUnifiedSearchRepository(ncApi: NcApi, userProvider: CurrentUserProvider): UnifiedSearchRepository {
     fun provideUnifiedSearchRepository(ncApi: NcApi, userProvider: CurrentUserProvider): UnifiedSearchRepository {
         return UnifiedSearchRepositoryImpl(ncApi, userProvider)
         return UnifiedSearchRepositoryImpl(ncApi, userProvider)
     }
     }
+
+    @Provides
+    fun provideRemoteFileBrowserItemsRepository(okHttpClient: OkHttpClient, userProvider: CurrentUserProvider):
+        RemoteFileBrowserItemsRepository {
+        return RemoteFileBrowserItemsRepositoryImpl(okHttpClient, userProvider)
+    }
 }
 }

+ 6 - 0
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.ViewModel
 import androidx.lifecycle.ViewModelProvider
 import androidx.lifecycle.ViewModelProvider
+import com.nextcloud.talk.remotefilebrowser.viewmodels.RemoteFileBrowserItemsViewModel
 import com.nextcloud.talk.messagesearch.MessageSearchViewModel
 import com.nextcloud.talk.messagesearch.MessageSearchViewModel
 import com.nextcloud.talk.shareditems.viewmodels.SharedItemsViewModel
 import com.nextcloud.talk.shareditems.viewmodels.SharedItemsViewModel
 import dagger.Binds
 import dagger.Binds
@@ -59,4 +60,9 @@ abstract class ViewModelModule {
     @IntoMap
     @IntoMap
     @ViewModelKey(MessageSearchViewModel::class)
     @ViewModelKey(MessageSearchViewModel::class)
     abstract fun messageSearchViewModel(viewModel: MessageSearchViewModel): ViewModel
     abstract fun messageSearchViewModel(viewModel: MessageSearchViewModel): ViewModel
+
+    @Binds
+    @IntoMap
+    @ViewModelKey(RemoteFileBrowserItemsViewModel::class)
+    abstract fun remoteFileBrowserItemsViewModel(viewModel: RemoteFileBrowserItemsViewModel): ViewModel
 }
 }

+ 3 - 5
app/src/main/java/com/nextcloud/talk/interfaces/SelectionInterface.kt → app/src/main/java/com/nextcloud/talk/remotefilebrowser/SelectionInterface.kt

@@ -2,6 +2,8 @@
  * Nextcloud Talk application
  * Nextcloud Talk application
  *
  *
  * @author Mario Danic
  * @author Mario Danic
+ * @author Andy Scherzinger
+ * Copyright (C) 2022 Andy Scherzinger <info@andy-scherzinger.de>
  * Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
  * Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
  *
  *
  * This program is free software: you can redistribute it and/or modify
  * This program is free software: you can redistribute it and/or modify
@@ -18,12 +20,8 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
  */
 
 
-package com.nextcloud.talk.interfaces
+package com.nextcloud.talk.remotefilebrowser
 
 
 interface SelectionInterface {
 interface SelectionInterface {
-    fun toggleBrowserItemSelection(path: String)
-
     fun isPathSelected(path: String): Boolean
     fun isPathSelected(path: String): Boolean
-
-    fun shouldOnlySelectOneImageFile(): Boolean
 }
 }

+ 260 - 0
app/src/main/java/com/nextcloud/talk/remotefilebrowser/activities/RemoteFileBrowserActivity.kt

@@ -0,0 +1,260 @@
+/*
+ * Nextcloud Talk application
+ *
+ * @author Andy Scherzinger
+ * @author Álvaro Brey
+ * Copyright (C) 2022 Andy Scherzinger <info@andy-scherzinger.de>
+ * Copyright (C) 2022 Álvaro Brey <alvaro.brey@nextcloud.com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+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.remotefilebrowser.SelectionInterface
+import com.nextcloud.talk.remotefilebrowser.adapters.RemoteFileBrowserItemsAdapter
+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.bundle.BundleKeys.KEY_MIME_TYPE_FILTER
+import com.nextcloud.talk.utils.database.user.CurrentUserProvider
+import javax.inject.Inject
+
+@AutoInjector(NextcloudTalkApplication::class)
+class RemoteFileBrowserActivity : AppCompatActivity(), SelectionInterface, SwipeRefreshLayout.OnRefreshListener {
+
+    @Inject
+    lateinit var viewModelFactory: ViewModelProvider.Factory
+
+    @Inject
+    lateinit var currentUserProvider: CurrentUserProvider
+
+    private lateinit var binding: ActivityRemoteFileBrowserBinding
+    private lateinit var viewModel: RemoteFileBrowserItemsViewModel
+
+    private var filesSelectionDoneMenuItem: MenuItem? = 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?.setDisplayHomeAsUpEnabled(true)
+
+        val extras = intent.extras
+        val mimeTypeSelectionFilter = extras?.getString(KEY_MIME_TYPE_FILTER, null)
+
+        initViewModel(mimeTypeSelectionFilter)
+
+        binding.swipeRefreshList.setOnRefreshListener(this)
+        binding.swipeRefreshList.setColorSchemeResources(R.color.colorPrimary)
+        binding.swipeRefreshList.setProgressBackgroundColorSchemeResource(R.color.refresh_spinner_background)
+
+        binding.pathNavigationBackButton.setOnClickListener { viewModel.navigateUp() }
+        binding.sortButton.setOnClickListener { changeSorting() }
+
+        viewModel.loadItems()
+    }
+
+    private fun initViewModel(mimeTypeSelectionFilter: String?) {
+        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 -> {
+                    loadList(state, mimeTypeSelectionFilter)
+                }
+                is RemoteFileBrowserItemsViewModel.FinishState -> {
+                    finishWithResult(state.selectedPaths)
+                }
+            }
+        }
+
+        viewModel.fileSortOrder.observe(this) { sortOrder ->
+            if (sortOrder != null) {
+                binding.sortButton.setText(DisplayUtils.getSortOrderStringId(sortOrder))
+            }
+        }
+
+        viewModel.currentPath.observe(this) { path ->
+            if (path != null) {
+                supportActionBar?.title = path
+            }
+        }
+
+        viewModel.selectedPaths.observe(this) { selectedPaths ->
+            filesSelectionDoneMenuItem?.isVisible = !selectedPaths.isNullOrEmpty()
+            binding.recyclerView.adapter?.notifyDataSetChanged()
+        }
+    }
+
+    private fun loadList(
+        state: RemoteFileBrowserItemsViewModel.LoadedState,
+        mimeTypeSelectionFilter: String?
+    ) {
+        val remoteFileBrowserItems = state.items
+        Log.d(TAG, "Items received: $remoteFileBrowserItems")
+
+        // TODO make showGrid based on preferences (when available)
+        val showGrid = false
+        val layoutManager = if (showGrid) {
+            GridLayoutManager(this, SPAN_COUNT)
+        } else {
+            LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
+        }
+
+        // TODO do not needlessly recreate adapter if it can be reused
+        val adapter = RemoteFileBrowserItemsAdapter(
+            showGrid = showGrid,
+            mimeTypeSelectionFilter = mimeTypeSelectionFilter,
+            userEntity = currentUserProvider.currentUser!!,
+            selectionInterface = this,
+            onItemClicked = viewModel::onItemClicked
+        )
+        adapter.items = remoteFileBrowserItems
+
+        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)
+        return true
+    }
+
+    override fun onBackPressed() {
+        setResult(Activity.RESULT_CANCELED)
+        super.onBackPressed()
+    }
+
+    override fun onResume() {
+        super.onResume()
+        refreshCurrentPath()
+    }
+
+    private fun changeSorting() {
+        val newFragment: DialogFragment = SortingOrderDialogFragment
+            .newInstance(FileSortOrder.getFileSortOrder(viewModel.fileSortOrder.value!!.name))
+        newFragment.show(
+            supportFragmentManager,
+            SortingOrderDialogFragment.SORTING_ORDER_FRAGMENT
+        )
+    }
+
+    override fun onOptionsItemSelected(item: MenuItem): Boolean {
+        return when (item.itemId) {
+            android.R.id.home -> {
+                onBackPressed()
+                true
+            }
+            R.id.files_selection_done -> {
+                viewModel.onSelectionDone()
+                true
+            }
+            else -> {
+                return super.onOptionsItemSelected(item)
+            }
+        }
+    }
+
+    private fun finishWithResult(selectedPaths: Set<String>) {
+        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()
+    }
+
+    override fun isPathSelected(path: String): Boolean {
+        return viewModel.isPathSelected(path)
+    }
+
+    companion object {
+        private val TAG = RemoteFileBrowserActivity::class.simpleName
+        const val SPAN_COUNT: Int = 4
+        const val EXTRA_SELECTED_PATHS = "EXTRA_SELECTED_PATH"
+    }
+}

+ 86 - 0
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 <info@andy-scherzinger.de>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+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.models.database.UserEntity
+import com.nextcloud.talk.remotefilebrowser.SelectionInterface
+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<RemoteFileBrowserItemsViewHolder>() {
+
+    var items: List<RemoteFileBrowserItem> = 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<RemoteFileBrowserItem>) {
+        items = browserItems
+        notifyDataSetChanged()
+    }
+}

+ 153 - 0
app/src/main/java/com/nextcloud/talk/remotefilebrowser/adapters/RemoteFileBrowserItemsListViewHolder.kt

@@ -0,0 +1,153 @@
+/*
+ * Nextcloud Talk application
+ *
+ * @author Andy Scherzinger
+ * Copyright (C) 2022 Andy Scherzinger <info@andy-scherzinger.de>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+package com.nextcloud.talk.remotefilebrowser.adapters
+
+import android.text.format.Formatter
+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.models.database.UserEntity
+import com.nextcloud.talk.remotefilebrowser.SelectionInterface
+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) &&
+            (item.isAllowedToReShare && !item.isEncrypted)
+    }
+
+    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
+    }
+}

+ 53 - 0
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 <info@andy-scherzinger.de>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+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.models.database.UserEntity
+import com.nextcloud.talk.remotefilebrowser.SelectionInterface
+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)!!
+    }
+}

+ 44 - 0
app/src/main/java/com/nextcloud/talk/remotefilebrowser/model/RemoteFileBrowserItem.kt

@@ -0,0 +1,44 @@
+/*
+ * Nextcloud Talk application
+ *
+ * @author Andy Scherzinger
+ * @author Mario Danic
+ * Copyright (C) 2022 Andy Scherzinger <info@andy-scherzinger.de>
+ * Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+package com.nextcloud.talk.remotefilebrowser.model
+
+import android.os.Parcelable
+import kotlinx.android.parcel.Parcelize
+
+@Parcelize
+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

+ 8 - 6
app/src/main/java/com/nextcloud/talk/components/filebrowser/interfaces/ListingInterface.java → app/src/main/java/com/nextcloud/talk/remotefilebrowser/repositories/RemoteFileBrowserItemsRepository.kt

@@ -1,8 +1,8 @@
 /*
 /*
  * Nextcloud Talk application
  * Nextcloud Talk application
  *
  *
- * @author Mario Danic
- * Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
+ * @author Andy Scherzinger
+ * Copyright (C) 2022 Andy Scherzinger <info@andy-scherzinger.de>
  *
  *
  * This program is free software: you can redistribute it and/or modify
  * 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
  * it under the terms of the GNU General Public License as published by
@@ -18,10 +18,12 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
  */
 
 
-package com.nextcloud.talk.components.filebrowser.interfaces;
+package com.nextcloud.talk.remotefilebrowser.repositories
 
 
-import com.nextcloud.talk.components.filebrowser.models.DavResponse;
+import com.nextcloud.talk.remotefilebrowser.model.RemoteFileBrowserItem
+import io.reactivex.Observable
 
 
-public interface ListingInterface {
-    void listingResult(DavResponse davResponse);
+interface RemoteFileBrowserItemsRepository {
+
+    fun listFolder(path: String): Observable<List<RemoteFileBrowserItem>>
 }
 }

+ 56 - 0
app/src/main/java/com/nextcloud/talk/remotefilebrowser/repositories/RemoteFileBrowserItemsRepositoryImpl.kt

@@ -0,0 +1,56 @@
+/*
+ * Nextcloud Talk application
+ *
+ * @author Andy Scherzinger
+ * Copyright (C) 2022 Andy Scherzinger <info@andy-scherzinger.de>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+package com.nextcloud.talk.remotefilebrowser.repositories
+
+import com.nextcloud.talk.components.filebrowser.webdav.ReadFolderListingOperation
+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<List<RemoteFileBrowserItem>> {
+        return Observable.fromCallable {
+            val operation =
+                ReadFolderListingOperation(
+                    okHttpClient,
+                    userEntity,
+                    path,
+                    1
+                )
+            val davResponse = operation.readRemotePath()
+            if (davResponse.getData() != null) {
+                return@fromCallable davResponse.getData() as List<RemoteFileBrowserItem>
+            }
+            return@fromCallable emptyList()
+        }
+    }
+}

+ 230 - 0
app/src/main/java/com/nextcloud/talk/remotefilebrowser/viewmodels/RemoteFileBrowserItemsViewModel.kt

@@ -0,0 +1,230 @@
+/*
+ * Nextcloud Talk application
+ *
+ * @author Andy Scherzinger
+ * @author Álvaro Brey
+ * Copyright (C) 2022 Andy Scherzinger <info@andy-scherzinger.de>
+ * Copyright (C) 2022 Álvaro Brey <alvaro.brey@nextcloud.com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+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 com.nextcloud.talk.utils.FileSortOrder
+import com.nextcloud.talk.utils.preferences.AppPreferences
+import io.reactivex.Observer
+import io.reactivex.android.schedulers.AndroidSchedulers
+import io.reactivex.disposables.Disposable
+import io.reactivex.schedulers.Schedulers
+import net.orange_box.storebox.listeners.OnPreferenceValueChangedListener
+import java.io.File
+import javax.inject.Inject
+
+/**
+ * @startuml
+ * hide empty description
+ * [*] --> InitialState
+ * InitialState --> LoadingItemsState
+ * LoadingItemsState --> NoRemoteFileItemsState
+ * NoRemoteFileItemsState --> LoadingItemsState
+ * LoadingItemsState --> LoadedState
+ * LoadedState --> LoadingItemsState
+ * LoadedState --> FinishState
+ * FinishState --> [*]
+ * @enduml
+ */
+class RemoteFileBrowserItemsViewModel @Inject constructor(
+    private val repository: RemoteFileBrowserItemsRepository,
+    private val appPreferences: AppPreferences
+) :
+    ViewModel() {
+
+    sealed interface ViewState
+    object InitialState : ViewState
+    object NoRemoteFileItemsState : ViewState
+    object LoadingItemsState : ViewState
+    class LoadedState(val items: List<RemoteFileBrowserItem>) : ViewState
+    class FinishState(val selectedPaths: Set<String>) : ViewState
+
+    private val initialSortOrder = FileSortOrder.getFileSortOrder(appPreferences.sorting)
+    private val sortingPrefListener: SortChangeListener = SortChangeListener()
+
+    private val _viewState: MutableLiveData<ViewState> = MutableLiveData(InitialState)
+    val viewState: LiveData<ViewState>
+        get() = _viewState
+
+    // TODO incorporate into view state object?
+    private val _fileSortOrder: MutableLiveData<FileSortOrder> = MutableLiveData(initialSortOrder)
+    val fileSortOrder: LiveData<FileSortOrder>
+        get() = _fileSortOrder
+
+    private val _currentPath: MutableLiveData<String> = MutableLiveData(ROOT_PATH)
+    val currentPath: LiveData<String>
+        get() = _currentPath
+
+    private val _selectedPaths: MutableLiveData<Set<String>> = MutableLiveData(emptySet())
+    val selectedPaths: LiveData<Set<String>>
+        get() = _selectedPaths
+
+    init {
+        appPreferences.registerSortingChangeListener(sortingPrefListener)
+    }
+
+    inner class SortChangeListener : OnPreferenceValueChangedListener<String> {
+        override fun onChanged(newValue: String) {
+            onSelectSortOrder(newValue)
+        }
+    }
+
+    override fun onCleared() {
+        super.onCleared()
+        appPreferences.unregisterSortingChangeListener(sortingPrefListener)
+    }
+
+    fun loadItems() {
+        _viewState.value = LoadingItemsState
+        repository.listFolder(currentPath.value!!).subscribeOn(Schedulers.io())
+            ?.observeOn(AndroidSchedulers.mainThread())
+            ?.subscribe(RemoteFileBrowserItemsObserver())
+    }
+
+    inner class RemoteFileBrowserItemsObserver : Observer<List<RemoteFileBrowserItem>> {
+
+        var newRemoteFileBrowserItems: List<RemoteFileBrowserItem>? = null
+
+        override fun onSubscribe(d: Disposable) = Unit
+
+        override fun onNext(response: List<RemoteFileBrowserItem>) {
+            newRemoteFileBrowserItems = fileSortOrder.value!!.sortCloudFiles(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<RemoteFileBrowserItem>) {
+            when (this@RemoteFileBrowserItemsViewModel._viewState.value) {
+                is LoadedState, LoadingItemsState -> {
+                    this@RemoteFileBrowserItemsViewModel._viewState.value = LoadedState(items)
+                }
+                else -> return
+            }
+        }
+    }
+
+    private fun onSelectSortOrder(newSortOrderString: String) {
+        val newSortOrder = FileSortOrder.getFileSortOrder(newSortOrderString)
+        if (newSortOrder.name != fileSortOrder.value?.name) {
+            _fileSortOrder.value = newSortOrder
+            val currentState = viewState.value
+            if (currentState is LoadedState) {
+                val sortedItems = newSortOrder.sortCloudFiles(currentState.items)
+                _viewState.value = LoadedState(sortedItems)
+            }
+        }
+    }
+
+    private fun changePath(path: String) {
+        _currentPath.value = path
+        loadItems()
+    }
+
+    fun navigateUp() {
+        val path = _currentPath.value
+        if (path!! != ROOT_PATH) {
+            _currentPath.value = File(path).parent!!
+            loadItems()
+        }
+    }
+
+    fun onSelectionDone() {
+        val selection = selectedPaths.value
+        if (!selection.isNullOrEmpty()) {
+            _viewState.value = FinishState(selection)
+        }
+    }
+
+    fun onItemClicked(remoteFileBrowserItem: RemoteFileBrowserItem) {
+        if (remoteFileBrowserItem.mimeType == MIME_DIRECTORY) {
+            changePath(remoteFileBrowserItem.path!!)
+        } else {
+            toggleBrowserItemSelection(remoteFileBrowserItem.path!!)
+        }
+    }
+
+    private fun toggleBrowserItemSelection(path: String) {
+        val paths = selectedPaths.value!!.toMutableSet()
+        if (paths.contains(path) || shouldPathBeSelectedDueToParent(path)) {
+            checkAndRemoveAnySelectedParents(path)
+        } else {
+            // TODO if it's a folder, remove all the children we added manually
+            paths.add(path)
+            _selectedPaths.value = paths
+        }
+    }
+
+    private fun checkAndRemoveAnySelectedParents(currentPath: String) {
+        var file = File(currentPath)
+        val paths = selectedPaths.value!!.toMutableSet()
+        paths.remove(currentPath)
+        while (file.parent != null) {
+            paths.remove(file.parent!! + File.pathSeparator)
+            file = File(file.parent!!)
+        }
+        _selectedPaths.value = paths
+    }
+
+    private fun shouldPathBeSelectedDueToParent(currentPath: String): Boolean {
+        var file = File(currentPath)
+        val paths = selectedPaths.value!!
+        if (paths.isNotEmpty() && file.parent != ROOT_PATH) {
+            while (file.parent != null) {
+                var parent = file.parent!!
+                if (File(file.parent!!).parent != null) {
+                    parent += File.pathSeparator
+                }
+                if (paths.contains(parent)) {
+                    return true
+                }
+                file = File(file.parent!!)
+            }
+        }
+        return false
+    }
+
+    fun isPathSelected(path: String): Boolean {
+        return selectedPaths.value?.contains(path) == true || shouldPathBeSelectedDueToParent(path)
+    }
+
+    companion object {
+        private val TAG = RemoteFileBrowserItemsViewModel::class.simpleName
+        private const val ROOT_PATH = "/"
+        private const val MIME_DIRECTORY = "inode/directory"
+    }
+}

+ 1 - 2
app/src/main/java/com/nextcloud/talk/ui/dialog/AttachmentDialog.kt

@@ -29,7 +29,6 @@ import android.view.ViewGroup
 import com.google.android.material.bottomsheet.BottomSheetBehavior
 import com.google.android.material.bottomsheet.BottomSheetBehavior
 import com.google.android.material.bottomsheet.BottomSheetDialog
 import com.google.android.material.bottomsheet.BottomSheetDialog
 import com.nextcloud.talk.R
 import com.nextcloud.talk.R
-import com.nextcloud.talk.components.filebrowser.controllers.BrowserController
 import com.nextcloud.talk.controllers.ChatController
 import com.nextcloud.talk.controllers.ChatController
 import com.nextcloud.talk.databinding.DialogAttachmentBinding
 import com.nextcloud.talk.databinding.DialogAttachmentBinding
 import com.nextcloud.talk.models.database.CapabilitiesUtil
 import com.nextcloud.talk.models.database.CapabilitiesUtil
@@ -76,7 +75,7 @@ class AttachmentDialog(val activity: Activity, var chatController: ChatControlle
         }
         }
 
 
         dialogAttachmentBinding.menuAttachFileFromCloud.setOnClickListener {
         dialogAttachmentBinding.menuAttachFileFromCloud.setOnClickListener {
-            chatController.showBrowserScreen(BrowserController.BrowserType.DAV_BROWSER)
+            chatController.showBrowserScreen()
             dismiss()
             dismiss()
         }
         }
 
 

+ 21 - 18
app/src/main/java/com/nextcloud/talk/ui/dialog/SortingOrderDialogFragment.java

@@ -39,6 +39,8 @@ import com.nextcloud.talk.databinding.SortingOrderFragmentBinding;
 import com.nextcloud.talk.utils.FileSortOrder;
 import com.nextcloud.talk.utils.FileSortOrder;
 import com.nextcloud.talk.utils.preferences.AppPreferences;
 import com.nextcloud.talk.utils.preferences.AppPreferences;
 
 
+import org.jetbrains.annotations.NotNull;
+
 import javax.inject.Inject;
 import javax.inject.Inject;
 
 
 import androidx.annotation.NonNull;
 import androidx.annotation.NonNull;
@@ -67,11 +69,11 @@ public class SortingOrderDialogFragment extends DialogFragment implements View.O
     private View[] taggedViews;
     private View[] taggedViews;
     private String currentSortOrderName;
     private String currentSortOrderName;
 
 
-    public static SortingOrderDialogFragment newInstance(FileSortOrder sortOrder) {
+    public static SortingOrderDialogFragment newInstance(@NotNull FileSortOrder sortOrder) {
         SortingOrderDialogFragment dialogFragment = new SortingOrderDialogFragment();
         SortingOrderDialogFragment dialogFragment = new SortingOrderDialogFragment();
 
 
         Bundle args = new Bundle();
         Bundle args = new Bundle();
-        args.putString(KEY_SORT_ORDER, sortOrder.name);
+        args.putString(KEY_SORT_ORDER, sortOrder.getName());
         dialogFragment.setArguments(args);
         dialogFragment.setArguments(args);
 
 
         return dialogFragment;
         return dialogFragment;
@@ -84,7 +86,8 @@ public class SortingOrderDialogFragment extends DialogFragment implements View.O
         setRetainInstance(true);
         setRetainInstance(true);
 
 
         if (getArguments() != null) {
         if (getArguments() != null) {
-            currentSortOrderName = getArguments().getString(KEY_SORT_ORDER, FileSortOrder.sort_a_to_z.name);
+            currentSortOrderName = getArguments().getString(KEY_SORT_ORDER,
+                                                            FileSortOrder.Companion.getSort_a_to_z().getName());
         }
         }
     }
     }
 
 
@@ -120,29 +123,29 @@ public class SortingOrderDialogFragment extends DialogFragment implements View.O
 
 
         taggedViews = new View[12];
         taggedViews = new View[12];
         taggedViews[0] = binding.sortByNameAscending;
         taggedViews[0] = binding.sortByNameAscending;
-        taggedViews[0].setTag(FileSortOrder.sort_a_to_z);
+        taggedViews[0].setTag(FileSortOrder.Companion.getSort_a_to_z());
         taggedViews[1] = binding.sortByNameAZText;
         taggedViews[1] = binding.sortByNameAZText;
-        taggedViews[1].setTag(FileSortOrder.sort_a_to_z);
+        taggedViews[1].setTag(FileSortOrder.Companion.getSort_a_to_z());
         taggedViews[2] = binding.sortByNameDescending;
         taggedViews[2] = binding.sortByNameDescending;
-        taggedViews[2].setTag(FileSortOrder.sort_z_to_a);
+        taggedViews[2].setTag(FileSortOrder.Companion.getSort_z_to_a());
         taggedViews[3] = binding.sortByNameZAText;
         taggedViews[3] = binding.sortByNameZAText;
-        taggedViews[3].setTag(FileSortOrder.sort_z_to_a);
+        taggedViews[3].setTag(FileSortOrder.Companion.getSort_z_to_a());
         taggedViews[4] = binding.sortByModificationDateAscending;
         taggedViews[4] = binding.sortByModificationDateAscending;
-        taggedViews[4].setTag(FileSortOrder.sort_old_to_new);
+        taggedViews[4].setTag(FileSortOrder.Companion.getSort_old_to_new());
         taggedViews[5] = binding.sortByModificationDateOldestFirstText;
         taggedViews[5] = binding.sortByModificationDateOldestFirstText;
-        taggedViews[5].setTag(FileSortOrder.sort_old_to_new);
+        taggedViews[5].setTag(FileSortOrder.Companion.getSort_old_to_new());
         taggedViews[6] = binding.sortByModificationDateDescending;
         taggedViews[6] = binding.sortByModificationDateDescending;
-        taggedViews[6].setTag(FileSortOrder.sort_new_to_old);
+        taggedViews[6].setTag(FileSortOrder.Companion.getSort_new_to_old());
         taggedViews[7] = binding.sortByModificationDateNewestFirstText;
         taggedViews[7] = binding.sortByModificationDateNewestFirstText;
-        taggedViews[7].setTag(FileSortOrder.sort_new_to_old);
+        taggedViews[7].setTag(FileSortOrder.Companion.getSort_new_to_old());
         taggedViews[8] = binding.sortBySizeAscending;
         taggedViews[8] = binding.sortBySizeAscending;
-        taggedViews[8].setTag(FileSortOrder.sort_small_to_big);
+        taggedViews[8].setTag(FileSortOrder.Companion.getSort_small_to_big());
         taggedViews[9] = binding.sortBySizeSmallestFirstText;
         taggedViews[9] = binding.sortBySizeSmallestFirstText;
-        taggedViews[9].setTag(FileSortOrder.sort_small_to_big);
+        taggedViews[9].setTag(FileSortOrder.Companion.getSort_small_to_big());
         taggedViews[10] = binding.sortBySizeDescending;
         taggedViews[10] = binding.sortBySizeDescending;
-        taggedViews[10].setTag(FileSortOrder.sort_big_to_small);
+        taggedViews[10].setTag(FileSortOrder.Companion.getSort_big_to_small());
         taggedViews[11] = binding.sortBySizeBiggestFirstText;
         taggedViews[11] = binding.sortBySizeBiggestFirstText;
-        taggedViews[11].setTag(FileSortOrder.sort_big_to_small);
+        taggedViews[11].setTag(FileSortOrder.Companion.getSort_big_to_small());
 
 
         setupActiveOrderSelection();
         setupActiveOrderSelection();
     }
     }
@@ -154,8 +157,8 @@ public class SortingOrderDialogFragment extends DialogFragment implements View.O
         final int color = getResources().getColor(R.color.colorPrimary);
         final int color = getResources().getColor(R.color.colorPrimary);
         Log.i("SortOrder", "currentSortOrderName="+currentSortOrderName);
         Log.i("SortOrder", "currentSortOrderName="+currentSortOrderName);
         for (View view : taggedViews) {
         for (View view : taggedViews) {
-            Log.i("SortOrder", ((FileSortOrder) view.getTag()).name);
-            if (!((FileSortOrder) view.getTag()).name.equals(currentSortOrderName)) {
+            Log.i("SortOrder", ((FileSortOrder) view.getTag()).getName());
+            if (!((FileSortOrder) view.getTag()).getName().equals(currentSortOrderName)) {
                 continue;
                 continue;
             }
             }
             if (view instanceof MaterialButton) {
             if (view instanceof MaterialButton) {
@@ -192,7 +195,7 @@ public class SortingOrderDialogFragment extends DialogFragment implements View.O
 
 
     @Override
     @Override
     public void onClick(View v) {
     public void onClick(View v) {
-        appPreferences.setSorting(((FileSortOrder) v.getTag()).name);
+        appPreferences.setSorting(((FileSortOrder) v.getTag()).getName());
         dismiss();
         dismiss();
     }
     }
 }
 }

+ 1 - 1
app/src/main/java/com/nextcloud/talk/utils/DisplayUtils.java

@@ -619,7 +619,7 @@ public class DisplayUtils {
 
 
     public static @StringRes
     public static @StringRes
     int getSortOrderStringId(FileSortOrder sortOrder) {
     int getSortOrderStringId(FileSortOrder sortOrder) {
-        switch (sortOrder.name) {
+        switch (sortOrder.getName()) {
             case sort_z_to_a_id:
             case sort_z_to_a_id:
                 return R.string.menu_item_sort_by_name_z_a;
                 return R.string.menu_item_sort_by_name_z_a;
             case sort_new_to_old_id:
             case sort_new_to_old_id:

+ 0 - 108
app/src/main/java/com/nextcloud/talk/utils/FileSortOrder.java

@@ -1,108 +0,0 @@
-/*
- * Nextcloud Talk application
- *
- * @author Sven R. Kunze
- * @author Andy Scherzinger
- * Copyright (C) 2021 Andy Scherzinger <info@andy-scherzinger.de>
- * 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 <http://www.gnu.org/licenses/>.
- */
-
-package com.nextcloud.talk.utils;
-
-import android.text.TextUtils;
-
-import com.nextcloud.talk.components.filebrowser.adapters.items.BrowserFileItem;
-
-import java.io.File;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import androidx.annotation.Nullable;
-
-/**
- * Sort order
- */
-public class FileSortOrder {
-    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 FileSortOrder sort_a_to_z = new FileSortOrderByName(sort_a_to_z_id, true);
-    public static final FileSortOrder sort_z_to_a = new FileSortOrderByName(sort_z_to_a_id, false);
-    public static final FileSortOrder sort_old_to_new = new FileSortOrderByDate(sort_old_to_new_id, true);
-    public static final FileSortOrder sort_new_to_old = new FileSortOrderByDate(sort_new_to_old_id, false);
-    public static final FileSortOrder sort_small_to_big = new FileSortOrderBySize(sort_small_to_big_id, true);
-    public static final FileSortOrder sort_big_to_small = new FileSortOrderBySize(sort_big_to_small_id, false);
-
-    public static final Map<String, FileSortOrder> sortOrders;
-
-    static {
-        HashMap<String, FileSortOrder> 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 FileSortOrder(String name, boolean ascending) {
-        this.name = name;
-        isAscending = ascending;
-    }
-
-    public static FileSortOrder getFileSortOrder(@Nullable String key) {
-        if (TextUtils.isEmpty(key) || !sortOrders.containsKey(key)) {
-            return sort_a_to_z;
-        } else {
-            return sortOrders.get(key);
-        }
-    }
-
-    public List<BrowserFileItem> sortCloudFiles(List<BrowserFileItem> files) {
-        return sortCloudFilesByFavourite(files);
-    }
-
-    /**
-     * Sorts list by Favourites.
-     *
-     * @param files files to sort
-     */
-    public static List<BrowserFileItem> sortCloudFilesByFavourite(List<BrowserFileItem> files) {
-        Collections.sort(files, (o1, o2) -> {
-            if (o1.getModel().isFavorite() && o2.getModel().isFavorite()) {
-                return 0;
-            } else if (o1.getModel().isFavorite()) {
-                return -1;
-            } else if (o2.getModel().isFavorite()) {
-                return 1;
-            }
-            return 0;
-        });
-
-        return files;
-    }
-}

+ 95 - 0
app/src/main/java/com/nextcloud/talk/utils/FileSortOrder.kt

@@ -0,0 +1,95 @@
+/*
+ * Nextcloud Talk application
+ *
+ * @author Sven R. Kunze
+ * @author Andy Scherzinger
+ * Copyright (C) 2021-2022 Andy Scherzinger <info@andy-scherzinger.de>
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+package com.nextcloud.talk.utils
+
+import android.text.TextUtils
+import com.nextcloud.talk.remotefilebrowser.model.RemoteFileBrowserItem
+import java.util.Collections
+
+open class FileSortOrder(var name: String, var isAscending: Boolean) {
+    companion object {
+        const val sort_a_to_z_id = "sort_a_to_z"
+        const val sort_z_to_a_id = "sort_z_to_a"
+        const val sort_old_to_new_id = "sort_old_to_new"
+        const val sort_new_to_old_id = "sort_new_to_old"
+        const val sort_small_to_big_id = "sort_small_to_big"
+        const val sort_big_to_small_id = "sort_big_to_small"
+
+        val sort_a_to_z: FileSortOrder = FileSortOrderByName(sort_a_to_z_id, true)
+        val sort_z_to_a: FileSortOrder = FileSortOrderByName(sort_z_to_a_id, false)
+        val sort_old_to_new: FileSortOrder = FileSortOrderByDate(sort_old_to_new_id, true)
+        val sort_new_to_old: FileSortOrder = FileSortOrderByDate(sort_new_to_old_id, false)
+        val sort_small_to_big: FileSortOrder = FileSortOrderBySize(sort_small_to_big_id, true)
+        val sort_big_to_small: FileSortOrder = FileSortOrderBySize(sort_big_to_small_id, false)
+
+        val sortOrders: Map<String, FileSortOrder> = mapOf(
+            sort_a_to_z.name to sort_a_to_z,
+            sort_z_to_a.name to sort_z_to_a,
+            sort_old_to_new.name to sort_old_to_new,
+            sort_new_to_old.name to sort_new_to_old,
+            sort_small_to_big.name to sort_small_to_big,
+            sort_big_to_small.name to sort_big_to_small,
+        )
+
+        fun getFileSortOrder(key: String?): FileSortOrder {
+            return if (TextUtils.isEmpty(key) || !sortOrders.containsKey(key)) {
+                sort_a_to_z
+            } else {
+                sortOrders[key]!!
+            }
+        }
+
+        /**
+         * Sorts list by Favourites.
+         *
+         * @param files files to sort
+         */
+        fun sortCloudFilesByFavourite(files: List<RemoteFileBrowserItem>): List<RemoteFileBrowserItem> {
+            Collections.sort(files, RemoteFileBrowserItemFavoriteComparator())
+            return files
+        }
+    }
+
+    val multiplier: Int
+        get() = if (isAscending) 1 else -1
+
+    open fun sortCloudFiles(files: List<RemoteFileBrowserItem>): List<RemoteFileBrowserItem> {
+        return sortCloudFilesByFavourite(files)
+    }
+
+    /**
+     * Comparator for RemoteFileBrowserItems, sorts favorite state.
+     */
+    class RemoteFileBrowserItemFavoriteComparator : Comparator<RemoteFileBrowserItem> {
+        override fun compare(left: RemoteFileBrowserItem, right: RemoteFileBrowserItem): Int {
+            return if (left.isFavorite && right.isFavorite) {
+                0
+            } else if (left.isFavorite) {
+                -1
+            } else if (right.isFavorite) {
+                1
+            } else {
+                0
+            }
+        }
+    }
+}

+ 0 - 52
app/src/main/java/com/nextcloud/talk/utils/FileSortOrderByDate.java

@@ -1,52 +0,0 @@
-/*
- * Nextcloud Talk application
- *
- * @author Sven R. Kunze
- * @author Andy Scherzinger
- * Copyright (C) 2021 Andy Scherzinger <info@andy-scherzinger.de>
- * 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 <http://www.gnu.org/licenses/>.
- */
-
-package com.nextcloud.talk.utils;
-
-import com.nextcloud.talk.components.filebrowser.adapters.items.BrowserFileItem;
-
-import java.util.Collections;
-import java.util.List;
-
-/**
- * Created by srkunze on 28.08.17.
- */
-public class FileSortOrderByDate extends FileSortOrder {
-
-    FileSortOrderByDate(String name, boolean ascending) {
-        super(name, ascending);
-    }
-
-    /**
-     * Sorts list by Date.
-     *
-     * @param files list of files to sort
-     */
-    public List<BrowserFileItem> sortCloudFiles(List<BrowserFileItem> files) {
-        final int multiplier = isAscending ? 1 : -1;
-
-        Collections.sort(files, (o1, o2) ->
-                multiplier * Long.compare(o1.getModel().getModifiedTimestamp(), o2.getModel().getModifiedTimestamp()));
-
-        return super.sortCloudFiles(files);
-    }
-}

+ 47 - 0
app/src/main/java/com/nextcloud/talk/utils/FileSortOrderByDate.kt

@@ -0,0 +1,47 @@
+/*
+ * Nextcloud Talk application
+ *
+ * @author Sven R. Kunze
+ * @author Andy Scherzinger
+ * Copyright (C) 2021-2022 Andy Scherzinger <info@andy-scherzinger.de>
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+package com.nextcloud.talk.utils
+
+import com.nextcloud.talk.remotefilebrowser.model.RemoteFileBrowserItem
+import java.util.Collections
+
+class FileSortOrderByDate internal constructor(name: String, ascending: Boolean) : FileSortOrder(name, ascending) {
+    /**
+     * Sorts list by Date.
+     *
+     * @param files list of files to sort
+     */
+    override fun sortCloudFiles(files: List<RemoteFileBrowserItem>): List<RemoteFileBrowserItem> {
+        Collections.sort(files, RemoteFileBrowserItemDateComparator(multiplier))
+        return super.sortCloudFiles(files)
+    }
+
+    /**
+     * Comparator for RemoteFileBrowserItems, sorts by modified timestamp.
+     */
+    class RemoteFileBrowserItemDateComparator(private val multiplier: Int) : Comparator<RemoteFileBrowserItem> {
+
+        override fun compare(left: RemoteFileBrowserItem, right: RemoteFileBrowserItem): Int {
+            return multiplier * left.modifiedTimestamp.compareTo(right.modifiedTimestamp)
+        }
+    }
+}

+ 0 - 63
app/src/main/java/com/nextcloud/talk/utils/FileSortOrderByName.java

@@ -1,63 +0,0 @@
-/*
- * Nextcloud Talk application
- *
- * @author Sven R. Kunze
- * @author Andy Scherzinger
- * Copyright (C) 2021 Andy Scherzinger <info@andy-scherzinger.de>
- * 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 <http://www.gnu.org/licenses/>.
- */
-
-package com.nextcloud.talk.utils;
-
-import com.nextcloud.talk.components.filebrowser.adapters.items.BrowserFileItem;
-
-import java.util.Collections;
-import java.util.List;
-
-import third_parties.daveKoeller.AlphanumComparator;
-
-/**
- * Created by srkunze on 28.08.17.
- */
-public class FileSortOrderByName extends FileSortOrder {
-
-    FileSortOrderByName(String name, boolean ascending) {
-        super(name, ascending);
-    }
-
-    /**
-     * Sorts list by Name.
-     *
-     * @param files files to sort
-     */
-    @SuppressWarnings("Bx")
-    public List<BrowserFileItem> sortCloudFiles(List<BrowserFileItem> files) {
-        final int multiplier = isAscending ? 1 : -1;
-
-        Collections.sort(files, (o1, o2) -> {
-            if (!o1.getModel().isFile() && !o2.getModel().isFile()) {
-                return multiplier * new AlphanumComparator().compare(o1, o2);
-            } else if (!o1.getModel().isFile()) {
-                return -1;
-            } else if (!o2.getModel().isFile()) {
-                return 1;
-            }
-            return multiplier * new AlphanumComparator().compare(o1, o2);
-        });
-
-        return super.sortCloudFiles(files);
-    }
-}

+ 57 - 0
app/src/main/java/com/nextcloud/talk/utils/FileSortOrderByName.kt

@@ -0,0 +1,57 @@
+/*
+ * Nextcloud Talk application
+ *
+ * @author Sven R. Kunze
+ * @author Andy Scherzinger
+ * Copyright (C) 2021-2022 Andy Scherzinger <info@andy-scherzinger.de>
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+package com.nextcloud.talk.utils
+
+import com.nextcloud.talk.remotefilebrowser.model.RemoteFileBrowserItem
+import third_parties.daveKoeller.AlphanumComparator
+import java.util.Collections
+
+class FileSortOrderByName internal constructor(name: String, ascending: Boolean) : FileSortOrder(name, ascending) {
+    /**
+     * Sorts list by Name.
+     *
+     * @param files files to sort
+     */
+    override fun sortCloudFiles(files: List<RemoteFileBrowserItem>): List<RemoteFileBrowserItem> {
+        Collections.sort(files, RemoteFileBrowserItemNameComparator(multiplier))
+        return super.sortCloudFiles(files)
+    }
+
+    /**
+     * Comparator for RemoteFileBrowserItems, sorts by name.
+     */
+    class RemoteFileBrowserItemNameComparator(private val multiplier: Int) : Comparator<RemoteFileBrowserItem> {
+        private val alphanumComparator = AlphanumComparator<RemoteFileBrowserItem>()
+
+        override fun compare(left: RemoteFileBrowserItem, right: RemoteFileBrowserItem): Int {
+            return if (!left.isFile && !right.isFile) {
+                return multiplier * alphanumComparator.compare(left.path, right.path)
+            } else if (!left.isFile) {
+                -1
+            } else if (!right.isFile) {
+                1
+            } else {
+                multiplier * alphanumComparator.compare(left.path, right.path)
+            }
+        }
+    }
+}

+ 0 - 61
app/src/main/java/com/nextcloud/talk/utils/FileSortOrderBySize.java

@@ -1,61 +0,0 @@
-/*
- * Nextcloud Talk application
- *
- * @author Sven R. Kunze
- * @author Andy Scherzinger
- * Copyright (C) 2021 Andy Scherzinger <info@andy-scherzinger.de>
- * 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 <http://www.gnu.org/licenses/>.
- */
-
-package com.nextcloud.talk.utils;
-
-import com.nextcloud.talk.components.filebrowser.adapters.items.BrowserFileItem;
-
-import java.util.Collections;
-import java.util.List;
-
-/**
- * Sorts files by sizes
- */
-public class FileSortOrderBySize extends FileSortOrder {
-
-    FileSortOrderBySize(String name, boolean ascending) {
-        super(name, ascending);
-    }
-
-    /**
-     * Sorts list by Size.
-     *
-     * @param files list of files to sort
-     */
-    public List<BrowserFileItem> sortCloudFiles(List<BrowserFileItem> files) {
-        final int multiplier = isAscending ? 1 : -1;
-
-        Collections.sort(files, (o1, o2) -> {
-            if (!o1.getModel().isFile() && !o2.getModel().isFile()) {
-                return multiplier * Long.compare(o1.getModel().getSize(), o2.getModel().getSize());
-            } else if (!o1.getModel().isFile()) {
-                return -1;
-            } else if (!o2.getModel().isFile()) {
-                return 1;
-            } else {
-                return multiplier * Long.compare(o1.getModel().getSize(), o2.getModel().getSize());
-            }
-        });
-
-        return super.sortCloudFiles(files);
-    }
-}

+ 55 - 0
app/src/main/java/com/nextcloud/talk/utils/FileSortOrderBySize.kt

@@ -0,0 +1,55 @@
+/*
+ * Nextcloud Talk application
+ *
+ * @author Sven R. Kunze
+ * @author Andy Scherzinger
+ * Copyright (C) 2021-2022 Andy Scherzinger <info@andy-scherzinger.de>
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+package com.nextcloud.talk.utils
+
+import com.nextcloud.talk.remotefilebrowser.model.RemoteFileBrowserItem
+import java.util.Collections
+
+class FileSortOrderBySize internal constructor(name: String, ascending: Boolean) : FileSortOrder(name, ascending) {
+    /**
+     * Sorts list by Size.
+     *
+     * @param files list of files to sort
+     */
+    override fun sortCloudFiles(files: List<RemoteFileBrowserItem>): List<RemoteFileBrowserItem> {
+        Collections.sort(files, RemoteFileBrowserItemSizeComparator(multiplier))
+        return super.sortCloudFiles(files)
+    }
+
+    /**
+     * Comparator for RemoteFileBrowserItems, sorts by name.
+     */
+    class RemoteFileBrowserItemSizeComparator(private val multiplier: Int) : Comparator<RemoteFileBrowserItem> {
+
+        override fun compare(left: RemoteFileBrowserItem, right: RemoteFileBrowserItem): Int {
+            return if (!left.isFile && !right.isFile) {
+                return multiplier * left.size.compareTo(right.size)
+            } else if (!left.isFile) {
+                -1
+            } else if (!right.isFile) {
+                1
+            } else {
+                multiplier * left.size.compareTo(right.size)
+            }
+        }
+    }
+}

+ 17 - 0
app/src/main/java/com/nextcloud/talk/utils/FileUtils.java

@@ -65,4 +65,21 @@ public class FileUtils {
 
 
         return cacheFile;
         return cacheFile;
     }
     }
+
+    /**
+     * Creates a new {@link File}
+     */
+    public static void removeTempCacheFile(@NonNull Context context, String fileName) throws IOException {
+        File cacheFile = new File(context.getApplicationContext().getFilesDir().getAbsolutePath() + "/" + fileName);
+
+        Log.v(TAG, "Full path for new cache file:" + cacheFile.getAbsolutePath());
+
+        if (cacheFile.exists()) {
+            if(cacheFile.delete()) {
+                Log.v(TAG, "Deletion successful");
+            } else {
+                throw new IOException("Directory for temporary file does not exist and could not be created.");
+            }
+        }
+    }
 }
 }

+ 1 - 0
app/src/main/java/com/nextcloud/talk/utils/bundle/BundleKeys.kt

@@ -74,4 +74,5 @@ object BundleKeys {
     val KEY_FORWARD_HIDE_SOURCE_ROOM = "KEY_FORWARD_HIDE_SOURCE_ROOM"
     val KEY_FORWARD_HIDE_SOURCE_ROOM = "KEY_FORWARD_HIDE_SOURCE_ROOM"
     val KEY_SYSTEM_NOTIFICATION_ID = "KEY_SYSTEM_NOTIFICATION_ID"
     val KEY_SYSTEM_NOTIFICATION_ID = "KEY_SYSTEM_NOTIFICATION_ID"
     const val KEY_MESSAGE_ID = "KEY_MESSAGE_ID"
     const val KEY_MESSAGE_ID = "KEY_MESSAGE_ID"
+    const val KEY_MIME_TYPE_FILTER = "KEY_MIME_TYPE_FILTER"
 }
 }

+ 0 - 1
app/src/main/java/com/nextcloud/talk/utils/preferences/AppPreferences.java

@@ -25,7 +25,6 @@
 package com.nextcloud.talk.utils.preferences;
 package com.nextcloud.talk.utils.preferences;
 
 
 import com.nextcloud.talk.R;
 import com.nextcloud.talk.R;
-import com.nextcloud.talk.utils.FileSortOrder;
 
 
 import net.orange_box.storebox.annotations.method.ClearMethod;
 import net.orange_box.storebox.annotations.method.ClearMethod;
 import net.orange_box.storebox.annotations.method.DefaultValue;
 import net.orange_box.storebox.annotations.method.DefaultValue;

+ 0 - 10
app/src/main/java/third_parties/daveKoeller/AlphanumComparator.java

@@ -24,9 +24,6 @@
 
 
 package third_parties.daveKoeller;
 package third_parties.daveKoeller;
 
 
-import com.nextcloud.talk.components.filebrowser.adapters.items.BrowserFileItem;
-
-import java.io.File;
 import java.io.Serializable;
 import java.io.Serializable;
 import java.math.BigInteger;
 import java.math.BigInteger;
 import java.text.Collator;
 import java.text.Collator;
@@ -87,13 +84,6 @@ public class AlphanumComparator<T> implements Comparator<T>, Serializable {
         return chunk.toString();
         return chunk.toString();
     }
     }
 
 
-    public int compare(BrowserFileItem f1, BrowserFileItem f2) {
-        String s1 = f1.getModel().getPath();
-        String s2 = f2.getModel().getPath();
-
-        return compare(s1, s2);
-    }
-
     public int compare(T t1, T t2) {
     public int compare(T t1, T t2) {
         return compare(t1.toString(), t2.toString());
         return compare(t1.toString(), t2.toString());
     }
     }

+ 1 - 1
app/src/main/res/drawable/round_corner.xml

@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
 <?xml version="1.0" encoding="utf-8"?>
 <shape xmlns:android="http://schemas.android.com/apk/res/android">
 <shape xmlns:android="http://schemas.android.com/apk/res/android">
     <solid android:color="#ededed" />
     <solid android:color="#ededed" />
-    <corners android:radius="15dp" />
+    <corners android:radius="24dp" />
 </shape>
 </shape>

+ 132 - 0
app/src/main/res/layout/activity_remote_file_browser.xml

@@ -0,0 +1,132 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Nextcloud Talk application
+  ~
+  ~ @author Mario Danic
+  ~ @author Andy Scherzinger
+  ~ Copyright (C) 2021-2022 Andy Scherzinger <info@andy-scherzinger.de>
+  ~ Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
+  ~
+  ~ 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 <http://www.gnu.org/licenses/>.
+  -->
+
+<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="@color/bg_default"
+    tools:context=".remotefilebrowser.activities.RemoteFileBrowserActivity">
+
+    <com.google.android.material.appbar.AppBarLayout
+        android:id="@+id/remote_file_browser_items_appbar"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content">
+
+        <com.google.android.material.appbar.MaterialToolbar
+            android:id="@+id/remote_file_browser_items_toolbar"
+            android:layout_width="match_parent"
+            android:layout_height="?attr/actionBarSize"
+            android:background="@color/appbar"
+            android:theme="?attr/actionBarPopupTheme"
+            app:layout_scrollFlags="scroll|enterAlways"
+            app:navigationIconTint="@color/fontAppbar"
+            app:popupTheme="@style/appActionBarPopupMenu"
+            app:titleTextColor="@color/fontAppbar"
+            tools:title="@string/nc_app_product_name" />
+
+        <!-- sorting/layout bar -->
+        <RelativeLayout
+            android:id="@+id/sort_list_button_group"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:background="@color/appbar"
+            android:visibility="visible"
+            tools:visibility="visible">
+
+            <com.google.android.material.button.MaterialButton
+                android:id="@+id/sort_button"
+                style="@style/Nextcloud.Material.TextButton"
+                android:layout_width="wrap_content"
+                android:layout_height="@dimen/min_size_clickable_area"
+                android:layout_marginStart="7dp"
+                android:contentDescription=""
+                android:text="@string/menu_item_sort_by_date_newest_first"
+                android:textAlignment="textStart"
+                android:textAllCaps="false"
+                android:textColor="@color/fontAppbar"
+                android:textSize="14sp"
+                app:icon="@drawable/ic_keyboard_arrow_down"
+                app:iconGravity="textEnd"
+                app:iconSize="16dp"
+                app:iconTint="@color/fontAppbar" />
+
+            <com.google.android.material.button.MaterialButton
+                android:id="@+id/switch_grid_view_button"
+                style="@style/Widget.AppTheme.Button.IconButton"
+                android:layout_width="@dimen/min_size_clickable_area"
+                android:layout_height="@dimen/min_size_clickable_area"
+                android:layout_alignEnd="@+id/sort_button"
+                android:layout_alignParentEnd="true"
+                android:layout_marginEnd="1dp"
+                android:contentDescription=""
+                android:visibility="invisible"
+                app:cornerRadius="24dp"
+                app:icon="@drawable/ic_search_grey"
+                app:iconTint="@color/fontAppbar" />
+
+            <com.google.android.material.button.MaterialButton
+                android:id="@+id/path_navigation_back_button"
+                style="@style/Nextcloud.Material.TextButton"
+                android:layout_width="wrap_content"
+                android:layout_height="@dimen/min_size_clickable_area"
+                android:layout_below="@id/sort_button"
+                android:layout_centerInParent="true"
+                android:contentDescription=""
+                android:text="@string/nc_file_browser_back"
+                android:textAlignment="textStart"
+                android:textAllCaps="false"
+                android:textColor="@color/fontAppbar"
+                android:textSize="14sp"
+                app:icon="@drawable/ic_arrow_back_black_24dp"
+                app:iconGravity="textStart"
+                app:iconPadding="@dimen/standard_half_padding"
+                app:iconSize="16dp"
+                app:iconTint="@color/fontAppbar" />
+
+        </RelativeLayout>
+
+    </com.google.android.material.appbar.AppBarLayout>
+
+    <include
+        android:id="@+id/emptyContainer"
+        layout="@layout/empty_list"
+        android:visibility="gone"
+        tools:visibility="visible" />
+
+    <androidx.swiperefreshlayout.widget.SwipeRefreshLayout
+        android:id="@+id/swipe_refresh_list"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        app:layout_behavior="@string/appbar_scrolling_view_behavior"
+        android:footerDividersEnabled="false">
+
+        <androidx.recyclerview.widget.RecyclerView
+            android:id="@+id/recycler_view"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            tools:listitem="@layout/rv_item_browser_file" />
+
+    </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
+
+</androidx.coordinatorlayout.widget.CoordinatorLayout>

+ 0 - 100
app/src/main/res/layout/controller_browser.xml

@@ -1,100 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?><!--
-  ~ Nextcloud Talk application
-  ~
-  ~ @author Mario Danic
-  ~ @author Andy Scherzinger
-  ~ Copyright (C) 2021 Andy Scherzinger <info@andy-scherzinger.de>
-  ~ Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
-  ~
-  ~ 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 <http://www.gnu.org/licenses/>.
-  -->
-
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto"
-    xmlns:tools="http://schemas.android.com/tools"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:background="@color/bg_default"
-    android:orientation="vertical">
-
-    <!-- sorting/layout bar -->
-    <RelativeLayout
-        android:id="@+id/sort_list_button_group"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:background="@color/appbar"
-        android:visibility="visible"
-        tools:visibility="visible">
-
-        <com.google.android.material.button.MaterialButton
-            android:id="@+id/sort_button"
-            style="@style/Nextcloud.Material.TextButton"
-            android:layout_width="wrap_content"
-            android:layout_height="@dimen/min_size_clickable_area"
-            android:layout_marginStart="7dp"
-            android:contentDescription=""
-            android:text="@string/menu_item_sort_by_date_newest_first"
-            android:textAlignment="textStart"
-            android:textAllCaps="false"
-            android:textColor="@color/fontAppbar"
-            android:textSize="14sp"
-            app:icon="@drawable/ic_keyboard_arrow_down"
-            app:iconGravity="textEnd"
-            app:iconSize="16dp"
-            app:iconTint="@color/fontAppbar" />
-
-        <com.google.android.material.button.MaterialButton
-            android:id="@+id/switch_grid_view_button"
-            style="@style/Widget.AppTheme.Button.IconButton"
-            android:layout_width="@dimen/min_size_clickable_area"
-            android:layout_height="@dimen/min_size_clickable_area"
-            android:layout_marginEnd="1dp"
-            android:contentDescription=""
-            android:layout_alignEnd="@+id/sort_button"
-            android:layout_alignParentEnd="true"
-            android:visibility="invisible"
-            app:cornerRadius="24dp"
-            app:icon="@drawable/ic_search_grey"
-            app:iconTint="@color/fontAppbar" />
-
-    </RelativeLayout>
-
-    <com.google.android.material.bottomnavigation.BottomNavigationView
-        android:id="@+id/path_navigation"
-        style="@style/BottomNavigationView"
-        android:layout_width="match_parent"
-        android:layout_height="64dp"
-        android:layout_below="@id/sort_list_button_group"
-        android:layout_marginTop="-1dp"
-        android:background="@color/bg_default"
-        app:itemIconTint="@color/fg_default"
-        app:itemTextColor="@color/fg_default"
-        app:menu="@menu/file_browser_path" />
-
-    <androidx.swiperefreshlayout.widget.SwipeRefreshLayout
-        android:id="@+id/swipe_refresh_list"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:layout_below="@id/path_navigation"
-        android:footerDividersEnabled="false">
-
-        <androidx.recyclerview.widget.RecyclerView
-            android:id="@+id/recycler_view"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            tools:listitem="@layout/rv_item_browser_file" />
-
-    </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
-
-</RelativeLayout>

+ 6 - 6
app/src/main/res/layout/controller_profile.xml

@@ -78,8 +78,8 @@
 
 
             <ImageButton
             <ImageButton
                 android:id="@+id/avatar_upload"
                 android:id="@+id/avatar_upload"
-                android:layout_width="30dp"
-                android:layout_height="30dp"
+                android:layout_width="@dimen/min_size_clickable_area"
+                android:layout_height="@dimen/min_size_clickable_area"
                 android:layout_marginLeft="@dimen/standard_half_margin"
                 android:layout_marginLeft="@dimen/standard_half_margin"
                 android:layout_marginRight="@dimen/standard_half_margin"
                 android:layout_marginRight="@dimen/standard_half_margin"
                 android:background="@drawable/round_corner"
                 android:background="@drawable/round_corner"
@@ -89,8 +89,8 @@
 
 
             <ImageButton
             <ImageButton
                 android:id="@+id/avatar_choose"
                 android:id="@+id/avatar_choose"
-                android:layout_width="30dp"
-                android:layout_height="30dp"
+                android:layout_width="@dimen/min_size_clickable_area"
+                android:layout_height="@dimen/min_size_clickable_area"
                 android:layout_marginLeft="@dimen/standard_half_margin"
                 android:layout_marginLeft="@dimen/standard_half_margin"
                 android:layout_marginRight="@dimen/standard_half_margin"
                 android:layout_marginRight="@dimen/standard_half_margin"
                 android:background="@drawable/round_corner"
                 android:background="@drawable/round_corner"
@@ -100,8 +100,8 @@
 
 
             <ImageButton
             <ImageButton
                 android:id="@+id/avatar_delete"
                 android:id="@+id/avatar_delete"
-                android:layout_width="30dp"
-                android:layout_height="30dp"
+                android:layout_width="@dimen/min_size_clickable_area"
+                android:layout_height="@dimen/min_size_clickable_area"
                 android:layout_marginLeft="@dimen/standard_half_margin"
                 android:layout_marginLeft="@dimen/standard_half_margin"
                 android:layout_marginRight="@dimen/standard_half_margin"
                 android:layout_marginRight="@dimen/standard_half_margin"
                 android:background="@drawable/round_corner"
                 android:background="@drawable/round_corner"

+ 2 - 2
app/src/main/res/layout/rv_item_browser_file.xml

@@ -34,8 +34,8 @@
         android:layout_height="48dp"
         android:layout_height="48dp"
         android:layout_alignParentEnd="true"
         android:layout_alignParentEnd="true"
         android:layout_centerVertical="true"
         android:layout_centerVertical="true"
-        android:clickable="true"
-        android:focusable="true"
+        android:clickable="false"
+        android:focusable="false"
         android:longClickable="false"
         android:longClickable="false"
         android:visibility="visible" />
         android:visibility="visible" />
 
 

+ 0 - 28
app/src/main/res/menu/file_browser_path.xml

@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Nextcloud Talk application
-  ~
-  ~ @author Mario Danic
-  ~ Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
-  ~
-  ~ 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 <http://www.gnu.org/licenses/>.
-  -->
-
-<menu xmlns:android="http://schemas.android.com/apk/res/android">
-    <item
-        android:id="@+id/action_back"
-        android:icon="@drawable/ic_arrow_back_black_24dp"
-        android:enabled="false"
-        android:title="@string/nc_file_browser_back"/>
-</menu>

+ 0 - 1
app/src/main/res/values/strings.xml

@@ -358,7 +358,6 @@
     <!-- Browser component -->
     <!-- Browser component -->
     <string name="nc_file_browser_back">Back</string>
     <string name="nc_file_browser_back">Back</string>
     <string name="nc_last_modified">%1$s | Last modified: %2$s</string>
     <string name="nc_last_modified">%1$s | Last modified: %2$s</string>
-    <string name="nc_file_browser_reshare_forbidden">You are not allowed to re-share this file</string>
     <string name="nc_sort_by">Sort by</string>
     <string name="nc_sort_by">Sort by</string>
     <string name="nc_file_browser_sort_by_key" translatable="false">file_browser_sort_by</string>
     <string name="nc_file_browser_sort_by_key" translatable="false">file_browser_sort_by</string>
     <string name="nc_file_browser_sort_by_default" translatable="false">sort_a_to_z</string>
     <string name="nc_file_browser_sort_by_default" translatable="false">sort_a_to_z</string>

+ 1 - 5
app/src/main/res/values/styles.xml

@@ -63,11 +63,7 @@
         <item name="iconTint">@color/fontAppbar</item>
         <item name="iconTint">@color/fontAppbar</item>
     </style>
     </style>
 
 
-    <style name="CallButtonMenu" parent="@style/ChatSendButtonMenu"></style>
-
-    <style name="BottomNavigationView" parent="@style/Widget.MaterialComponents.BottomNavigationView">
-        <item name="elevation">1dp</item>
-    </style>
+    <style name="CallButtonMenu" parent="@style/ChatSendButtonMenu" />
 
 
     <style name="ThemeOverlay.App.BottomSheetDialog" parent="ThemeOverlay.MaterialComponents.DayNight.BottomSheetDialog">
     <style name="ThemeOverlay.App.BottomSheetDialog" parent="ThemeOverlay.MaterialComponents.DayNight.BottomSheetDialog">
         <item name="bottomSheetStyle">@style/Talk.BottomSheetDialog</item>
         <item name="bottomSheetStyle">@style/Talk.BottomSheetDialog</item>

+ 1 - 1
detekt.yml

@@ -1,5 +1,5 @@
 build:
 build:
-  maxIssues: 89
+  maxIssues: 88
   weights:
   weights:
     # complexity: 2
     # complexity: 2
     # LongParameterList: 1
     # LongParameterList: 1

+ 1 - 1
scripts/analysis/findbugs-results.txt

@@ -1 +1 @@
-179
+174

+ 1 - 1
scripts/analysis/lint-results.txt

@@ -1,2 +1,2 @@
 DO NOT TOUCH; GENERATED BY DRONE
 DO NOT TOUCH; GENERATED BY DRONE
-      <span class="mdl-layout-title">Lint Report: 2 errors and 123 warnings</span>
+      <span class="mdl-layout-title">Lint Report: 2 errors and 122 warnings</span>