diff --git a/app/src/main/java/com/nextcloud/client/database/dao/FileDao.kt b/app/src/main/java/com/nextcloud/client/database/dao/FileDao.kt index 7f15f5ec3252..f8d5f3855c30 100644 --- a/app/src/main/java/com/nextcloud/client/database/dao/FileDao.kt +++ b/app/src/main/java/com/nextcloud/client/database/dao/FileDao.kt @@ -56,6 +56,15 @@ interface FileDao { ) fun getGalleryItems(startDate: Long, endDate: Long, fileOwner: String): List + @Query( + "SELECT * FROM filelist WHERE modified >= :startDate" + + " AND modified < :endDate" + + " AND (content_type LIKE 'image/%' OR content_type LIKE 'video/%')" + + " AND file_owner = :fileOwner" + + " ORDER BY ${ProviderTableMeta.FILE_DEFAULT_SORT_ORDER}" + ) + suspend fun getGalleryItemsSuspended(startDate: Long, endDate: Long, fileOwner: String): List + @Query("SELECT * FROM filelist WHERE file_owner = :fileOwner ORDER BY ${ProviderTableMeta.FILE_DEFAULT_SORT_ORDER}") fun getAllFiles(fileOwner: String): List diff --git a/app/src/main/java/com/nextcloud/utils/extensions/FileDataStorageManagerExtensions.kt b/app/src/main/java/com/nextcloud/utils/extensions/FileDataStorageManagerExtensions.kt index d98a06cb4138..e163c5a861a8 100644 --- a/app/src/main/java/com/nextcloud/utils/extensions/FileDataStorageManagerExtensions.kt +++ b/app/src/main/java/com/nextcloud/utils/extensions/FileDataStorageManagerExtensions.kt @@ -25,6 +25,11 @@ suspend fun FileDataStorageManager.saveShares(shares: List, accountName } } +suspend fun FileDataStorageManager.getAllGalleryItemsSuspended(): List { + val fileEntities = fileDao.getGalleryItemsSuspended(0, Long.MAX_VALUE, user.accountName) + return fileEntities.map { createFileInstance(it) } +} + fun FileDataStorageManager.searchFilesByName(file: OCFile, accountName: String, query: String): List = fileDao.searchFilesInFolder(file.fileId, accountName, query).map { createFileInstance(it) diff --git a/app/src/main/java/com/nextcloud/utils/extensions/OCFileExtensions.kt b/app/src/main/java/com/nextcloud/utils/extensions/OCFileExtensions.kt index 51a7f21e523f..b8db4fa83bc4 100644 --- a/app/src/main/java/com/nextcloud/utils/extensions/OCFileExtensions.kt +++ b/app/src/main/java/com/nextcloud/utils/extensions/OCFileExtensions.kt @@ -8,12 +8,16 @@ package com.nextcloud.utils.extensions import com.owncloud.android.MainApp +import com.owncloud.android.datamodel.GalleryItems +import com.owncloud.android.datamodel.GalleryRow import com.owncloud.android.datamodel.OCFile import com.owncloud.android.datamodel.OCFileDepth import com.owncloud.android.datamodel.OCFileDepth.DeepLevel import com.owncloud.android.datamodel.OCFileDepth.FirstLevel import com.owncloud.android.datamodel.OCFileDepth.Root import com.owncloud.android.utils.FileStorageUtils +import java.util.Calendar +import java.util.Date fun List.filterFilenames(): List = distinctBy { it.fileName } @@ -50,3 +54,31 @@ fun OCFile?.getDepth(): OCFileDepth? { // Otherwise, it's a subdirectory of a subdirectory return DeepLevel } + +fun List.toGalleryItems(columns: Int, defaultSize: Int): List { + if (isEmpty()) return emptyList() + + val calendar = Calendar.getInstance() + return groupBy { + calendar.timeInMillis = it.modificationTimestamp + calendar.set(Calendar.DAY_OF_MONTH, 1) + calendar.set(Calendar.HOUR_OF_DAY, 0) + calendar.set(Calendar.MINUTE, 0) + calendar.set(Calendar.SECOND, 0) + calendar.set(Calendar.MILLISECOND, 0) + calendar.timeInMillis + } + .map { (date, filesList) -> + GalleryItems(date, transformToRows(filesList, columns, defaultSize)) + } + .sortedByDescending { it.date } +} + +private fun transformToRows(list: List, columns: Int, defaultSize: Int): List { + if (list.isEmpty()) return emptyList() + + return list + .sortedByDescending { it.modificationTimestamp } + .chunked(columns) + .map { chunk -> GalleryRow(chunk, defaultSize, defaultSize) } +} diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/GalleryAdapter.kt b/app/src/main/java/com/owncloud/android/ui/adapter/GalleryAdapter.kt index 864e856999c5..41df5a06cb99 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/GalleryAdapter.kt +++ b/app/src/main/java/com/owncloud/android/ui/adapter/GalleryAdapter.kt @@ -23,25 +23,19 @@ import com.afollestad.sectionedrecyclerview.SectionedRecyclerViewAdapter import com.afollestad.sectionedrecyclerview.SectionedViewHolder import com.nextcloud.client.account.User import com.nextcloud.client.preferences.AppPreferences +import com.nextcloud.utils.extensions.toGalleryItems import com.owncloud.android.databinding.GalleryHeaderBinding import com.owncloud.android.databinding.GalleryRowBinding import com.owncloud.android.datamodel.FileDataStorageManager import com.owncloud.android.datamodel.GalleryItems -import com.owncloud.android.datamodel.GalleryRow import com.owncloud.android.datamodel.OCFile import com.owncloud.android.lib.common.utils.Log_OC import com.owncloud.android.ui.activity.ComponentsGetter -import com.owncloud.android.ui.fragment.GalleryFragment -import com.owncloud.android.ui.fragment.GalleryFragmentBottomSheetDialog -import com.owncloud.android.ui.fragment.SearchType import com.owncloud.android.ui.interfaces.OCFileListFragmentInterface import com.owncloud.android.utils.DisplayUtils import com.owncloud.android.utils.FileSortOrder -import com.owncloud.android.utils.MimeTypeUtil import com.owncloud.android.utils.theme.ViewThemeUtils import me.zhanghai.android.fastscroll.PopupTextProvider -import java.util.Calendar -import java.util.Date @Suppress("LongParameterList", "TooManyFunctions") class GalleryAdapter( @@ -210,72 +204,17 @@ class GalleryAdapter( } @SuppressLint("NotifyDataSetChanged") - fun showAllGalleryItems( - remotePath: String, - mediaState: GalleryFragmentBottomSheetDialog.MediaState, - photoFragment: GalleryFragment - ) { - val items = storageManager.allGalleryItems - - val filteredList = items.filter { it != null && it.remotePath.startsWith(remotePath) } - - setMediaFilter( - filteredList, - mediaState, - photoFragment - ) - } - - // Set Image/Video List According to Selection of Hide/Show Image/Video - @SuppressLint("NotifyDataSetChanged") - private fun setMediaFilter( - items: List, - mediaState: GalleryFragmentBottomSheetDialog.MediaState, - photoFragment: GalleryFragment - ) { - val finalSortedList: List = when (mediaState) { - GalleryFragmentBottomSheetDialog.MediaState.MEDIA_STATE_PHOTOS_ONLY -> { - items.filter { MimeTypeUtil.isImage(it.mimeType) }.distinct() - } - - GalleryFragmentBottomSheetDialog.MediaState.MEDIA_STATE_VIDEOS_ONLY -> { - items.filter { MimeTypeUtil.isVideo(it.mimeType) }.distinct() - } - - else -> items - } - - if (finalSortedList.isEmpty()) { - photoFragment.setEmptyListMessage(SearchType.GALLERY_SEARCH) - } - - files = finalSortedList.toGalleryItems() + fun updateList(items: List) { + files = items notifyDataSetChanged() } - private fun transformToRows(list: List): List { - if (list.isEmpty()) return emptyList() - - return list - .sortedByDescending { it.modificationTimestamp } - .chunked(columns) - .map { chunk -> GalleryRow(chunk, defaultThumbnailSize, defaultThumbnailSize) } - } - @SuppressLint("NotifyDataSetChanged") fun clear() { files = emptyList() notifyDataSetChanged() } - private fun firstOfMonth(timestamp: Long): Long = Calendar.getInstance().apply { - time = Date(timestamp) - set(Calendar.DAY_OF_MONTH, getActualMinimum(Calendar.DAY_OF_MONTH)) - set(Calendar.HOUR_OF_DAY, 0) - set(Calendar.MINUTE, 0) - set(Calendar.SECOND, 0) - }.timeInMillis - fun isEmpty(): Boolean = files.isEmpty() fun getItem(position: Int): OCFile? { @@ -365,21 +304,11 @@ class GalleryAdapter( val allFiles = getAllFiles() allFiles.firstOrNull { it.remotePath == remotePath }?.also { file -> file.isFavorite = favorite - files = allFiles.toGalleryItems() + files = allFiles.toGalleryItems(columns, defaultThumbnailSize) notifyItemChanged(file) } } - private fun List.toGalleryItems(): List { - if (isEmpty()) return emptyList() - - return groupBy { firstOfMonth(it.modificationTimestamp) } - .map { (date, filesList) -> - GalleryItems(date, transformToRows(filesList)) - } - .sortedByDescending { it.date } - } - override fun onBindFooterViewHolder(holder: SectionedViewHolder?, section: Int) = Unit override fun swapDirectory( diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/GalleryFragment.kt b/app/src/main/java/com/owncloud/android/ui/fragment/GalleryFragment.kt index 5842aa041c7e..1ab8e8334dde 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/GalleryFragment.kt +++ b/app/src/main/java/com/owncloud/android/ui/fragment/GalleryFragment.kt @@ -24,12 +24,15 @@ import androidx.activity.result.contract.ActivityResultContracts import androidx.core.view.MenuHost import androidx.core.view.MenuProvider import androidx.lifecycle.Lifecycle +import androidx.lifecycle.lifecycleScope import androidx.localbroadcastmanager.content.LocalBroadcastManager import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.RecyclerView +import com.nextcloud.utils.extensions.getAllGalleryItemsSuspended import com.nextcloud.utils.extensions.getParcelableArgument import kotlinx.coroutines.Job import com.nextcloud.utils.extensions.getTypedActivity +import com.nextcloud.utils.extensions.toGalleryItems import com.owncloud.android.BuildConfig import com.owncloud.android.R import com.owncloud.android.datamodel.OCFile @@ -44,6 +47,10 @@ import com.owncloud.android.ui.adapter.GalleryAdapter import com.owncloud.android.ui.asynctasks.GallerySearchTask import com.owncloud.android.ui.events.ChangeMenuEvent import com.owncloud.android.ui.fragment.GalleryFragmentBottomSheetDialog.MediaState +import com.owncloud.android.utils.MimeTypeUtil +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext @Suppress("ForbiddenComment", "ReturnCount", "MagicNumber", "MaxLineLength") class GalleryFragment : @@ -51,6 +58,7 @@ class GalleryFragment : GalleryFragmentBottomSheetActions { var isPhotoSearchQueryRunning: Boolean = false private var photoSearchTask: Job? = null + private var showGalleryJob: Job? = null private var endDate: Long = 0 private val limit = 150 private var adapter: GalleryAdapter? = null @@ -124,10 +132,11 @@ class GalleryFragment : } override fun onDestroyView() { - if (photoSearchTask != null) { - photoSearchTask?.cancel() - photoSearchTask = null - } + showGalleryJob?.cancel() + showGalleryJob = null + + photoSearchTask?.cancel() + photoSearchTask = null LocalBroadcastManager.getInstance(requireContext()).unregisterReceiver(refreshSearchEventReceiver) @@ -372,12 +381,32 @@ class GalleryFragment : fun showAllGalleryItems() { val mediaState = bottomSheet?.currMediaState ?: return - adapter?.showAllGalleryItems( - preferences.getLastSelectedMediaFolder(), - mediaState, - this - ) - updateSubtitle(mediaState) + showGalleryJob?.cancel() + showGalleryJob = lifecycleScope.launch(Dispatchers.Default) { + val remotePath = preferences.getLastSelectedMediaFolder() + val items = mContainerActivity.storageManager.getAllGalleryItemsSuspended() + + val isPhotosOnly = mediaState == MediaState.MEDIA_STATE_PHOTOS_ONLY + val isVideosOnly = mediaState == MediaState.MEDIA_STATE_VIDEOS_ONLY + + val filteredItems = items.filter { + if (!it.remotePath.startsWith(remotePath)) return@filter false + if (isPhotosOnly) return@filter MimeTypeUtil.isImage(it.mimeType) + if (isVideosOnly) return@filter MimeTypeUtil.isVideo(it.mimeType) + true + } + + val galleryItems = + filteredItems.toGalleryItems(columnsCount, ThumbnailsCacheManager.getThumbnailDimension()) + + withContext(Dispatchers.Main) { + if (galleryItems.isEmpty()) { + setEmptyListMessage(SearchType.GALLERY_SEARCH) + } + adapter?.updateList(galleryItems) + updateSubtitle(mediaState) + } + } } private fun updateSubtitle(mediaState: MediaState?) {