From 24b21779ab2ed5e77ea7252bb16ab29d396164a5 Mon Sep 17 00:00:00 2001 From: Fernando Sanz Date: Wed, 24 Nov 2021 14:08:00 +0100 Subject: [PATCH 1/6] RefreshFolderFromServerAsyncUseCase has been implemented. --- .../dependecyinjection/ViewModelModule.kt | 2 +- .../ui/files/filelist/MainFileListFragment.kt | 3 + .../files/filelist/MainFileListViewModel.kt | 19 +++++- .../res/layout/main_file_list_fragment.xml | 60 +++++++++++-------- 4 files changed, 57 insertions(+), 27 deletions(-) diff --git a/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/ViewModelModule.kt b/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/ViewModelModule.kt index a9f6936ce56..b1a19596fbe 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/ViewModelModule.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/ViewModelModule.kt @@ -75,5 +75,5 @@ val viewModelModule = module { viewModel { PreviewImageViewModel(get(), get(), get()) } viewModel { FileDetailsViewModel(get(), get(), get(), get(), get()) } viewModel { FileOperationViewModel(get(), get(), get(), get(), get(), get()) } - viewModel { MainFileListViewModel(get(), get()) } + viewModel { MainFileListViewModel(get(), get(), get()) } } diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/ui/files/filelist/MainFileListFragment.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/ui/files/filelist/MainFileListFragment.kt index d6704aa689d..a99cff2286c 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/presentation/ui/files/filelist/MainFileListFragment.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/ui/files/filelist/MainFileListFragment.kt @@ -75,6 +75,9 @@ class MainFileListFragment : Fragment() { layoutManager = LinearLayoutManager(requireContext()) adapter = fileListAdapter } + + //Set Swipe to refresh and its listener + binding.swipeRefreshMainFileList.setOnRefreshListener { mainFileListViewModel.refreshDirectory() } } private fun subscribeToViewModels() { diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/ui/files/filelist/MainFileListViewModel.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/ui/files/filelist/MainFileListViewModel.kt index 462703005a3..a1cb23d34f0 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/presentation/ui/files/filelist/MainFileListViewModel.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/ui/files/filelist/MainFileListViewModel.kt @@ -24,19 +24,24 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.MediatorLiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope import com.owncloud.android.R import com.owncloud.android.domain.files.model.OCFile import com.owncloud.android.domain.files.usecases.GetFolderContentAsLiveDataUseCase +import com.owncloud.android.domain.files.usecases.RefreshFolderFromServerAsyncUseCase import com.owncloud.android.domain.utils.Event import com.owncloud.android.presentation.UIResult import com.owncloud.android.providers.ContextProvider +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch class MainFileListViewModel( private val getFolderContentAsLiveDataUseCase: GetFolderContentAsLiveDataUseCase, + private val refreshFolderFromServerAsyncUseCase: RefreshFolderFromServerAsyncUseCase, private val contextProvider: ContextProvider, ) : ViewModel() { - private var file: OCFile? = null + private lateinit var file: OCFile private val _getFilesListStatusLiveData = MediatorLiveData>>>() val getFilesListStatusLiveData: LiveData>>> @@ -59,6 +64,13 @@ class MainFileListViewModel( } } + private fun refreshFilesList(remotePath: String) { + viewModelScope.launch(Dispatchers.IO) { + _getFilesListStatusLiveData.postValue(Event(UIResult.Loading())) + refreshFolderFromServerAsyncUseCase.execute(RefreshFolderFromServerAsyncUseCase.Params(remotePath = remotePath)) + } + } + fun manageListOfFiles(list: List) { var filesCount = 0 var foldersCount = 0 @@ -131,8 +143,13 @@ class MainFileListViewModel( } fun listDirectory(directory: OCFile) { + file = directory getFilesList(directory.id!!) } + + fun refreshDirectory() { + refreshFilesList(file.remotePath) + } } diff --git a/owncloudApp/src/main/res/layout/main_file_list_fragment.xml b/owncloudApp/src/main/res/layout/main_file_list_fragment.xml index ab316253e13..cab7c80c3e6 100644 --- a/owncloudApp/src/main/res/layout/main_file_list_fragment.xml +++ b/owncloudApp/src/main/res/layout/main_file_list_fragment.xml @@ -25,34 +25,44 @@ android:layout_height="match_parent" tools:context=".presentation.ui.files.filelist.MainFileListFragment"> - - - - - + app:layout_constraintTop_toTopOf="parent"> + + + + + + + From eef6151c2c1dd098141bfea342a77c923abd4152 Mon Sep 17 00:00:00 2001 From: Fernando Sanz Date: Thu, 25 Nov 2021 10:56:31 +0100 Subject: [PATCH 2/6] CoroutinesDispatcherProvider added. --- .../owncloud/android/dependecyinjection/ViewModelModule.kt | 2 +- .../presentation/ui/files/filelist/MainFileListViewModel.kt | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/ViewModelModule.kt b/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/ViewModelModule.kt index b1a19596fbe..690c640953d 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/ViewModelModule.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/ViewModelModule.kt @@ -75,5 +75,5 @@ val viewModelModule = module { viewModel { PreviewImageViewModel(get(), get(), get()) } viewModel { FileDetailsViewModel(get(), get(), get(), get(), get()) } viewModel { FileOperationViewModel(get(), get(), get(), get(), get(), get()) } - viewModel { MainFileListViewModel(get(), get(), get()) } + viewModel { MainFileListViewModel(get(), get(), get(), get()) } } diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/ui/files/filelist/MainFileListViewModel.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/ui/files/filelist/MainFileListViewModel.kt index a1cb23d34f0..901c9ba8fef 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/presentation/ui/files/filelist/MainFileListViewModel.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/ui/files/filelist/MainFileListViewModel.kt @@ -32,13 +32,14 @@ import com.owncloud.android.domain.files.usecases.RefreshFolderFromServerAsyncUs import com.owncloud.android.domain.utils.Event import com.owncloud.android.presentation.UIResult import com.owncloud.android.providers.ContextProvider -import kotlinx.coroutines.Dispatchers +import com.owncloud.android.providers.CoroutinesDispatcherProvider import kotlinx.coroutines.launch class MainFileListViewModel( private val getFolderContentAsLiveDataUseCase: GetFolderContentAsLiveDataUseCase, private val refreshFolderFromServerAsyncUseCase: RefreshFolderFromServerAsyncUseCase, private val contextProvider: ContextProvider, + private val coroutinesDispatcherProvider: CoroutinesDispatcherProvider, ) : ViewModel() { private lateinit var file: OCFile @@ -65,7 +66,7 @@ class MainFileListViewModel( } private fun refreshFilesList(remotePath: String) { - viewModelScope.launch(Dispatchers.IO) { + viewModelScope.launch(coroutinesDispatcherProvider.io) { _getFilesListStatusLiveData.postValue(Event(UIResult.Loading())) refreshFolderFromServerAsyncUseCase.execute(RefreshFolderFromServerAsyncUseCase.Params(remotePath = remotePath)) } From fe4312024edc2f0036c168622f49b1ba317a4fe8 Mon Sep 17 00:00:00 2001 From: Fernando Sanz Date: Thu, 25 Nov 2021 11:55:23 +0100 Subject: [PATCH 3/6] SwipeRefresh implemented. --- .../android/extensions/SwipeRefreshExt.kt | 27 +++++++++++++++++++ .../ui/files/filelist/MainFileListFragment.kt | 6 +++-- 2 files changed, 31 insertions(+), 2 deletions(-) create mode 100644 owncloudApp/src/main/java/com/owncloud/android/extensions/SwipeRefreshExt.kt diff --git a/owncloudApp/src/main/java/com/owncloud/android/extensions/SwipeRefreshExt.kt b/owncloudApp/src/main/java/com/owncloud/android/extensions/SwipeRefreshExt.kt new file mode 100644 index 00000000000..f4ad0fb8c1e --- /dev/null +++ b/owncloudApp/src/main/java/com/owncloud/android/extensions/SwipeRefreshExt.kt @@ -0,0 +1,27 @@ +/** + * ownCloud Android client application + * + * @author Fernando Sanz Velasco + * Copyright (C) 2021 ownCloud GmbH. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package com.owncloud.android.extensions + +import androidx.swiperefreshlayout.widget.SwipeRefreshLayout + +fun SwipeRefreshLayout.cancel() { + this.isRefreshing = false +} diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/ui/files/filelist/MainFileListFragment.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/ui/files/filelist/MainFileListFragment.kt index a99cff2286c..4a7f6f44c42 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/presentation/ui/files/filelist/MainFileListFragment.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/ui/files/filelist/MainFileListFragment.kt @@ -29,6 +29,7 @@ import androidx.recyclerview.widget.LinearLayoutManager import com.owncloud.android.databinding.MainFileListFragmentBinding import com.owncloud.android.domain.files.model.OCFile import com.owncloud.android.domain.utils.Event +import com.owncloud.android.extensions.cancel import com.owncloud.android.presentation.adapters.filelist.FileListAdapter import com.owncloud.android.presentation.observers.EmptyDataObserver import com.owncloud.android.presentation.onSuccess @@ -76,18 +77,19 @@ class MainFileListFragment : Fragment() { adapter = fileListAdapter } - //Set Swipe to refresh and its listener + // Set Swipe to refresh and its listener binding.swipeRefreshMainFileList.setOnRefreshListener { mainFileListViewModel.refreshDirectory() } } private fun subscribeToViewModels() { - //Observe the action of retrieving the list of files from BBDD. + // Observe the action of retrieving the list of files from DB. mainFileListViewModel.getFilesListStatusLiveData.observe(viewLifecycleOwner, Event.EventObserver { it.onSuccess { data -> val files = data ?: emptyList() fileListAdapter.updateFileList(filesToAdd = files) registerListAdapterDataObserver() mainFileListViewModel.manageListOfFiles(files) + binding.swipeRefreshMainFileList.cancel() } }) From 6c57e023c56cd96cc511c3a69d7cda8e6dca39a0 Mon Sep 17 00:00:00 2001 From: Fernando Sanz Date: Thu, 9 Dec 2021 10:59:20 +0100 Subject: [PATCH 4/6] Footer with Swipe to refresh implemented. --- .../adapters/filelist/FileListAdapter.kt | 153 +++++++++++++++--- .../ui/files/filelist/MainFileListFragment.kt | 33 +--- .../files/filelist/MainFileListViewModel.kt | 79 --------- .../utils/WrapContentLinearLayoutManager.kt | 37 +++++ .../res/layout/main_file_list_fragment.xml | 34 ++-- 5 files changed, 180 insertions(+), 156 deletions(-) create mode 100644 owncloudApp/src/main/java/com/owncloud/android/ui/utils/WrapContentLinearLayoutManager.kt diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/adapters/filelist/FileListAdapter.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/adapters/filelist/FileListAdapter.kt index 2fe6a55080f..85f7692cb6b 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/presentation/adapters/filelist/FileListAdapter.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/adapters/filelist/FileListAdapter.kt @@ -31,6 +31,7 @@ import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.RecyclerView import com.owncloud.android.R import com.owncloud.android.databinding.ItemFileListBinding +import com.owncloud.android.databinding.ListFooterBinding import com.owncloud.android.domain.files.model.OCFile import com.owncloud.android.extensions.setPicture import com.owncloud.android.presentation.diffutils.FileListDiffCallback @@ -39,10 +40,15 @@ import com.owncloud.android.utils.MimetypeIconUtil class FileListAdapter( private val context: Context, - private val listener: FileListAdapterListener -) : RecyclerView.Adapter() { + private val isShowingJustFolders: Boolean, + private val listener: FileListAdapterListener, +) : RecyclerView.Adapter() { private val files = mutableListOf() + private lateinit var viewHolder: RecyclerView.ViewHolder + + private val TYPE_ITEMS = 0 + private val TYPE_FOOTER = 1 fun updateFileList(filesToAdd: List) { val diffUtilCallback = FileListDiffCallback(oldList = files, newList = filesToAdd) @@ -52,40 +58,71 @@ class FileListAdapter( diffResult.dispatchUpdatesTo(this) } - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { - val binding = ItemFileListBinding.inflate(LayoutInflater.from(parent.context), parent, false) - - return ViewHolder(binding) + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { + when (viewType) { + TYPE_ITEMS -> { + val binding = ItemFileListBinding.inflate(LayoutInflater.from(parent.context), parent, false) + viewHolder = ViewHolder(binding) + } + TYPE_FOOTER -> { + val binding = ListFooterBinding.inflate(LayoutInflater.from(parent.context), parent, false) + viewHolder = FooterViewHolder(binding) + } + } + return viewHolder } - override fun getItemCount(): Int = files.size + override fun getItemCount(): Int { + return if (files.isNotEmpty()) { + files.size.plus(1) + } else { + files.size + } + } override fun getItemId(position: Int): Long = position.toLong() fun getItem(position: Int): Any? { - return if (files.size <= position) { + return if (files.isNotEmpty() && files.size <= position) { null } else files[position] } - override fun getItemViewType(position: Int): Int = position - - override fun onBindViewHolder(holder: ViewHolder, position: Int) { - with(holder) { - with(files[position]) { - binding.Filename.text = this.fileName - binding.fileListSize.text = DisplayUtils.bytesToHumanReadable(this.length, context) - binding.fileListLastMod.text = DisplayUtils.getRelativeTimestamp(context, this.modificationTimestamp) - binding.localFileIndicator.isVisible = false //TODO Modify in the future, when we start the synchronization task. - binding.customCheckbox.isVisible = false //TODO Modify in the future, when we start the multi selection task. - binding.thumbnail.let { - it.tag = this.id - getThumbnailPicture(imageView = it, file = this) + override fun getItemViewType(position: Int): Int { + return if (!files.isNullOrEmpty() && position == files.size) { + TYPE_FOOTER + } else { + TYPE_ITEMS + } + } + + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { + when (holder.itemViewType) { + TYPE_ITEMS -> { + with(holder as ViewHolder) { + with(files[position]) { + binding.Filename.text = this.fileName + binding.fileListSize.text = DisplayUtils.bytesToHumanReadable(this.length, context) + binding.fileListLastMod.text = DisplayUtils.getRelativeTimestamp(context, this.modificationTimestamp) + binding.localFileIndicator.isVisible = false //TODO Modify in the future, when we start the synchronization task. + binding.customCheckbox.isVisible = false //TODO Modify in the future, when we start the multi selection task. + binding.thumbnail.let { + it.tag = this.id + getThumbnailPicture(imageView = it, file = this) + } + //TODO Check this with FileListListAdapter.java and its viewType (LIST or GRID) + getSharedIcon(imageView = binding.sharedIcon, file = this) + binding.root.setOnClickListener { + listener.clickItem(this) + } + } } - //TODO Check this with FileListListAdapter.java and its viewType (LIST or GRID) - getSharedIcon(imageView = binding.sharedIcon, file = this) - binding.root.setOnClickListener { - listener.clickItem(this) + } + TYPE_FOOTER -> { + if (!isShowingJustFolders) { + with(holder as FooterViewHolder) { + binding.footerText.text = manageListOfFilesAndGenerateText(files) + } } } } @@ -119,9 +156,75 @@ class FileListAdapter( } } + private fun manageListOfFilesAndGenerateText(list: List): String { + var filesCount = 0 + var foldersCount = 0 + val count: Int = list.size + var file: OCFile + for (i in 0 until count) { + file = list[i] + if (file.isFolder) { + foldersCount++ + } else { + if (!file.isHidden) { + filesCount++ + } + } + } + + return generateFooterText(filesCount, foldersCount) + } + + private fun generateFooterText(filesCount: Int, foldersCount: Int): String { + when { + filesCount <= 0 -> { + return when { + foldersCount <= 0 -> { + "" + } + foldersCount == 1 -> { + context.getString(R.string.file_list__footer__folder) + } + else -> { // foldersCount > 1 + context.getString(R.string.file_list__footer__folders, foldersCount) + } + } + } + filesCount == 1 -> { + return when { + foldersCount <= 0 -> { + context.getString(R.string.file_list__footer__file) + } + foldersCount == 1 -> { + context.getString(R.string.file_list__footer__file_and_folder) + } + else -> { // foldersCount > 1 + context.getString(R.string.file_list__footer__file_and_folders, foldersCount) + } + } + } + else -> { // filesCount > 1 + return when { + foldersCount <= 0 -> { + context.getString(R.string.file_list__footer__files, filesCount) + } + foldersCount == 1 -> { + context.getString(R.string.file_list__footer__files_and_folder, filesCount) + } + else -> { // foldersCount > 1 + context.getString( + R.string.file_list__footer__files_and_folders, filesCount, foldersCount + ) + } + } + } + } + } + interface FileListAdapterListener { fun clickItem(ocFile: OCFile) } inner class ViewHolder(val binding: ItemFileListBinding) : RecyclerView.ViewHolder(binding.root) + inner class FooterViewHolder(val binding: ListFooterBinding) : RecyclerView.ViewHolder(binding.root) } diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/ui/files/filelist/MainFileListFragment.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/ui/files/filelist/MainFileListFragment.kt index 4a7f6f44c42..78556ce8686 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/presentation/ui/files/filelist/MainFileListFragment.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/ui/files/filelist/MainFileListFragment.kt @@ -25,7 +25,6 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.fragment.app.Fragment -import androidx.recyclerview.widget.LinearLayoutManager import com.owncloud.android.databinding.MainFileListFragmentBinding import com.owncloud.android.domain.files.model.OCFile import com.owncloud.android.domain.utils.Event @@ -33,6 +32,7 @@ import com.owncloud.android.extensions.cancel import com.owncloud.android.presentation.adapters.filelist.FileListAdapter import com.owncloud.android.presentation.observers.EmptyDataObserver import com.owncloud.android.presentation.onSuccess +import com.owncloud.android.ui.utils.WrapContentLinearLayoutManager import org.koin.androidx.viewmodel.ext.android.viewModel class MainFileListFragment : Fragment() { @@ -60,7 +60,7 @@ class MainFileListFragment : Fragment() { private fun initViews() { //Set RecyclerView and its adapter. - fileListAdapter = FileListAdapter(context = requireContext(), listener = object : + fileListAdapter = FileListAdapter(context = requireContext(), isShowingJustFolders = isShowingJustFolders(), listener = object : FileListAdapter.FileListAdapterListener { override fun clickItem(ocFile: OCFile) { if (ocFile.isFolder) { @@ -71,9 +71,9 @@ class MainFileListFragment : Fragment() { } } - } ) + }) binding.recyclerViewMainFileList.apply { - layoutManager = LinearLayoutManager(requireContext()) + layoutManager = WrapContentLinearLayoutManager(requireContext()) adapter = fileListAdapter } @@ -88,40 +88,15 @@ class MainFileListFragment : Fragment() { val files = data ?: emptyList() fileListAdapter.updateFileList(filesToAdd = files) registerListAdapterDataObserver() - mainFileListViewModel.manageListOfFiles(files) binding.swipeRefreshMainFileList.cancel() } }) - - mainFileListViewModel.numberOfFilesPerType.observe(viewLifecycleOwner, Event.EventObserver { - it.onSuccess { data -> - if (!isShowingJustFolders()) { - mainFileListViewModel.generateFooterText(data!!.first, data.second) - } - } - }) - - mainFileListViewModel.footerText.observe(viewLifecycleOwner, Event.EventObserver { - it.onSuccess { data -> - setFooterText(data) - } - }) } fun listDirectory(directory: OCFile) { mainFileListViewModel.listDirectory(directory = directory) } - private fun setFooterText(text: String?) { - if (text?.isNotEmpty() == true) { - binding.footerMainFileList.footerText.text = text - // TODO Manage footer enable/disable options - //setFooterEnabled(true) - } else { - //setFooterEnabled(false) - } - } - private fun isShowingJustFolders(): Boolean { val args = arguments return args != null && args.getBoolean(ARG_JUST_FOLDERS, false) diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/ui/files/filelist/MainFileListViewModel.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/ui/files/filelist/MainFileListViewModel.kt index 901c9ba8fef..715ce3657a9 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/presentation/ui/files/filelist/MainFileListViewModel.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/ui/files/filelist/MainFileListViewModel.kt @@ -48,14 +48,6 @@ class MainFileListViewModel( val getFilesListStatusLiveData: LiveData>>> get() = _getFilesListStatusLiveData - private val _numberOfFilesPerType = MutableLiveData>>>() - val numberOfFilesPerType: LiveData>>> - get() = _numberOfFilesPerType - - private val _footerText = MutableLiveData>>() - val footerText: LiveData>> - get() = _footerText - private fun getFilesList(folderId: Long) { val filesListLiveData: LiveData> = getFolderContentAsLiveDataUseCase.execute(GetFolderContentAsLiveDataUseCase.Params(folderId = folderId)) @@ -72,77 +64,6 @@ class MainFileListViewModel( } } - fun manageListOfFiles(list: List) { - var filesCount = 0 - var foldersCount = 0 - val count: Int = list.size - var file: OCFile - for (i in 0 until count) { - file = list[i] - if (file.isFolder) { - foldersCount++ - } else { - if (!file.isHidden) { - filesCount++ - } - } - } - - _numberOfFilesPerType.postValue(Event(UIResult.Success(Pair(foldersCount, filesCount)))) - } - - fun generateFooterText(filesCount: Int, foldersCount: Int) { - _footerText.postValue( - Event( - UIResult.Success( - when { - filesCount <= 0 -> { - when { - foldersCount <= 0 -> { - "" - } - foldersCount == 1 -> { - contextProvider.getContext().getString(R.string.file_list__footer__folder) - } - else -> { // foldersCount > 1 - contextProvider.getContext().getString(R.string.file_list__footer__folders, foldersCount) - } - } - } - filesCount == 1 -> { - when { - foldersCount <= 0 -> { - contextProvider.getContext().getString(R.string.file_list__footer__file) - } - foldersCount == 1 -> { - contextProvider.getContext().getString(R.string.file_list__footer__file_and_folder) - } - else -> { // foldersCount > 1 - contextProvider.getContext().getString(R.string.file_list__footer__file_and_folders, foldersCount) - } - } - } - else -> { // filesCount > 1 - when { - foldersCount <= 0 -> { - contextProvider.getContext().getString(R.string.file_list__footer__files, filesCount) - } - foldersCount == 1 -> { - contextProvider.getContext().getString(R.string.file_list__footer__files_and_folder, filesCount) - } - else -> { // foldersCount > 1 - contextProvider.getContext().getString( - R.string.file_list__footer__files_and_folders, filesCount, foldersCount - ) - } - } - } - } - ) - ) - ) - } - fun listDirectory(directory: OCFile) { file = directory getFilesList(directory.id!!) diff --git a/owncloudApp/src/main/java/com/owncloud/android/ui/utils/WrapContentLinearLayoutManager.kt b/owncloudApp/src/main/java/com/owncloud/android/ui/utils/WrapContentLinearLayoutManager.kt new file mode 100644 index 00000000000..02dffd5bac0 --- /dev/null +++ b/owncloudApp/src/main/java/com/owncloud/android/ui/utils/WrapContentLinearLayoutManager.kt @@ -0,0 +1,37 @@ +/** + * ownCloud Android client application + * + * @author Fernando Sanz Velasco + * Copyright (C) 2021 ownCloud GmbH. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package com.owncloud.android.ui.utils + +import android.content.Context +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import timber.log.Timber + +class WrapContentLinearLayoutManager(context: Context) : LinearLayoutManager(context) { + + override fun onLayoutChildren(recycler: RecyclerView.Recycler?, state: RecyclerView.State?) { + try { + super.onLayoutChildren(recycler, state) + } catch (e: IndexOutOfBoundsException) { + Timber.e("IndexOutOfBoundsException with recyclerView") + } + } +} diff --git a/owncloudApp/src/main/res/layout/main_file_list_fragment.xml b/owncloudApp/src/main/res/layout/main_file_list_fragment.xml index cab7c80c3e6..588c94abbf9 100644 --- a/owncloudApp/src/main/res/layout/main_file_list_fragment.xml +++ b/owncloudApp/src/main/res/layout/main_file_list_fragment.xml @@ -29,6 +29,7 @@ android:id="@+id/swipeRefresh_main_file_list" android:layout_width="match_parent" android:layout_height="match_parent" + app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintStart_toStartOf="parent" @@ -38,31 +39,18 @@ android:id="@+id/recyclerView_main_file_list" android:layout_width="0dp" android:layout_height="0dp" - app:layout_constraintBottom_toTopOf="@id/footer_main_file_list" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toTopOf="parent" tools:listitem="@layout/item_file_list" /> - + - + - From 47d2d976bc74910eb571db951ed188b53b4911b9 Mon Sep 17 00:00:00 2001 From: Fernando Sanz Date: Thu, 9 Dec 2021 11:01:51 +0100 Subject: [PATCH 5/6] Context provider deleted. --- .../com/owncloud/android/dependecyinjection/ViewModelModule.kt | 2 +- .../presentation/ui/files/filelist/MainFileListViewModel.kt | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/ViewModelModule.kt b/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/ViewModelModule.kt index 690c640953d..b1a19596fbe 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/ViewModelModule.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/ViewModelModule.kt @@ -75,5 +75,5 @@ val viewModelModule = module { viewModel { PreviewImageViewModel(get(), get(), get()) } viewModel { FileDetailsViewModel(get(), get(), get(), get(), get()) } viewModel { FileOperationViewModel(get(), get(), get(), get(), get(), get()) } - viewModel { MainFileListViewModel(get(), get(), get(), get()) } + viewModel { MainFileListViewModel(get(), get(), get()) } } diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/ui/files/filelist/MainFileListViewModel.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/ui/files/filelist/MainFileListViewModel.kt index 715ce3657a9..5454ba5bb39 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/presentation/ui/files/filelist/MainFileListViewModel.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/ui/files/filelist/MainFileListViewModel.kt @@ -22,10 +22,8 @@ package com.owncloud.android.presentation.ui.files.filelist import androidx.lifecycle.LiveData import androidx.lifecycle.MediatorLiveData -import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.owncloud.android.R import com.owncloud.android.domain.files.model.OCFile import com.owncloud.android.domain.files.usecases.GetFolderContentAsLiveDataUseCase import com.owncloud.android.domain.files.usecases.RefreshFolderFromServerAsyncUseCase @@ -38,7 +36,6 @@ import kotlinx.coroutines.launch class MainFileListViewModel( private val getFolderContentAsLiveDataUseCase: GetFolderContentAsLiveDataUseCase, private val refreshFolderFromServerAsyncUseCase: RefreshFolderFromServerAsyncUseCase, - private val contextProvider: ContextProvider, private val coroutinesDispatcherProvider: CoroutinesDispatcherProvider, ) : ViewModel() { From bb63e2b447d751146f0167f50e0acc581752b80b Mon Sep 17 00:00:00 2001 From: Fernando Sanz Date: Thu, 9 Dec 2021 12:50:03 +0100 Subject: [PATCH 6/6] Footer with Swipe to refresh improved. --- .../adapters/filelist/FileListAdapter.kt | 28 +++++++---------- .../diffutils/FileListDiffCallback.kt | 31 ++++++++++++++----- .../ui/files/filelist/MainFileListFragment.kt | 4 +-- .../domain/files/model/OCFooterFile.kt | 20 +++--------- 4 files changed, 41 insertions(+), 42 deletions(-) rename owncloudApp/src/main/java/com/owncloud/android/ui/utils/WrapContentLinearLayoutManager.kt => owncloudDomain/src/main/java/com/owncloud/android/domain/files/model/OCFooterFile.kt (54%) diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/adapters/filelist/FileListAdapter.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/adapters/filelist/FileListAdapter.kt index 85f7692cb6b..4c9b9e5e723 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/presentation/adapters/filelist/FileListAdapter.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/adapters/filelist/FileListAdapter.kt @@ -33,6 +33,7 @@ import com.owncloud.android.R import com.owncloud.android.databinding.ItemFileListBinding import com.owncloud.android.databinding.ListFooterBinding import com.owncloud.android.domain.files.model.OCFile +import com.owncloud.android.domain.files.model.OCFooterFile import com.owncloud.android.extensions.setPicture import com.owncloud.android.presentation.diffutils.FileListDiffCallback import com.owncloud.android.utils.DisplayUtils @@ -44,7 +45,7 @@ class FileListAdapter( private val listener: FileListAdapterListener, ) : RecyclerView.Adapter() { - private val files = mutableListOf() + private val files = mutableListOf() private lateinit var viewHolder: RecyclerView.ViewHolder private val TYPE_ITEMS = 0 @@ -55,6 +56,9 @@ class FileListAdapter( val diffResult = DiffUtil.calculateDiff(diffUtilCallback) files.clear() files.addAll(filesToAdd) + if (filesToAdd.isNotEmpty()) { + files.add(OCFooterFile(manageListOfFilesAndGenerateText(filesToAdd))) + } diffResult.dispatchUpdatesTo(this) } @@ -72,24 +76,12 @@ class FileListAdapter( return viewHolder } - override fun getItemCount(): Int { - return if (files.isNotEmpty()) { - files.size.plus(1) - } else { - files.size - } - } + override fun getItemCount(): Int = files.size override fun getItemId(position: Int): Long = position.toLong() - fun getItem(position: Int): Any? { - return if (files.isNotEmpty() && files.size <= position) { - null - } else files[position] - } - override fun getItemViewType(position: Int): Int { - return if (!files.isNullOrEmpty() && position == files.size) { + return if (position == files.size.minus(1)) { TYPE_FOOTER } else { TYPE_ITEMS @@ -100,7 +92,7 @@ class FileListAdapter( when (holder.itemViewType) { TYPE_ITEMS -> { with(holder as ViewHolder) { - with(files[position]) { + with(files[position] as OCFile) { binding.Filename.text = this.fileName binding.fileListSize.text = DisplayUtils.bytesToHumanReadable(this.length, context) binding.fileListLastMod.text = DisplayUtils.getRelativeTimestamp(context, this.modificationTimestamp) @@ -121,7 +113,9 @@ class FileListAdapter( TYPE_FOOTER -> { if (!isShowingJustFolders) { with(holder as FooterViewHolder) { - binding.footerText.text = manageListOfFilesAndGenerateText(files) + with(files[position] as OCFooterFile) { + binding.footerText.text = this.text + } } } } diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/diffutils/FileListDiffCallback.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/diffutils/FileListDiffCallback.kt index d2c09afad6d..810fe82d6c3 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/presentation/diffutils/FileListDiffCallback.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/diffutils/FileListDiffCallback.kt @@ -22,20 +22,37 @@ package com.owncloud.android.presentation.diffutils import androidx.recyclerview.widget.DiffUtil import com.owncloud.android.domain.files.model.OCFile +import com.owncloud.android.domain.files.model.OCFooterFile -class FileListDiffCallback(private val oldList: List, private val newList: List) : DiffUtil.Callback() { +class FileListDiffCallback(private val oldList: List, private val newList: List) : DiffUtil.Callback() { override fun getOldListSize(): Int = oldList.size override fun getNewListSize(): Int = newList.size override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { - return oldList[oldItemPosition].id === newList[newItemPosition].id - } + val oldItem = oldList[oldItemPosition] + val newItem = newList[newItemPosition] + + if (oldItem is Unit && newItem is Unit) { + return true + } + + if (oldItem is Boolean && newItem is Boolean) { + return true + } - override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { - val (_, value, name) = oldList[oldItemPosition] - val (_, value1, name1) = newList[newItemPosition] - return name == name1 && value == value1 + if (oldItem is OCFile && newItem is OCFile) { + return oldItem.id == newItem.id + } + + if (oldItem is OCFooterFile && newItem is OCFooterFile) { + return oldItem.text == newItem.text + } + + return false } + + override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean = + oldList[oldItemPosition] == newList[newItemPosition] } diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/ui/files/filelist/MainFileListFragment.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/ui/files/filelist/MainFileListFragment.kt index 78556ce8686..a31ade172ce 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/presentation/ui/files/filelist/MainFileListFragment.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/ui/files/filelist/MainFileListFragment.kt @@ -25,6 +25,7 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.fragment.app.Fragment +import androidx.recyclerview.widget.LinearLayoutManager import com.owncloud.android.databinding.MainFileListFragmentBinding import com.owncloud.android.domain.files.model.OCFile import com.owncloud.android.domain.utils.Event @@ -32,7 +33,6 @@ import com.owncloud.android.extensions.cancel import com.owncloud.android.presentation.adapters.filelist.FileListAdapter import com.owncloud.android.presentation.observers.EmptyDataObserver import com.owncloud.android.presentation.onSuccess -import com.owncloud.android.ui.utils.WrapContentLinearLayoutManager import org.koin.androidx.viewmodel.ext.android.viewModel class MainFileListFragment : Fragment() { @@ -73,7 +73,7 @@ class MainFileListFragment : Fragment() { }) binding.recyclerViewMainFileList.apply { - layoutManager = WrapContentLinearLayoutManager(requireContext()) + layoutManager = LinearLayoutManager(requireContext()) adapter = fileListAdapter } diff --git a/owncloudApp/src/main/java/com/owncloud/android/ui/utils/WrapContentLinearLayoutManager.kt b/owncloudDomain/src/main/java/com/owncloud/android/domain/files/model/OCFooterFile.kt similarity index 54% rename from owncloudApp/src/main/java/com/owncloud/android/ui/utils/WrapContentLinearLayoutManager.kt rename to owncloudDomain/src/main/java/com/owncloud/android/domain/files/model/OCFooterFile.kt index 02dffd5bac0..0fedf80cbb3 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/ui/utils/WrapContentLinearLayoutManager.kt +++ b/owncloudDomain/src/main/java/com/owncloud/android/domain/files/model/OCFooterFile.kt @@ -18,20 +18,8 @@ * */ -package com.owncloud.android.ui.utils +package com.owncloud.android.domain.files.model -import android.content.Context -import androidx.recyclerview.widget.LinearLayoutManager -import androidx.recyclerview.widget.RecyclerView -import timber.log.Timber - -class WrapContentLinearLayoutManager(context: Context) : LinearLayoutManager(context) { - - override fun onLayoutChildren(recycler: RecyclerView.Recycler?, state: RecyclerView.State?) { - try { - super.onLayoutChildren(recycler, state) - } catch (e: IndexOutOfBoundsException) { - Timber.e("IndexOutOfBoundsException with recyclerView") - } - } -} +data class OCFooterFile( + val text: String = "" +)