Skip to content
Merged
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
Expand Up @@ -10,14 +10,15 @@ package com.owncloud.android.ui.adapter

import android.view.Gravity
import android.view.View
import android.view.ViewOutlineProvider
import android.widget.FrameLayout
import android.widget.ImageView
import androidx.core.content.ContextCompat
import androidx.core.view.get
import com.afollestad.sectionedrecyclerview.SectionedViewHolder
import com.elyeproj.loaderviewlibrary.LoaderImageView
import com.nextcloud.android.common.ui.theme.utils.ColorRole
import com.nextcloud.utils.extensions.makeRounded
import com.nextcloud.utils.extensions.createRoundedOutline
import com.nextcloud.utils.extensions.setVisibleIf
import com.owncloud.android.R
import com.owncloud.android.databinding.GalleryRowBinding
Expand All @@ -29,10 +30,12 @@ import com.owncloud.android.utils.theme.ViewThemeUtils
private const val CHECKED_SCALE = 0.8f
private const val PLACEHOLDER_ICON_INSET_RATIO = 0.32f
private const val UNCHECKED_SCALE = 1.0f
private const val SELECTION_ANIMATION_DURATION_MS = 150L

private const val SHIMMER_INDEX = 0
private const val THUMBNAIL_INDEX = 1
private const val CHECKBOX_INDEX = 2
private const val SELECTION_BACKGROUND_INDEX = 0
private const val SHIMMER_INDEX = 1
private const val THUMBNAIL_INDEX = 2
private const val CHECKBOX_INDEX = 3

class GalleryRowHolder(
val binding: GalleryRowBinding,
Expand All @@ -44,18 +47,26 @@ class GalleryRowHolder(

private val zero by lazy { context.resources.getInteger(R.integer.zero) }
private val smallMargin by lazy { context.resources.getInteger(R.integer.small_margin) }
private val iconRadius by lazy { context.resources.getDimension(R.dimen.activity_icon_radius) }
private val standardMargin by lazy { context.resources.getDimension(R.dimen.standard_margin) }
private val checkBoxMargin by lazy { context.resources.getDimension(R.dimen.standard_quarter_padding) }
private val checkBoxMargin by lazy { context.resources.getDimensionPixelSize(R.dimen.standard_half_margin) }

private val selectedOutline by lazy {
val resources = context.resources
val radiusDp = resources.getDimension(R.dimen.activity_icon_radius) / resources.displayMetrics.density
createRoundedOutline(context, radiusDp)
}

private val checkedDrawable by lazy {
ContextCompat.getDrawable(context, R.drawable.ic_checkbox_marked)?.also {
viewThemeUtils.platform.tintDrawable(context, it, ColorRole.PRIMARY)
}
}

private val checkedBackground by lazy {
ContextCompat.getDrawable(context, R.drawable.gallery_selection_checked_background)
}

private val uncheckedDrawable by lazy {
ContextCompat.getDrawable(context, R.drawable.ic_checkbox_blank_outline)
ContextCompat.getDrawable(context, R.drawable.gallery_selection_unchecked)
}

fun bind(row: GalleryRow) {
Expand Down Expand Up @@ -83,6 +94,12 @@ class GalleryRowHolder(
}

private fun createCell(): FrameLayout {
val selectionBackground = View(context).apply {
visibility = View.GONE
layoutParams = FrameLayout.LayoutParams(0, 0)
viewThemeUtils.platform.colorViewBackground(this, ColorRole.SURFACE_CONTAINER_HIGHEST)
}

val shimmer = LoaderImageView(context).apply {
setImageResource(R.drawable.background)
resetLoader()
Expand All @@ -101,12 +118,13 @@ class GalleryRowHolder(
FrameLayout.LayoutParams.WRAP_CONTENT
).apply {
gravity = Gravity.TOP or Gravity.START
marginStart = checkBoxMargin.toInt()
topMargin = checkBoxMargin.toInt()
marginStart = checkBoxMargin
topMargin = checkBoxMargin
}
}

return FrameLayout(context).apply {
addView(selectionBackground)
addView(shimmer)
addView(thumbnail)
addView(checkbox)
Expand All @@ -115,15 +133,19 @@ class GalleryRowHolder(

private fun bindCell(index: Int, file: OCFile, size: GalleryCellSize, isLast: Boolean) {
val frameLayout = binding.rowLayout[index] as FrameLayout
val selectionBackground = frameLayout[SELECTION_BACKGROUND_INDEX]
val shimmer = frameLayout[SHIMMER_INDEX] as LoaderImageView
val thumbnail = frameLayout[THUMBNAIL_INDEX] as ImageView
val checkbox = frameLayout[CHECKBOX_INDEX] as ImageView

val endMargin = if (isLast) zero else smallMargin
applyCellSize(shimmer, size, endMargin = zero, bottomMargin = zero)
applyCellSize(thumbnail, size, endMargin = if (isLast) zero else smallMargin, bottomMargin = smallMargin)
applyCellSize(thumbnail, size, endMargin = endMargin, bottomMargin = smallMargin)
applyCellSize(selectionBackground, size, endMargin = endMargin, bottomMargin = smallMargin)

val isChecked = ocFileListDelegate.isCheckedFile(file)
applySelection(thumbnail, isChecked)
selectionBackground.setVisibleIf(isChecked)
applySelection(thumbnail, file, isChecked)
applyCheckBox(checkbox, isChecked)

ocFileListDelegate.bindGalleryRow(shimmer, thumbnail, file, this, placeholderInset(size))
Expand All @@ -150,32 +172,39 @@ class GalleryRowHolder(
view.layoutParams = params
}

private fun applySelection(imageView: ImageView, isChecked: Boolean) {
private fun applySelection(thumbnail: ImageView, file: OCFile, isChecked: Boolean) {
thumbnail.outlineProvider = if (isChecked) selectedOutline else ViewOutlineProvider.BACKGROUND
thumbnail.clipToOutline = isChecked

val scale = if (isChecked) CHECKED_SCALE else UNCHECKED_SCALE
if (imageView.scaleX == scale) {
thumbnail.animate().cancel()

val isSameFileRebound = thumbnail.tag == file.fileId
if (!isSameFileRebound) {
thumbnail.scaleX = scale
thumbnail.scaleY = scale
return
}

imageView.scaleX = scale
imageView.scaleY = scale
imageView.makeRounded(context, if (isChecked) iconRadius else 0f)
thumbnail.animate()
.scaleX(scale)
.scaleY(scale)
.setDuration(SELECTION_ANIMATION_DURATION_MS)
.start()
}

private fun applyCheckBox(imageView: ImageView, isChecked: Boolean) {
if (ocFileListDelegate.isMultiSelect) {
val checkboxDrawable = if (isChecked) checkedDrawable else uncheckedDrawable

checkboxDrawable?.apply {
val margin = standardMargin.toInt()
setBounds(margin, margin, margin, margin)
}

if (imageView.drawable !== checkboxDrawable) {
imageView.setImageDrawable(checkboxDrawable)
}
val isMultiSelect = ocFileListDelegate.isMultiSelect
imageView.setVisibleIf(isMultiSelect)
if (!isMultiSelect) {
return
}

imageView.setVisibleIf(ocFileListDelegate.isMultiSelect)
val checkboxDrawable = if (isChecked) checkedDrawable else uncheckedDrawable
if (imageView.drawable !== checkboxDrawable) {
imageView.setImageDrawable(checkboxDrawable)
imageView.background = if (isChecked) checkedBackground else null
}
}

private fun thumbnailAt(index: Int): ImageView =
Expand Down
11 changes: 11 additions & 0 deletions app/src/main/res/drawable/gallery_selection_checked_background.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Nextcloud - Android Client
~
~ SPDX-FileCopyrightText: 2026 Alper Ozturk <alper.ozturk@nextcloud.com>
~ SPDX-License-Identifier: AGPL-3.0-or-later
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="@color/white" />
</shape>
17 changes: 17 additions & 0 deletions app/src/main/res/drawable/gallery_selection_unchecked.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Nextcloud - Android Client
~
~ SPDX-FileCopyrightText: 2026 Alper Ozturk <alper.ozturk@nextcloud.com>
~ SPDX-License-Identifier: AGPL-3.0-or-later
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<size
android:width="@dimen/gallery_selection_indicator_size"
android:height="@dimen/gallery_selection_indicator_size" />
<solid android:color="@color/gallery_selection_scrim" />
<stroke
android:width="@dimen/standard_eighth_margin"
android:color="@color/white" />
</shape>
1 change: 1 addition & 0 deletions app/src/main/res/values/colors.xml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
<color name="fg_inverse">#FFFFFF</color>
<color name="filelist_icon_background">#DDDDDD</color>
<color name="transparent">#00000000</color>
<color name="gallery_selection_scrim">#33000000</color>
<color name="secondary_text_color">#666666</color>
<color name="highlight_textColor_Warning">#e53935</color>

Expand Down
1 change: 1 addition & 0 deletions app/src/main/res/values/dims.xml
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@
<dimen name="activity_icon_width">32dp</dimen>
<dimen name="activity_icon_height">32dp</dimen>
<dimen name="activity_icon_radius">16dp</dimen>
<dimen name="gallery_selection_indicator_size">24dp</dimen>
<dimen name="activity_icon_layout_right_end_margin">24dp</dimen>
<dimen name="activity_list_item_grid_layout_left_start_margin">-3dp</dimen>
<dimen name="activity_list_item_title_header_text_size">16sp</dimen>
Expand Down
Loading