Skip to content
7 changes: 7 additions & 0 deletions app/src/main/java/fr/free/nrw/commons/edit/EditActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,12 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.core.graphics.rotationMatrix
import androidx.core.graphics.scaleMatrix
import androidx.core.net.toUri
import androidx.core.view.WindowInsetsCompat
import androidx.exifinterface.media.ExifInterface
import androidx.lifecycle.ViewModelProvider
import fr.free.nrw.commons.databinding.ActivityEditBinding
import fr.free.nrw.commons.utils.applyEdgeToEdgeBottomInsets
import fr.free.nrw.commons.utils.applyEdgeToEdgeTopPaddingInsets
import timber.log.Timber
import java.io.File
import kotlin.math.ceil
Expand Down Expand Up @@ -45,6 +48,9 @@ class EditActivity : AppCompatActivity() {
vm = ViewModelProvider(this)[EditViewModel::class.java]
val sourceExif = imageUri.toUri().path?.let { ExifInterface(it) }

applyEdgeToEdgeBottomInsets(binding.root, false)
binding.topBar.applyEdgeToEdgeTopPaddingInsets(WindowInsetsCompat.Type.statusBars())

val exifTags =
arrayOf(
ExifInterface.TAG_F_NUMBER,
Expand Down Expand Up @@ -125,6 +131,7 @@ class EditActivity : AppCompatActivity() {
binding.btnSave.setOnClickListener {
getRotatedImage()
}
binding.btnBack.setOnClickListener { onBackPressedDispatcher.onBackPressed() }
}

var imageRotation = 0
Expand Down
27 changes: 26 additions & 1 deletion app/src/main/java/fr/free/nrw/commons/media/ZoomableActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ import android.view.View
import android.view.Window
import android.widget.Button
import android.widget.Toast
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat
import androidx.lifecycle.ViewModelProvider
import com.facebook.drawee.backends.pipeline.Fresco
import com.facebook.drawee.controller.BaseControllerListener
Expand Down Expand Up @@ -39,6 +42,7 @@ import fr.free.nrw.commons.theme.BaseActivity
import fr.free.nrw.commons.upload.FileProcessor
import fr.free.nrw.commons.upload.FileUtilsWrapper
import fr.free.nrw.commons.utils.CustomSelectorUtils
import fr.free.nrw.commons.utils.applyEdgeToEdgeTopPaddingInsets
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
Expand Down Expand Up @@ -131,12 +135,17 @@ class ZoomableActivity : BaseActivity() {
private var defaultDispatcher: CoroutineDispatcher = Dispatchers.Default
private var ioDispatcher: CoroutineDispatcher = Dispatchers.IO
private val scope: CoroutineScope = MainScope()
private var isSystemBarsVisible = true

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityZoomableBinding.inflate(layoutInflater)
setContentView(binding.root)

binding.topBar.applyEdgeToEdgeTopPaddingInsets(WindowInsetsCompat.Type.statusBars())
binding.btnBack.setOnClickListener {
onBackPressed()
}
prefs =
applicationContext.getSharedPreferences(
ImageHelper.CUSTOM_SELECTOR_PREFERENCE_KEY,
Expand Down Expand Up @@ -651,7 +660,9 @@ class ZoomableActivity : BaseActivity() {
setHierarchy(hierarchy)
setAllowTouchInterceptionWhileZoomed(true)
setIsLongpressEnabled(false)
setTapListener(DoubleTapGestureListener(this))
setTapListener(DoubleTapGestureListener(this) {
toggleSystemBars()
})
}
val controller: DraweeController =
Fresco
Expand All @@ -677,6 +688,20 @@ class ZoomableActivity : BaseActivity() {
}
}

private fun toggleSystemBars() {
val windowInsetsController = WindowCompat.getInsetsController(window, window.decorView)
if (isSystemBarsVisible) {
windowInsetsController.hide(WindowInsetsCompat.Type.systemBars())
windowInsetsController.systemBarsBehavior =
WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
binding.topBar.visibility = View.GONE
} else {
windowInsetsController.show(WindowInsetsCompat.Type.systemBars())
binding.topBar.visibility = View.VISIBLE
}
isSystemBarsVisible = !isSystemBarsVisible
}

/**
* Inserts an image in Not For Upload table
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@ import kotlin.math.hypot
*
* @see ZoomableDraweeView.setTapListener
*/
class DoubleTapGestureListener(private val draweeView: ZoomableDraweeView) :
GestureDetector.SimpleOnGestureListener() {
class DoubleTapGestureListener(
private val draweeView: ZoomableDraweeView,
private val onSingleTap: (() -> Unit)? = null
) : GestureDetector.SimpleOnGestureListener() {

companion object {
private const val DURATION_MS = 300L
Expand All @@ -24,6 +26,15 @@ class DoubleTapGestureListener(private val draweeView: ZoomableDraweeView) :
private var doubleTapScale = 1f
private var doubleTapScroll = false

override fun onSingleTapConfirmed(e: MotionEvent): Boolean {
if(onSingleTap != null) {
onSingleTap.invoke()
return true
} else {
return super.onSingleTapConfirmed(e)
}
}

override fun onDoubleTapEvent(e: MotionEvent): Boolean {
val zc = draweeView.getZoomableController() as AbstractAnimatedZoomableController
val vp = PointF(e.x, e.y)
Expand Down
101 changes: 81 additions & 20 deletions app/src/main/java/fr/free/nrw/commons/utils/EdgeToEdgeUtils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package fr.free.nrw.commons.utils
import android.view.View
import android.view.ViewGroup.MarginLayoutParams
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsAnimationCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.marginBottom
import androidx.core.view.marginLeft
Expand All @@ -14,16 +13,16 @@ import androidx.core.view.updatePadding
import fr.free.nrw.commons.R

/**
* Applies edge-to-edge system bar insets to a [View]’s margins using a custom adjustment block.
* Applies edge-to-edge insets to a [View]’s margins using a custom adjustment modifier [block].
*
* Stores the initial margins to ensure inset calculations are additive, and applies the provided
* [block] with an [InsetsAccumulator] containing initial and system bar inset values.
*
* @param typeMask The type of window insets to apply. Defaults to [WindowInsetsCompat.Type.systemBars].
* @param typeMask The type of window insets to apply. Default to [WindowInsetsCompat.Type.systemBars].
* @param shouldConsumeInsets If `true`, the insets are consumed and not propagated to child views.
* @param block Lambda applied to update [MarginLayoutParams] using the accumulated insets.
*/
fun View.applyEdgeToEdgeInsets(
fun View.applyEdgeToEdgeInsetsAsMargin(
typeMask: Int = WindowInsetsCompat.Type.systemBars(),
shouldConsumeInsets: Boolean = true,
block: MarginLayoutParams.(InsetsAccumulator) -> Unit
Expand Down Expand Up @@ -79,44 +78,103 @@ fun View.applyEdgeToEdgeInsets(
}

/**
* Applies edge-to-edge system bar insets to the top padding of the view.
* Applies edge-to-edge insets to a [View]’s padding using a custom adjustment modifier [block].
*
* Stores the initial paddings to ensure inset calculations are additive, and applies the provided
* [block] with an [InsetsAccumulator] containing initial and system bar inset values.
*
* @param typeMask The type of window insets to apply. Defaults to [WindowInsetsCompat.Type.systemBars].
* @param shouldConsumeInsets If `true`, the insets are consumed and not propagated to child views.
* @param block Lambda applied to update [View]'s padding using the values from [InsetsAccumulator].
*/
fun View.applyEdgeToEdgeTopPaddingInsets(
fun View.applyEdgeToEdgeInsetsAsPadding(
typeMask: Int = WindowInsetsCompat.Type.systemBars(),
shouldConsumeInsets: Boolean = true,
block: View.(InsetsAccumulator) -> Unit
) {
ViewCompat.setOnApplyWindowInsetsListener(this) { view, windowInsets ->
val insets = windowInsets.getInsets(typeMask)

view.updatePadding(
left = insets.left,
right = insets.right,
top = insets.top
val initialTop = if (view.getTag(R.id.initial_padding_top) != null) {
view.getTag(R.id.initial_padding_top) as Int
} else {
view.setTag(R.id.initial_padding_top, view.paddingTop)
view.paddingTop
}

val initialBottom = if (view.getTag(R.id.initial_padding_bottom) != null) {
view.getTag(R.id.initial_padding_bottom) as Int
} else {
view.setTag(R.id.initial_padding_bottom, view.paddingBottom)
view.paddingBottom
}

val initialLeft = if (view.getTag(R.id.initial_padding_left) != null) {
view.getTag(R.id.initial_padding_left) as Int
} else {
view.setTag(R.id.initial_padding_left, view.paddingLeft)
view.paddingLeft
}

val initialRight = if (view.getTag(R.id.initial_padding_right) != null) {
view.getTag(R.id.initial_padding_right) as Int
} else {
view.setTag(R.id.initial_padding_right, view.paddingRight)
view.paddingRight
}

val accumulator = InsetsAccumulator(
initialTop,
insets.top,
initialBottom,
insets.bottom,
initialLeft,
insets.left,
initialRight,
insets.right
)

WindowInsetsCompat.CONSUMED
block(accumulator)

if(shouldConsumeInsets) WindowInsetsCompat.CONSUMED else windowInsets
}
}

/**
* Applies edge-to-edge system bar insets to the top padding of the view.
*
* @param typeMask The type of window insets to apply. Defaults to [WindowInsetsCompat.Type.systemBars].
* @param shouldConsumeInsets If `true`, the insets are consumed and not propagated to child views.
*/
fun View.applyEdgeToEdgeTopPaddingInsets(
typeMask: Int = WindowInsetsCompat.Type.systemBars(),
shouldConsumeInsets: Boolean = true
) {
applyEdgeToEdgeInsetsAsPadding(typeMask, shouldConsumeInsets) { insets ->
updatePadding(
left = insets.left,
top = insets.top,
right = insets.right
)
}
}

/**
* Applies edge-to-edge system bar insets to the bottom padding of the view.
*
* @param typeMask The type of window insets to apply. Defaults to [WindowInsetsCompat.Type.systemBars].
* @param shouldConsumeInsets If `true`, the insets are consumed and not propagated to child views.
*/
fun View.applyEdgeToEdgeBottomPaddingInsets(
typeMask: Int = WindowInsetsCompat.Type.systemBars(),
shouldConsumeInsets: Boolean = true
) {
ViewCompat.setOnApplyWindowInsetsListener(this) { view, windowInsets ->
val insets = windowInsets.getInsets(typeMask)

view.updatePadding(
applyEdgeToEdgeInsetsAsPadding(typeMask, shouldConsumeInsets) { insets ->
updatePadding(
left = insets.left,
right = insets.right,
bottom = insets.bottom
)

WindowInsetsCompat.CONSUMED
}
}

Expand All @@ -129,7 +187,7 @@ fun View.applyEdgeToEdgeBottomPaddingInsets(
fun applyEdgeToEdgeAllInsets(
view: View,
shouldConsumeInsets: Boolean = true
) = view.applyEdgeToEdgeInsets(shouldConsumeInsets = shouldConsumeInsets) { insets ->
) = view.applyEdgeToEdgeInsetsAsMargin(shouldConsumeInsets = shouldConsumeInsets) { insets ->
leftMargin = insets.left
rightMargin = insets.right
topMargin = insets.top
Expand All @@ -141,7 +199,7 @@ fun applyEdgeToEdgeAllInsets(
*
* @param view The target view.
*/
fun applyEdgeToEdgeTopInsets(view: View) = view.applyEdgeToEdgeInsets { insets ->
fun applyEdgeToEdgeTopInsets(view: View) = view.applyEdgeToEdgeInsetsAsMargin { insets ->
leftMargin = insets.left
rightMargin = insets.right
topMargin = insets.top
Expand All @@ -152,7 +210,10 @@ fun applyEdgeToEdgeTopInsets(view: View) = view.applyEdgeToEdgeInsets { insets -
*
* @param view The target view.
*/
fun applyEdgeToEdgeBottomInsets(view: View) = view.applyEdgeToEdgeInsets { insets ->
fun applyEdgeToEdgeBottomInsets(
view: View,
shouldConsumeInsets: Boolean = true
) = view.applyEdgeToEdgeInsetsAsMargin(shouldConsumeInsets = shouldConsumeInsets) { insets ->
leftMargin = insets.left
rightMargin = insets.right
bottomMargin = insets.bottom
Expand Down
3 changes: 3 additions & 0 deletions app/src/main/res/drawable-night/ic_arrow_back.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:autoMirrored="true" android:height="24dp" android:tint="#ffffff" android:viewportHeight="960" android:viewportWidth="960" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M313,520L537,744L480,800L160,480L480,160L537,216L313,440L800,440L800,520L313,520Z"/>
</vector>
3 changes: 3 additions & 0 deletions app/src/main/res/drawable/ic_arrow_back.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:autoMirrored="true" android:height="24dp" android:tint="#000000" android:viewportHeight="960" android:viewportWidth="960" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M313,520L537,744L480,800L160,480L480,160L537,216L313,440L800,440L800,520L313,520Z"/>
</vector>
Loading