mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2025-03-17 21:31:57 +01:00
Merge branch 'main' of https://github.com/vitorpamplona/amethyst
This commit is contained in:
commit
40865ac97d
@ -252,6 +252,7 @@ dependencies {
|
||||
|
||||
// Language picker and Theme chooser
|
||||
implementation libs.androidx.appcompat
|
||||
implementation libs.androidx.window.core.android
|
||||
|
||||
// Local model for language identification
|
||||
playImplementation libs.google.mlkit.language.id
|
||||
|
@ -0,0 +1,182 @@
|
||||
/**
|
||||
* Copyright (c) 2024 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package androidx.compose.material3.adaptive
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.ui.geometry.Rect
|
||||
import androidx.compose.ui.graphics.toComposeRect
|
||||
import androidx.window.layout.FoldingFeature
|
||||
|
||||
/**
|
||||
* Calculates the [Posture] for a given list of [FoldingFeature]s. This methods converts framework
|
||||
* folding info into the Material-opinionated posture info.
|
||||
*/
|
||||
fun calculatePosture(foldingFeatures: List<FoldingFeature>): Posture {
|
||||
var isTableTop = false
|
||||
val hingeList = mutableListOf<HingeInfo>()
|
||||
@Suppress("ListIterator")
|
||||
foldingFeatures.forEach {
|
||||
if (it.orientation == FoldingFeature.Orientation.HORIZONTAL &&
|
||||
it.state == FoldingFeature.State.HALF_OPENED
|
||||
) {
|
||||
isTableTop = true
|
||||
}
|
||||
hingeList.add(
|
||||
HingeInfo(
|
||||
bounds = it.bounds.toComposeRect(),
|
||||
isFlat = it.state == FoldingFeature.State.FLAT,
|
||||
isVertical = it.orientation == FoldingFeature.Orientation.VERTICAL,
|
||||
isSeparating = it.isSeparating,
|
||||
isOccluding = it.occlusionType == FoldingFeature.OcclusionType.FULL,
|
||||
),
|
||||
)
|
||||
}
|
||||
return Posture(isTableTop, hingeList)
|
||||
}
|
||||
|
||||
/**
|
||||
* Posture info that can help make layout adaptation decisions. For example when
|
||||
* [Posture.separatingVerticalHingeBounds] is not empty, the layout may want to avoid putting any
|
||||
* content over those hinge area. We suggest to use [calculatePosture] to retrieve instances of this
|
||||
* class in applications, unless you have a strong need of customization that cannot be fulfilled by
|
||||
* the default implementation.
|
||||
*
|
||||
* Note that the hinge bounds will be represent as [Rect] with window coordinates, instead of layout
|
||||
* coordinate.
|
||||
*
|
||||
* @constructor create an instance of [Posture]
|
||||
* @property isTabletop `true` if the current window is considered as in the table top mode, i.e.
|
||||
* there is one half-opened horizontal hinge in the middle of the current window. When
|
||||
* this is `true` it usually means it's hard for users to interact with the window area
|
||||
* around the hinge and developers may consider separating the layout along the hinge and
|
||||
* show software keyboard or other controls in the bottom half of the window.
|
||||
* @property hingeList a list of all hinges that are relevant to the posture.
|
||||
*/
|
||||
@Immutable
|
||||
class Posture(
|
||||
val isTabletop: Boolean = false,
|
||||
val hingeList: List<HingeInfo> = emptyList(),
|
||||
) {
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other !is Posture) return false
|
||||
if (isTabletop != other.isTabletop) return false
|
||||
if (hingeList != other.hingeList) return false
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = isTabletop.hashCode()
|
||||
result = 31 * result + hingeList.hashCode()
|
||||
return result
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
@Suppress("ListIterator")
|
||||
return "Posture(isTabletop=$isTabletop, " +
|
||||
"hinges=[${hingeList.joinToString(", ")}])"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of vertical hinge bounds that are separating.
|
||||
*/
|
||||
val Posture.separatingVerticalHingeBounds get() = hingeList.getBounds { isVertical && isSeparating }
|
||||
|
||||
/**
|
||||
* Returns the list of vertical hinge bounds that are occluding.
|
||||
*/
|
||||
val Posture.occludingVerticalHingeBounds get() = hingeList.getBounds { isVertical && isOccluding }
|
||||
|
||||
/**
|
||||
* Returns the list of all vertical hinge bounds.
|
||||
*/
|
||||
val Posture.allVerticalHingeBounds get() = hingeList.getBounds { isVertical }
|
||||
|
||||
/**
|
||||
* Returns the list of horizontal hinge bounds that are separating.
|
||||
*/
|
||||
val Posture.separatingHorizontalHingeBounds
|
||||
get() = hingeList.getBounds { !isVertical && isSeparating }
|
||||
|
||||
/**
|
||||
* Returns the list of horizontal hinge bounds that are occluding.
|
||||
*/
|
||||
val Posture.occludingHorizontalHingeBounds
|
||||
get() = hingeList.getBounds { !isVertical && isOccluding }
|
||||
|
||||
/**
|
||||
* Returns the list of all horizontal hinge bounds.
|
||||
*/
|
||||
val Posture.allHorizontalHingeBounds
|
||||
get() = hingeList.getBounds { !isVertical }
|
||||
|
||||
/**
|
||||
* A class that contains the info of a hinge relevant to a [Posture].
|
||||
*
|
||||
* @param bounds the bounds of the hinge in the relevant viewport.
|
||||
* @param isFlat `true` if the hinge is fully open and the relevant window space presented to the
|
||||
* user is flat.
|
||||
* @param isVertical `true` if the hinge is a vertical one, i.e., it separates the viewport into
|
||||
* left and right; `false` if the hinge is horizontal, i.e., it separates the viewport
|
||||
* into top and bottom.
|
||||
* @param isSeparating `true` if the hinge creates two logical display areas.
|
||||
* @param isOccluding `true` if the hinge conceals part of the display.
|
||||
*/
|
||||
@Immutable
|
||||
class HingeInfo(
|
||||
val bounds: Rect,
|
||||
val isFlat: Boolean,
|
||||
val isVertical: Boolean,
|
||||
val isSeparating: Boolean,
|
||||
val isOccluding: Boolean,
|
||||
) {
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other !is HingeInfo) return false
|
||||
if (bounds != other.bounds) return false
|
||||
if (isFlat != other.isFlat) return false
|
||||
if (isVertical != other.isVertical) return false
|
||||
if (isSeparating != other.isSeparating) return false
|
||||
if (isOccluding != other.isOccluding) return false
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = bounds.hashCode()
|
||||
result = 31 * result + isFlat.hashCode()
|
||||
result = 31 * result + isVertical.hashCode()
|
||||
result = 31 * result + isSeparating.hashCode()
|
||||
result = 31 * result + isOccluding.hashCode()
|
||||
return result
|
||||
}
|
||||
|
||||
override fun toString(): String =
|
||||
"HingeInfo(bounds=$bounds, " +
|
||||
"isFlat=$isFlat, " +
|
||||
"isVertical=$isVertical, " +
|
||||
"isSeparating=$isSeparating, " +
|
||||
"isOccluding=$isOccluding)"
|
||||
}
|
||||
|
||||
private inline fun List<HingeInfo>.getBounds(predicate: HingeInfo.() -> Boolean): List<Rect> =
|
||||
@Suppress("ListIterator")
|
||||
mapNotNull { if (it.predicate()) it.bounds else null }
|
@ -0,0 +1,122 @@
|
||||
/**
|
||||
* Copyright (c) 2024 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package androidx.compose.material3.adaptive
|
||||
|
||||
import android.app.Activity
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.unit.IntSize
|
||||
import androidx.compose.ui.unit.toSize
|
||||
import androidx.window.core.layout.WindowSizeClass
|
||||
import androidx.window.layout.FoldingFeature
|
||||
import androidx.window.layout.WindowInfoTracker
|
||||
import androidx.window.layout.WindowMetricsCalculator
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
@Composable
|
||||
fun currentWindowAdaptiveInfo(): WindowAdaptiveInfo {
|
||||
val windowSize =
|
||||
with(LocalDensity.current) {
|
||||
currentWindowSize().toSize().toDpSize()
|
||||
}
|
||||
return WindowAdaptiveInfo(
|
||||
WindowSizeClass.compute(windowSize.width.value, windowSize.height.value),
|
||||
calculatePosture(collectFoldingFeaturesAsState().value),
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns and automatically update the current window size from [WindowMetricsCalculator].
|
||||
*
|
||||
* @return an [IntSize] that represents the current window size.
|
||||
*/
|
||||
@Composable
|
||||
fun currentWindowSize(): IntSize {
|
||||
// Observe view configuration changes and recalculate the size class on each change. We can't
|
||||
// use Activity#onConfigurationChanged as this will sometimes fail to be called on different
|
||||
// API levels, hence why this function needs to be @Composable so we can observe the
|
||||
// ComposeView's configuration changes.
|
||||
LocalConfiguration.current
|
||||
val windowBounds =
|
||||
WindowMetricsCalculator
|
||||
.getOrCreate()
|
||||
.computeCurrentWindowMetrics(LocalContext.current)
|
||||
.bounds
|
||||
return IntSize(windowBounds.width(), windowBounds.height())
|
||||
}
|
||||
|
||||
/**
|
||||
* Collects the current window folding features from [WindowInfoTracker] in to a [State].
|
||||
*
|
||||
* @return a [State] of a [FoldingFeature] list.
|
||||
*/
|
||||
@Composable
|
||||
fun collectFoldingFeaturesAsState(): State<List<FoldingFeature>> {
|
||||
val context = LocalContext.current
|
||||
return remember(context) {
|
||||
if (context is Activity) {
|
||||
// TODO(b/284347941) remove the instance check after the test bug is fixed.
|
||||
WindowInfoTracker
|
||||
.getOrCreate(context)
|
||||
.windowLayoutInfo(context)
|
||||
} else {
|
||||
WindowInfoTracker
|
||||
.getOrCreate(context)
|
||||
.windowLayoutInfo(context)
|
||||
}.map { it.displayFeatures.filterIsInstance<FoldingFeature>() }
|
||||
}.collectAsState(emptyList())
|
||||
}
|
||||
|
||||
/**
|
||||
* This class collects window info that affects adaptation decisions. An adaptive layout is supposed
|
||||
* to use the info from this class to decide how the layout is supposed to be adapted.
|
||||
*
|
||||
* @constructor create an instance of [WindowAdaptiveInfo]
|
||||
* @param windowSizeClass [WindowSizeClass] of the current window.
|
||||
* @param windowPosture [Posture] of the current window.
|
||||
*/
|
||||
@Immutable
|
||||
class WindowAdaptiveInfo(
|
||||
val windowSizeClass: WindowSizeClass,
|
||||
val windowPosture: Posture,
|
||||
) {
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other !is WindowAdaptiveInfo) return false
|
||||
if (windowSizeClass != other.windowSizeClass) return false
|
||||
if (windowPosture != other.windowPosture) return false
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = windowSizeClass.hashCode()
|
||||
result = 31 * result + windowPosture.hashCode()
|
||||
return result
|
||||
}
|
||||
|
||||
override fun toString(): String = "WindowAdaptiveInfo(windowSizeClass=$windowSizeClass, windowPosture=$windowPosture)"
|
||||
}
|
@ -350,7 +350,7 @@ fun VideoViewInner(
|
||||
defaultToStart: Boolean = false,
|
||||
title: String? = null,
|
||||
thumb: VideoThumb? = null,
|
||||
showControls: Boolean = false,
|
||||
showControls: Boolean = true,
|
||||
isFiniteHeight: Boolean,
|
||||
borderModifier: Modifier,
|
||||
waveform: ImmutableList<Int>? = null,
|
||||
@ -375,7 +375,7 @@ fun VideoViewInner(
|
||||
mimeType = mimeType,
|
||||
controller = controller,
|
||||
thumbData = thumb,
|
||||
hideControls = showControls,
|
||||
showControls = showControls,
|
||||
isFiniteHeight = isFiniteHeight,
|
||||
nostrUriCallback = nostrUriCallback,
|
||||
waveform = waveform,
|
||||
@ -723,7 +723,7 @@ private fun RenderVideoPlayer(
|
||||
mimeType: String?,
|
||||
controller: MediaController,
|
||||
thumbData: VideoThumb?,
|
||||
hideControls: Boolean = false,
|
||||
showControls: Boolean = true,
|
||||
isFiniteHeight: Boolean,
|
||||
nostrUriCallback: String?,
|
||||
waveform: ImmutableList<Int>? = null,
|
||||
@ -754,7 +754,7 @@ private fun RenderVideoPlayer(
|
||||
setBackgroundColor(Color.Transparent.toArgb())
|
||||
setShutterBackgroundColor(Color.Transparent.toArgb())
|
||||
controllerAutoShow = false
|
||||
useController = !hideControls
|
||||
useController = showControls
|
||||
thumbData?.thumb?.let { defaultArtwork = it }
|
||||
hideController()
|
||||
resizeMode =
|
||||
@ -763,7 +763,7 @@ private fun RenderVideoPlayer(
|
||||
} else {
|
||||
AspectRatioFrameLayout.RESIZE_MODE_FIXED_WIDTH
|
||||
}
|
||||
if (!hideControls) {
|
||||
if (showControls) {
|
||||
onDialog?.let { innerOnDialog ->
|
||||
setFullscreenButtonClickListener {
|
||||
controller.pause()
|
||||
@ -783,7 +783,7 @@ private fun RenderVideoPlayer(
|
||||
|
||||
waveform?.let { Waveform(it, controller, remember { Modifier.align(Alignment.Center) }) }
|
||||
|
||||
if (!hideControls) {
|
||||
if (showControls) {
|
||||
val startingMuteState = remember(controller) { controller.volume < 0.001 }
|
||||
|
||||
MuteButton(
|
||||
|
@ -40,6 +40,7 @@ import androidx.compose.material3.DropdownMenuItem
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.MutableState
|
||||
@ -82,6 +83,8 @@ import com.vitorpamplona.amethyst.service.BlurHashRequester
|
||||
import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled
|
||||
import com.vitorpamplona.amethyst.ui.actions.InformationDialog
|
||||
import com.vitorpamplona.amethyst.ui.actions.LoadingAnimation
|
||||
import com.vitorpamplona.amethyst.ui.components.util.DeviceUtils
|
||||
import com.vitorpamplona.amethyst.ui.navigation.getActivity
|
||||
import com.vitorpamplona.amethyst.ui.note.BlankNote
|
||||
import com.vitorpamplona.amethyst.ui.note.DownloadForOfflineIcon
|
||||
import com.vitorpamplona.amethyst.ui.note.HashCheckFailedIcon
|
||||
@ -118,6 +121,12 @@ fun ZoomableContentView(
|
||||
) {
|
||||
var dialogOpen by remember(content) { mutableStateOf(false) }
|
||||
|
||||
val activity = LocalView.current.context.getActivity()
|
||||
val currentWindowSize = currentWindowAdaptiveInfo().windowSizeClass
|
||||
|
||||
val isLandscapeMode = DeviceUtils.isLandscapeMetric(LocalContext.current)
|
||||
val isFoldableOrLarge = DeviceUtils.windowIsLarge(windowSize = currentWindowSize, isInLandscapeMode = isLandscapeMode)
|
||||
|
||||
val contentScale =
|
||||
if (isFiniteHeight) {
|
||||
ContentScale.Fit
|
||||
@ -149,7 +158,12 @@ fun ZoomableContentView(
|
||||
roundedCorner = roundedCorner,
|
||||
isFiniteHeight = isFiniteHeight,
|
||||
nostrUriCallback = content.uri,
|
||||
onDialog = { dialogOpen = true },
|
||||
onDialog = {
|
||||
dialogOpen = true
|
||||
if (!isFoldableOrLarge) {
|
||||
DeviceUtils.changeDeviceOrientation(isLandscapeMode, activity)
|
||||
}
|
||||
},
|
||||
accountViewModel = accountViewModel,
|
||||
)
|
||||
}
|
||||
@ -181,7 +195,15 @@ fun ZoomableContentView(
|
||||
}
|
||||
|
||||
if (dialogOpen) {
|
||||
ZoomableImageDialog(content, images, onDismiss = { dialogOpen = false }, accountViewModel)
|
||||
ZoomableImageDialog(
|
||||
content,
|
||||
images,
|
||||
onDismiss = {
|
||||
dialogOpen = false
|
||||
if (!isFoldableOrLarge) DeviceUtils.changeDeviceOrientation(isLandscapeMode, activity)
|
||||
},
|
||||
accountViewModel,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,86 @@
|
||||
/**
|
||||
* Copyright (c) 2024 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.ui.components.util
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.pm.ActivityInfo
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.window.core.layout.WindowHeightSizeClass
|
||||
import androidx.window.core.layout.WindowSizeClass
|
||||
import androidx.window.core.layout.WindowWidthSizeClass
|
||||
|
||||
object DeviceUtils {
|
||||
/**
|
||||
* Tries to determine if the device is
|
||||
* in landscape mode, by using the [android.util.DisplayMetrics] API.
|
||||
*
|
||||
* Credits: NewPipe devs
|
||||
*/
|
||||
fun isLandscapeMetric(context: Context): Boolean = context.resources.displayMetrics.heightPixels < context.resources.displayMetrics.widthPixels
|
||||
|
||||
fun changeDeviceOrientation(
|
||||
isInLandscape: Boolean,
|
||||
currentActivity: Activity,
|
||||
) {
|
||||
val newOrientation =
|
||||
if (isInLandscape) {
|
||||
ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
|
||||
} else {
|
||||
ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
|
||||
}
|
||||
currentActivity.requestedOrientation = newOrientation
|
||||
}
|
||||
|
||||
/**
|
||||
* This method looks at the window in which the app resides,
|
||||
* and determines if it is large, while making sure not to be affected
|
||||
* by configuration changes(such as screen rotation),
|
||||
* as the device display metrics can be affected as well.
|
||||
*
|
||||
* It could be used as an approximation of the type of device(as is the case here),
|
||||
* though one ought to be careful about multi-window situations.
|
||||
*/
|
||||
|
||||
@Composable
|
||||
fun windowIsLarge(
|
||||
isInLandscapeMode: Boolean,
|
||||
windowSize: WindowSizeClass,
|
||||
): Boolean =
|
||||
remember(windowSize) {
|
||||
if (isInLandscapeMode) {
|
||||
when (windowSize.windowHeightSizeClass) {
|
||||
WindowHeightSizeClass.COMPACT -> false
|
||||
WindowHeightSizeClass.MEDIUM -> true
|
||||
WindowHeightSizeClass.EXPANDED -> true
|
||||
else -> true
|
||||
}
|
||||
} else {
|
||||
when (windowSize.windowWidthSizeClass) {
|
||||
WindowWidthSizeClass.EXPANDED -> true
|
||||
WindowWidthSizeClass.MEDIUM -> true
|
||||
WindowWidthSizeClass.COMPACT -> false
|
||||
else -> true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -49,6 +49,7 @@ zelory = "3.0.1"
|
||||
zoomable = "1.6.1"
|
||||
zxing = "3.5.3"
|
||||
zxingAndroidEmbedded = "4.3.0"
|
||||
windowCoreAndroid = "1.3.0"
|
||||
|
||||
[libraries]
|
||||
abedElazizShe-image-compressor = { group = "com.github.AbedElazizShe", name = "LightCompressor", version.ref = "lightcompressor" }
|
||||
@ -120,6 +121,7 @@ zelory-video-compressor = { group = "id.zelory", name = "compressor", version.re
|
||||
zoomable = { group = "net.engawapg.lib", name = "zoomable", version.ref = "zoomable" }
|
||||
zxing = { group = "com.google.zxing", name = "core", version.ref = "zxing" }
|
||||
zxing-embedded = { group = "com.journeyapps", name = "zxing-android-embedded", version.ref = "zxingAndroidEmbedded" }
|
||||
androidx-window-core-android = { group = "androidx.window", name = "window-core-android", version.ref = "windowCoreAndroid" }
|
||||
|
||||
[plugins]
|
||||
androidApplication = { id = "com.android.application", version.ref = "agp" }
|
||||
|
Loading…
x
Reference in New Issue
Block a user