Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.owncloud.android.ui.fragment

import android.content.Context
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import kotlin.math.max
import kotlin.math.roundToInt

/**
* A [GridLayoutManager] that works out its own column count from the width it is actually given.
*
* The width is read during layout rather than from the display metrics, because the metrics are
* not a reliable stand in: they describe the display rather than the space this list occupies,
* they are not yet meaningful while the view is being created, and they can still describe the
* previous orientation while a rotation is being delivered.
*
* @param columnWidthProvider target width of a single column in pixels, read on every layout so
* that a changed preference is picked up without recreating the layout manager.
*/
class AutofitGridLayoutManager(context: Context, private val columnWidthProvider: () -> Int) :
GridLayoutManager(context, 1) {

private var lastWidth = 0
private var lastColumnWidth = 0

override fun onLayoutChildren(recycler: RecyclerView.Recycler?, state: RecyclerView.State?) {
updateSpanCount()
super.onLayoutChildren(recycler, state)
}

private fun updateSpanCount() {
val columnWidth = columnWidthProvider()
if (width <= 0 || columnWidth <= 0) {
return
}

if (width == lastWidth && columnWidth == lastColumnWidth) {
return
}

lastWidth = width
lastColumnWidth = columnWidth
spanCount = max(MIN_COLUMN_COUNT, (width.toFloat() / columnWidth).roundToInt())
}

companion object {
private const val MIN_COLUMN_COUNT = 2
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,6 @@ open class ExtendedListFragment :
SearchView.OnQueryTextListener,
SearchView.OnCloseListener,
Injectable {
private var maxColumnSize = 5

@Inject
lateinit var preferences: AppPreferences

Expand Down Expand Up @@ -146,7 +144,7 @@ open class ExtendedListFragment :

open fun switchToGridView() {
if (!isGridEnabled) {
recyclerView?.layoutManager = GridLayoutManager(context, columnsCount)
recyclerView?.layoutManager = AutofitGridLayoutManager(requireContext()) { targetColumnWidth }
}
}

Expand Down Expand Up @@ -375,9 +373,8 @@ open class ExtendedListFragment :
mScale = gridLayoutManager.spanCount.toFloat()
}
mScale *= 2f - scaleFactor
mScale = max(MIN_COLUMN_SIZE, min(mScale, maxColumnSize.toFloat()))
val scaleInt = mScale.roundToInt()
gridLayoutManager.setSpanCount(scaleInt)
mScale = max(MIN_COLUMN_SIZE, min(mScale, MAX_COLUMN_SCALE))
gridLayoutManager.requestLayout()
mRecyclerView?.adapter?.notifyDataSetChanged()
}
}
Expand Down Expand Up @@ -437,14 +434,24 @@ open class ExtendedListFragment :
preferences.setGridColumns(mScale)
}

open val columnsCount: Int
/**
* Target width of one grid column in pixels.
*
* [mScale] is a zoom level: asking for more columns than the default means asking for
* smaller cells. The column count itself is worked out by [AutofitGridLayoutManager] from
* the width the list is actually given.
*/
internal val targetColumnWidth: Int
get() {
if (mScale == -1f) {
return AppPreferencesImpl.DEFAULT_GRID_COLUMN.roundToInt()
}
return mScale.roundToInt()
val zoom = (if (mScale > 0f) mScale else AppPreferencesImpl.DEFAULT_GRID_COLUMN) /
AppPreferencesImpl.DEFAULT_GRID_COLUMN
return (resources.getDimension(R.dimen.grid_item_default_width) / zoom).roundToInt()
}

open val columnsCount: Int
get() = (recyclerView?.layoutManager as? GridLayoutManager)?.spanCount
?: AppPreferencesImpl.DEFAULT_GRID_COLUMN.roundToInt()

/*
* Restore index and position
*/
Expand Down Expand Up @@ -778,15 +785,7 @@ open class ExtendedListFragment :
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)

if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
maxColumnSize = 10
} else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
maxColumnSize = 5
}

if (isGridEnabled && columnsCount > maxColumnSize) {
(recyclerView?.layoutManager as GridLayoutManager).spanCount = maxColumnSize
}
// The column count follows the width the list is given, so nothing to do here.
}

protected fun setLayoutSwitchButton() {
Expand Down Expand Up @@ -821,5 +820,10 @@ open class ExtendedListFragment :
private const val KEY_EMPTY_LIST_MESSAGE = "EMPTY_LIST_MESSAGE"
private const val KEY_IS_GRID_VISIBLE = "IS_GRID_VISIBLE"
private const val MIN_COLUMN_SIZE: Float = 2.0f

/**
* Highest number of columns the pinch gesture can select on a reference-width screen.
*/
private const val MAX_COLUMN_SCALE: Float = 5.0f
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ class FileListLayoutManager(private val fragment: OCFileListFragment, private va

val layoutManager: RecyclerView.LayoutManager?
if (grid) {
layoutManager = GridLayoutManager(context, fragment.columnsCount)
layoutManager = AutofitGridLayoutManager(context) { fragment.targetColumnWidth }
layoutManager.spanSizeLookup = object : SpanSizeLookup() {
override fun getSpanSize(position: Int): Int = if (position == fragment.adapter.itemCount - 1 ||
(position == 0 && fragment.adapter.shouldShowHeader())
Expand Down
3 changes: 3 additions & 0 deletions app/src/main/res/values/dims.xml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@
<dimen name="grid_recommended_item_reason_text_size">12sp</dimen>
<dimen name="grid_bottom_view_height">20dp</dimen>
<dimen name="grid_layout_item_size">10dp</dimen>
<!-- Width a grid cell aims for at the default zoom level; the number of columns
is derived from how many of these fit the window. -->
<dimen name="grid_item_default_width">140dp</dimen>
<dimen name="grid_layout_margin_end">2dp</dimen>
<dimen name="grid_sync_item_layout_next_text_size">22sp</dimen>
<dimen name="grid_container_margin">2dp</dimen>
Expand Down