mirror of
https://github.com/commons-app/apps-android-commons.git
synced 2025-10-28 21:33:53 +01:00
Bump target sdk to API 35 and make the app UI compatible with edge to edge (#6393)
* chore: upgrade target SDK and refactor function signatures to resolve build issues * chore: bump android gradle plugin version * chore(ui): add extension functions for applying edge to edge insets * fix: apply system bar top and bottom insets for edge to edge * fix: force edge to edge for backward compatibility and consistent UI * fix: apply top bar insets as padding and make the status bar color white Since the toolbars have primary color as bg, we should make the status bar white * chore: bump robolectric version for API 35 compatibility * fix: preserve existing margins when adding new insets * feat(customselector): improve RecyclerView edge-to-edge inset handling It allows the last item to sits above the navigation bar while preserving edge-to-edge appearance. * feat(notification): improve RecyclerView edge-to-edge insets handling Also, refactor LocationPicker and DescriptionEdit activities to use extension functions and reduce duplication * fix(quiz): enable and handle edge-to-edge insets and status icon colors * fix: bottom insets not dispatched on all API versions consistently Upgraded core-ktx version installCompatInsetsDispatch wasn't available on current version * fix: return fallback value when versionName is null Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * fix: resolve compilation errors * docs: add KDoc for edge-to-edge insets utility functions * fix(SearchActivity): apply insets for system bars * fix(util): add utility function to handle keyboard insets with animation * fix(upload): handle keyboard insets for upload media detail card view * fix(login): hadle IME insets and make edge-to-edge backward compatible --------- Co-authored-by: Ritika Pahwa <83745993+RitikaPahwa4444@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
parent
b8a558303b
commit
718c466505
35 changed files with 348 additions and 19 deletions
|
|
@ -12,9 +12,9 @@ object ConfigUtils {
|
|||
val isBetaFlavour: Boolean = BuildConfig.FLAVOR == "beta"
|
||||
|
||||
@JvmStatic
|
||||
private fun Context.getVersionName(): String =
|
||||
private fun Context.getVersionName(): String? =
|
||||
try {
|
||||
packageManager.getPackageInfo(packageName, 0).versionName
|
||||
packageManager.getPackageInfo(packageName, 0).versionName ?: BuildConfig.VERSION_NAME
|
||||
} catch (e: PackageManager.NameNotFoundException) {
|
||||
BuildConfig.VERSION_NAME
|
||||
}
|
||||
|
|
|
|||
229
app/src/main/java/fr/free/nrw/commons/utils/EdgeToEdgeUtils.kt
Normal file
229
app/src/main/java/fr/free/nrw/commons/utils/EdgeToEdgeUtils.kt
Normal file
|
|
@ -0,0 +1,229 @@
|
|||
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
|
||||
import androidx.core.view.marginRight
|
||||
import androidx.core.view.marginTop
|
||||
import androidx.core.view.updateLayoutParams
|
||||
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.
|
||||
*
|
||||
* 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 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(
|
||||
typeMask: Int = WindowInsetsCompat.Type.systemBars(),
|
||||
shouldConsumeInsets: Boolean = true,
|
||||
block: MarginLayoutParams.(InsetsAccumulator) -> Unit
|
||||
) {
|
||||
ViewCompat.setOnApplyWindowInsetsListener(this) { view, windowInsets ->
|
||||
val insets = windowInsets.getInsets(typeMask)
|
||||
|
||||
val initialTop = if (view.getTag(R.id.initial_margin_top) != null) {
|
||||
view.getTag(R.id.initial_margin_top) as Int
|
||||
} else {
|
||||
view.setTag(R.id.initial_margin_top, view.marginTop)
|
||||
view.marginTop
|
||||
}
|
||||
|
||||
val initialBottom = if (view.getTag(R.id.initial_margin_bottom) != null) {
|
||||
view.getTag(R.id.initial_margin_bottom) as Int
|
||||
} else {
|
||||
view.setTag(R.id.initial_margin_bottom, view.marginBottom)
|
||||
view.marginBottom
|
||||
}
|
||||
|
||||
val initialLeft = if (view.getTag(R.id.initial_margin_left) != null) {
|
||||
view.getTag(R.id.initial_margin_left) as Int
|
||||
} else {
|
||||
view.setTag(R.id.initial_margin_left, view.marginLeft)
|
||||
view.marginLeft
|
||||
}
|
||||
|
||||
val initialRight = if (view.getTag(R.id.initial_margin_right) != null) {
|
||||
view.getTag(R.id.initial_margin_right) as Int
|
||||
} else {
|
||||
view.setTag(R.id.initial_margin_right, view.marginRight)
|
||||
view.marginRight
|
||||
}
|
||||
|
||||
val accumulator = InsetsAccumulator(
|
||||
initialTop,
|
||||
insets.top,
|
||||
initialBottom,
|
||||
insets.bottom,
|
||||
initialLeft,
|
||||
insets.left,
|
||||
initialRight,
|
||||
insets.right
|
||||
)
|
||||
|
||||
view.updateLayoutParams<MarginLayoutParams> {
|
||||
apply { 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].
|
||||
*/
|
||||
fun View.applyEdgeToEdgeTopPaddingInsets(
|
||||
typeMask: Int = WindowInsetsCompat.Type.systemBars(),
|
||||
) {
|
||||
ViewCompat.setOnApplyWindowInsetsListener(this) { view, windowInsets ->
|
||||
val insets = windowInsets.getInsets(typeMask)
|
||||
|
||||
view.updatePadding(
|
||||
left = insets.left,
|
||||
right = insets.right,
|
||||
top = insets.top
|
||||
)
|
||||
|
||||
WindowInsetsCompat.CONSUMED
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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].
|
||||
*/
|
||||
fun View.applyEdgeToEdgeBottomPaddingInsets(
|
||||
typeMask: Int = WindowInsetsCompat.Type.systemBars(),
|
||||
) {
|
||||
ViewCompat.setOnApplyWindowInsetsListener(this) { view, windowInsets ->
|
||||
val insets = windowInsets.getInsets(typeMask)
|
||||
|
||||
view.updatePadding(
|
||||
left = insets.left,
|
||||
right = insets.right,
|
||||
bottom = insets.bottom
|
||||
)
|
||||
|
||||
WindowInsetsCompat.CONSUMED
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies system bar insets to all margins (top, bottom, left, right) of the view.
|
||||
*
|
||||
* @param view The target view.
|
||||
* @param shouldConsumeInsets If `true`, the insets are consumed and not propagated to child views.
|
||||
*/
|
||||
fun applyEdgeToEdgeAllInsets(
|
||||
view: View,
|
||||
shouldConsumeInsets: Boolean = true
|
||||
) = view.applyEdgeToEdgeInsets(shouldConsumeInsets = shouldConsumeInsets) { insets ->
|
||||
leftMargin = insets.left
|
||||
rightMargin = insets.right
|
||||
topMargin = insets.top
|
||||
bottomMargin = insets.bottom
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies system bar insets to the top and horizontal margins of the view.
|
||||
*
|
||||
* @param view The target view.
|
||||
*/
|
||||
fun applyEdgeToEdgeTopInsets(view: View) = view.applyEdgeToEdgeInsets { insets ->
|
||||
leftMargin = insets.left
|
||||
rightMargin = insets.right
|
||||
topMargin = insets.top
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies system bar insets to the bottom and horizontal margins of the view.
|
||||
*
|
||||
* @param view The target view.
|
||||
*/
|
||||
fun applyEdgeToEdgeBottomInsets(view: View) = view.applyEdgeToEdgeInsets { insets ->
|
||||
leftMargin = insets.left
|
||||
rightMargin = insets.right
|
||||
bottomMargin = insets.bottom
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjusts a [View]'s bottom margin dynamically to account for the on-screen keyboard (IME),
|
||||
* ensuring the view remains visible above the keyboard during transitions.
|
||||
*
|
||||
* Preserves the initial margin, adjusts during IME visibility changes,
|
||||
* and accounts for navigation bar insets to avoid double offsets.
|
||||
*/
|
||||
fun View.handleKeyboardInsets() {
|
||||
var existingBottomMargin = 0
|
||||
|
||||
ViewCompat.setOnApplyWindowInsetsListener(this) { view, windowInsets ->
|
||||
existingBottomMargin = if (view.getTag(R.id.initial_margin_bottom) != null) {
|
||||
view.getTag(R.id.initial_margin_bottom) as Int
|
||||
} else {
|
||||
view.setTag(R.id.initial_margin_bottom, view.marginBottom)
|
||||
view.marginBottom
|
||||
}
|
||||
|
||||
WindowInsetsCompat.CONSUMED
|
||||
}
|
||||
|
||||
// Animate during IME transition
|
||||
ViewCompat.setWindowInsetsAnimationCallback(
|
||||
this,
|
||||
object : WindowInsetsAnimationCompat.Callback(
|
||||
DISPATCH_MODE_CONTINUE_ON_SUBTREE
|
||||
) {
|
||||
override fun onProgress(
|
||||
insets: WindowInsetsCompat,
|
||||
runningAnimations: MutableList<WindowInsetsAnimationCompat>
|
||||
): WindowInsetsCompat {
|
||||
val lp = layoutParams as MarginLayoutParams
|
||||
val navBarInsets = insets.getInsets(WindowInsetsCompat.Type.navigationBars())
|
||||
val imeInsets = insets.getInsets(WindowInsetsCompat.Type.ime())
|
||||
val imeVisible = insets.isVisible(WindowInsetsCompat.Type.ime())
|
||||
|
||||
// Avoid extra space due to system nav bar when the keyboard is shown
|
||||
val imeBottomMargin = imeInsets.bottom - navBarInsets.bottom
|
||||
|
||||
lp.bottomMargin = if(imeVisible && imeBottomMargin >= existingBottomMargin)
|
||||
imeBottomMargin + existingBottomMargin
|
||||
else existingBottomMargin
|
||||
|
||||
layoutParams = lp
|
||||
return WindowInsetsCompat.CONSUMED
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds both initial margin values and system bar insets, providing summed values
|
||||
* for each side (top, bottom, left, right) to apply in layout updates.
|
||||
*/
|
||||
data class InsetsAccumulator(
|
||||
private val initialTop: Int,
|
||||
private val insetTop: Int,
|
||||
private val initialBottom: Int,
|
||||
private val insetBottom: Int,
|
||||
private val initialLeft: Int,
|
||||
private val insetLeft: Int,
|
||||
private val initialRight: Int,
|
||||
private val insetRight: Int
|
||||
) {
|
||||
val top = initialTop + insetTop
|
||||
val bottom = initialBottom + insetBottom
|
||||
val left = initialLeft + insetLeft
|
||||
val right = initialRight + insetRight
|
||||
}
|
||||
|
|
@ -24,6 +24,7 @@ import io.reactivex.android.schedulers.AndroidSchedulers
|
|||
import io.reactivex.disposables.CompositeDisposable
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import timber.log.Timber
|
||||
import androidx.core.graphics.createBitmap
|
||||
|
||||
/**
|
||||
* Created by blueSir9 on 3/10/17.
|
||||
|
|
@ -307,16 +308,19 @@ object ImageUtils {
|
|||
* * @return
|
||||
*/
|
||||
@JvmStatic
|
||||
fun addRedBorder(bitmap: Bitmap, borderSize: Int, context: Context): Bitmap {
|
||||
val bmpWithBorder = Bitmap.createBitmap(
|
||||
bitmap.width + borderSize * 2,
|
||||
bitmap.height + borderSize * 2,
|
||||
bitmap.config
|
||||
)
|
||||
val canvas = Canvas(bmpWithBorder)
|
||||
canvas.drawColor(ContextCompat.getColor(context, R.color.deleteRed))
|
||||
canvas.drawBitmap(bitmap, borderSize.toFloat(), borderSize.toFloat(), null)
|
||||
return bmpWithBorder
|
||||
fun addRedBorder(bitmap: Bitmap, borderSize: Int, context: Context): Bitmap? {
|
||||
return bitmap.config?.let { config ->
|
||||
val bmpWithBorder =
|
||||
createBitmap(
|
||||
width = bitmap.width + borderSize * 2,
|
||||
height = bitmap.height + borderSize * 2,
|
||||
config = config
|
||||
)
|
||||
val canvas = Canvas(bmpWithBorder)
|
||||
canvas.drawColor(ContextCompat.getColor(context, R.color.deleteRed))
|
||||
canvas.drawBitmap(bitmap, borderSize.toFloat(), borderSize.toFloat(), null)
|
||||
return bmpWithBorder
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue