diff --git a/app/src/main/java/com/infomaniak/drive/ui/menu/GalleryAdapter.kt b/app/src/main/java/com/infomaniak/drive/ui/menu/GalleryAdapter.kt index d47d00a3f2..06159dcfef 100644 --- a/app/src/main/java/com/infomaniak/drive/ui/menu/GalleryAdapter.kt +++ b/app/src/main/java/com/infomaniak/drive/ui/menu/GalleryAdapter.kt @@ -143,9 +143,15 @@ class GalleryAdapter( updateMultiSelect?.invoke() } - fun insertDuplicatedImages(prefix: List) { - val index = if (prefix.firstOrNull() is File && itemList.firstOrNull() is String) 1 else 0 - itemList.addAll(index, prefix) + fun setDisplayList(displayList: List) { + itemList.clear() + itemList.addAll(displayList) + notifyDataSetChanged() + } + + fun insertDuplicatedImages(prefix: List, offset: Int) { + itemList.addAll(offset, prefix) + notifyItemRangeInserted(offset, prefix.size) } fun clearGallery() { diff --git a/app/src/main/java/com/infomaniak/drive/ui/menu/GalleryFragment.kt b/app/src/main/java/com/infomaniak/drive/ui/menu/GalleryFragment.kt index ffd63dd47b..fb1325f313 100644 --- a/app/src/main/java/com/infomaniak/drive/ui/menu/GalleryFragment.kt +++ b/app/src/main/java/com/infomaniak/drive/ui/menu/GalleryFragment.kt @@ -274,6 +274,13 @@ class GalleryFragment : MultiSelectFragment( } } + val currentPeriod get() = galleryViewModel.period + + fun onPeriodSelected(newPeriod: GalleryPeriod) { + val displayList = galleryViewModel.updatePeriod(newPeriod) ?: return + galleryAdapter.setDisplayList(displayList) + } + fun onRefreshGallery() { if (isResumed) { galleryViewModel.clearGallery() @@ -342,15 +349,8 @@ class GalleryFragment : MultiSelectFragment( override fun onAllIndividualActionsFinished(type: BulkOperationType) { if (type == BulkOperationType.COPY) { - val oldTotal = galleryAdapter.itemList.size - val oldFirstItem = galleryAdapter.itemList.firstOrNull() - - galleryAdapter.insertDuplicatedImages(galleryViewModel.prependDuplicatedImages(oldFirstItem)) - val newTotal = galleryAdapter.itemList.count() - val newFirstItem = galleryAdapter.itemList.firstOrNull() - - val positionStart = if (oldFirstItem != newFirstItem) 0 else 1 - galleryAdapter.notifyItemRangeInserted(positionStart, newTotal - oldTotal) + val (prefix, offset) = galleryViewModel.prependDuplicatedImages(galleryAdapter.itemList.firstOrNull()) + galleryAdapter.insertDuplicatedImages(prefix, offset) } } diff --git a/app/src/main/java/com/infomaniak/drive/ui/menu/GalleryPeriod.kt b/app/src/main/java/com/infomaniak/drive/ui/menu/GalleryPeriod.kt new file mode 100644 index 0000000000..c62c32c6e1 --- /dev/null +++ b/app/src/main/java/com/infomaniak/drive/ui/menu/GalleryPeriod.kt @@ -0,0 +1,27 @@ +/* + * Infomaniak kDrive - Android + * Copyright (C) 2026 Infomaniak Network SA + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.infomaniak.drive.ui.menu + +import androidx.annotation.StringRes +import com.infomaniak.drive.R + +enum class GalleryPeriod(@StringRes val translation: Int, val pattern: String) { + DAY(R.string.sortDay, "d MMMM yyyy"), + MONTH(R.string.sortMonth, "MMMM yyyy"), + YEAR(R.string.sortYear, "yyyy"), +} diff --git a/app/src/main/java/com/infomaniak/drive/ui/menu/GalleryPeriodBottomSheetAdapter.kt b/app/src/main/java/com/infomaniak/drive/ui/menu/GalleryPeriodBottomSheetAdapter.kt new file mode 100644 index 0000000000..55ac5fa027 --- /dev/null +++ b/app/src/main/java/com/infomaniak/drive/ui/menu/GalleryPeriodBottomSheetAdapter.kt @@ -0,0 +1,50 @@ +/* + * Infomaniak kDrive - Android + * Copyright (C) 2026 Infomaniak Network SA + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.infomaniak.drive.ui.menu + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.core.view.isVisible +import androidx.recyclerview.widget.RecyclerView.Adapter +import androidx.recyclerview.widget.RecyclerView.ViewHolder +import com.infomaniak.drive.databinding.ItemSelectBottomSheetBinding +import com.infomaniak.drive.ui.menu.GalleryPeriodBottomSheetAdapter.GalleryPeriodViewHolder + +class GalleryPeriodBottomSheetAdapter( + private val selectedPeriod: GalleryPeriod, + private val onItemClicked: (period: GalleryPeriod) -> Unit, +) : Adapter() { + + private val periods = GalleryPeriod.entries + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): GalleryPeriodViewHolder { + return GalleryPeriodViewHolder(ItemSelectBottomSheetBinding.inflate(LayoutInflater.from(parent.context), parent, false)) + } + + override fun onBindViewHolder(holder: GalleryPeriodViewHolder, position: Int) = with(holder.binding) { + periods[position].let { period -> + itemSelectText.setText(period.translation) + itemSelectActiveIcon.isVisible = selectedPeriod == period + root.setOnClickListener { onItemClicked(period) } + } + } + + override fun getItemCount() = periods.size + + class GalleryPeriodViewHolder(val binding: ItemSelectBottomSheetBinding) : ViewHolder(binding.root) +} diff --git a/app/src/main/java/com/infomaniak/drive/ui/menu/GalleryPeriodBottomSheetDialog.kt b/app/src/main/java/com/infomaniak/drive/ui/menu/GalleryPeriodBottomSheetDialog.kt new file mode 100644 index 0000000000..5f2f578816 --- /dev/null +++ b/app/src/main/java/com/infomaniak/drive/ui/menu/GalleryPeriodBottomSheetDialog.kt @@ -0,0 +1,45 @@ +/* + * Infomaniak kDrive - Android + * Copyright (C) 2026 Infomaniak Network SA + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.infomaniak.drive.ui.menu + +import android.os.Bundle +import android.view.View +import androidx.navigation.fragment.navArgs +import com.infomaniak.core.legacy.utils.setBackNavigationResult +import com.infomaniak.drive.R +import com.infomaniak.drive.views.SelectBottomSheetDialog + +class GalleryPeriodBottomSheetDialog : SelectBottomSheetDialog() { + + private val navigationArgs: GalleryPeriodBottomSheetDialogArgs by navArgs() + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) = with(binding) { + super.onViewCreated(view, savedInstanceState) + + selectTitle.setText(R.string.sortTitle) + + selectRecyclerView.adapter = GalleryPeriodBottomSheetAdapter( + selectedPeriod = navigationArgs.period, + onItemClicked = { period -> setBackNavigationResult(GALLERY_PERIOD_KEY, period) }, + ) + } + + companion object { + const val GALLERY_PERIOD_KEY = "gallery_period" + } +} diff --git a/app/src/main/java/com/infomaniak/drive/ui/menu/GalleryViewModel.kt b/app/src/main/java/com/infomaniak/drive/ui/menu/GalleryViewModel.kt index bdd0bb1c17..5684f2e134 100644 --- a/app/src/main/java/com/infomaniak/drive/ui/menu/GalleryViewModel.kt +++ b/app/src/main/java/com/infomaniak/drive/ui/menu/GalleryViewModel.kt @@ -35,6 +35,9 @@ class GalleryViewModel : ViewModel() { val galleryList = arrayListOf() val duplicatedList = arrayListOf() + var period: GalleryPeriod = GalleryPeriod.MONTH + private set + private var getGalleryJob: Job? = null private var currentCursor: String? = null @@ -69,35 +72,48 @@ class GalleryViewModel : ViewModel() { val displayList = arrayListOf() for (file in newFiles) { - val month = file.getMonth() - if (lastSectionTitle != month) { - displayList.add(month) - lastSectionTitle = month + val sectionTitle = file.getSectionTitle() + if (lastSectionTitle != sectionTitle) { + displayList.add(sectionTitle) + lastSectionTitle = sectionTitle } displayList.add(file) } return displayList } + + fun updatePeriod(newPeriod: GalleryPeriod): List? { + if (newPeriod == period) return null + + period = newPeriod + + val files = ArrayList(galleryList) + galleryList.clear() + lastSectionTitle = "" + + return formatList(files) + } - fun prependDuplicatedImages(currentTopTitle: Any?): List { + fun prependDuplicatedImages(currentTopTitle: Any?): Pair, Int> { val prefix = arrayListOf() var newestSectionTitle = currentTopTitle galleryList.addAll(0, duplicatedList) for (file in duplicatedList) { - val month = file.getMonth() - if (newestSectionTitle != month) { - prefix.add(month) - newestSectionTitle = month + val sectionTitle = file.getSectionTitle() + if (newestSectionTitle != sectionTitle) { + prefix.add(sectionTitle) + newestSectionTitle = sectionTitle } prefix.add(file) } duplicatedList.clear() - return prefix + val offset = if (prefix.firstOrNull() is String) 0 else 1 + return prefix to offset } fun removeFileFromGallery(fileId: Int) { @@ -109,8 +125,8 @@ class GalleryViewModel : ViewModel() { lastSectionTitle = "" } - fun File.getMonth(): String { - return getLastModifiedAt().format("MMMM yyyy").capitalizeFirstChar() + private fun File.getSectionTitle(): String { + return getLastModifiedAt().format(period.pattern).capitalizeFirstChar() } private fun loadLastGallery( diff --git a/app/src/main/java/com/infomaniak/drive/ui/menu/MenuGalleryFragment.kt b/app/src/main/java/com/infomaniak/drive/ui/menu/MenuGalleryFragment.kt index 8058f2c328..1d384edee5 100644 --- a/app/src/main/java/com/infomaniak/drive/ui/menu/MenuGalleryFragment.kt +++ b/app/src/main/java/com/infomaniak/drive/ui/menu/MenuGalleryFragment.kt @@ -1,6 +1,6 @@ /* * Infomaniak kDrive - Android - * Copyright (C) 2022-2024 Infomaniak Network SA + * Copyright (C) 2022-2026 Infomaniak Network SA * * 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 @@ -25,7 +25,9 @@ import androidx.core.view.isGone import androidx.core.view.marginBottom import androidx.core.view.marginTop import androidx.fragment.app.Fragment +import com.infomaniak.core.legacy.utils.getBackNavigationResult import com.infomaniak.core.legacy.utils.safeBinding +import com.infomaniak.core.legacy.utils.safeNavigate import com.infomaniak.core.legacy.utils.toPx import com.infomaniak.drive.R import com.infomaniak.drive.databinding.FragmentMenuGalleryBinding @@ -65,6 +67,23 @@ class MenuGalleryFragment : Fragment() { swipeRefreshLayout.setOnRefreshListener(galleryFragment::onRefreshGallery) + toolbar.setOnMenuItemClickListener { menuItem -> + if (menuItem.itemId == R.id.selectGalleryPeriod) { + safeNavigate( + MenuGalleryFragmentDirections.actionMenuGalleryFragmentToGalleryPeriodBottomSheetDialog( + galleryFragment.currentPeriod, + ) + ) + true + } else { + false + } + } + + getBackNavigationResult(GalleryPeriodBottomSheetDialog.GALLERY_PERIOD_KEY) { + galleryFragment.onPeriodSelected(it) + } + multiSelectLayout.apply { selectAllButton.isGone = true setMultiSelectClickListeners(galleryFragment) diff --git a/app/src/main/res/layout/fragment_menu_gallery.xml b/app/src/main/res/layout/fragment_menu_gallery.xml index 06933d0bef..6f5bba8b8d 100644 --- a/app/src/main/res/layout/fragment_menu_gallery.xml +++ b/app/src/main/res/layout/fragment_menu_gallery.xml @@ -40,6 +40,7 @@ android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" app:layout_collapseMode="pin" + app:menu="@menu/gallery_menu" app:navigationIcon="@null" /> diff --git a/app/src/main/res/menu/gallery_menu.xml b/app/src/main/res/menu/gallery_menu.xml new file mode 100644 index 0000000000..54e09c2542 --- /dev/null +++ b/app/src/main/res/menu/gallery_menu.xml @@ -0,0 +1,27 @@ + + + + + + diff --git a/app/src/main/res/navigation/main_navigation.xml b/app/src/main/res/navigation/main_navigation.xml index 875093e78a..cce3de8f07 100644 --- a/app/src/main/res/navigation/main_navigation.xml +++ b/app/src/main/res/navigation/main_navigation.xml @@ -1175,7 +1175,22 @@ android:id="@+id/menuGalleryFragment" android:name="com.infomaniak.drive.ui.menu.MenuGalleryFragment" android:label="MenuGalleryFragment" - tools:layout="@layout/fragment_menu_gallery" /> + tools:layout="@layout/fragment_menu_gallery"> + + + + + +