Refine iOS sheets and ratings

This commit is contained in:
tapframe 2026-07-16 12:33:27 +05:30
parent f9b5ef8aef
commit 1dbbbdecbc
7 changed files with 310 additions and 46 deletions

View file

@ -0,0 +1,23 @@
package com.nuvio.app.core.ui
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
internal actual val usesNativeNuvioBottomSheet: Boolean = false
@OptIn(ExperimentalMaterial3Api::class)
@Composable
internal actual fun NuvioNativeModalBottomSheet(
onDismissRequest: () -> Unit,
modifier: Modifier,
containerColor: Color,
contentColor: Color,
showDragHandle: Boolean,
fullHeight: Boolean,
content: @Composable ColumnScope.() -> Unit,
) = Unit
internal actual fun dismissNativeNuvioBottomSheet() = Unit

View file

@ -37,22 +37,35 @@ fun NuvioModalBottomSheet(
contentColor: Color = MaterialTheme.nuvio.colors.textPrimary,
shape: Shape = RoundedCornerShape(topStart = NuvioTokens.Space.s28, topEnd = NuvioTokens.Space.s28),
showDragHandle: Boolean = true,
fullHeight: Boolean = false,
content: @Composable ColumnScope.() -> Unit,
) {
ModalBottomSheet(
onDismissRequest = onDismissRequest,
sheetState = sheetState,
modifier = modifier,
containerColor = containerColor,
contentColor = contentColor,
shape = shape,
dragHandle = if (showDragHandle) {
{ NuvioBottomSheetDragHandle() }
} else {
null
},
content = content,
)
if (usesNativeNuvioBottomSheet) {
NuvioNativeModalBottomSheet(
onDismissRequest = onDismissRequest,
modifier = modifier,
containerColor = containerColor,
contentColor = contentColor,
showDragHandle = showDragHandle,
fullHeight = fullHeight,
content = content,
)
} else {
ModalBottomSheet(
onDismissRequest = onDismissRequest,
sheetState = sheetState,
modifier = modifier,
containerColor = containerColor,
contentColor = contentColor,
shape = shape,
dragHandle = if (showDragHandle) {
{ NuvioBottomSheetDragHandle() }
} else {
null
},
content = content,
)
}
}
@Composable
@ -107,7 +120,9 @@ suspend fun dismissNuvioBottomSheet(
sheetState: SheetState,
onDismiss: () -> Unit,
) {
if (sheetState.isVisible) {
if (usesNativeNuvioBottomSheet) {
dismissNativeNuvioBottomSheet()
} else if (sheetState.isVisible) {
sheetState.hide()
}
onDismiss()

View file

@ -0,0 +1,23 @@
package com.nuvio.app.core.ui
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
@OptIn(ExperimentalMaterial3Api::class)
@Composable
internal expect fun NuvioNativeModalBottomSheet(
onDismissRequest: () -> Unit,
modifier: Modifier,
containerColor: Color,
contentColor: Color,
showDragHandle: Boolean,
fullHeight: Boolean,
content: @Composable ColumnScope.() -> Unit,
)
internal expect val usesNativeNuvioBottomSheet: Boolean
internal expect fun dismissNativeNuvioBottomSheet()

View file

@ -151,17 +151,16 @@ fun CommentDetailSheet(
}
}
Spacer(modifier = Modifier.height(12.dp))
if (comment.review || comment.hasSpoilerContent) {
Spacer(modifier = Modifier.height(12.dp))
Row(horizontalArrangement = Arrangement.spacedBy(6.dp)) {
if (comment.review) {
CommentDetailChip(text = stringResource(Res.string.detail_comments_badge_review))
}
if (comment.hasSpoilerContent) {
CommentDetailChip(text = stringResource(Res.string.detail_comments_badge_spoiler))
}
comment.rating?.let { rating ->
CommentDetailChip(text = stringResource(Res.string.detail_comments_badge_rating, rating))
Row(horizontalArrangement = Arrangement.spacedBy(6.dp)) {
if (comment.review) {
CommentDetailChip(text = stringResource(Res.string.detail_comments_badge_review))
}
if (comment.hasSpoilerContent) {
CommentDetailChip(text = stringResource(Res.string.detail_comments_badge_spoiler))
}
}
}
@ -190,11 +189,20 @@ fun CommentDetailSheet(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
) {
Text(
text = stringResource(Res.string.detail_comments_likes, comment.likes),
style = MaterialTheme.typography.labelMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
comment.rating?.let { rating ->
Text(
text = stringResource(Res.string.detail_comments_badge_rating, rating),
style = MaterialTheme.typography.labelMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
}
Text(
text = stringResource(Res.string.detail_comments_likes, comment.likes),
style = MaterialTheme.typography.labelMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
}
comment.createdAt?.take(10)?.let { date ->
Text(
text = date,

View file

@ -214,15 +214,14 @@ private fun CommentCard(
fontWeight = FontWeight.SemiBold,
)
Row(horizontalArrangement = Arrangement.spacedBy(6.dp)) {
if (review.review) {
CommentChip(text = stringResource(Res.string.detail_comments_badge_review))
}
if (review.hasSpoilerContent) {
CommentChip(text = stringResource(Res.string.detail_comments_badge_spoiler))
}
review.rating?.let { rating ->
CommentChip(text = stringResource(Res.string.detail_comments_badge_rating, rating))
if (review.review || review.hasSpoilerContent) {
Row(horizontalArrangement = Arrangement.spacedBy(6.dp)) {
if (review.review) {
CommentChip(text = stringResource(Res.string.detail_comments_badge_review))
}
if (review.hasSpoilerContent) {
CommentChip(text = stringResource(Res.string.detail_comments_badge_spoiler))
}
}
}
@ -235,13 +234,23 @@ private fun CommentCard(
modifier = Modifier.weight(1f, fill = false),
)
Text(
text = stringResource(Res.string.detail_comments_likes, review.likes),
style = MaterialTheme.typography.labelMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.7f),
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
review.rating?.let { rating ->
Text(
text = stringResource(Res.string.detail_comments_badge_rating, rating),
style = MaterialTheme.typography.labelMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.7f),
maxLines = 1,
)
}
Text(
text = stringResource(Res.string.detail_comments_likes, review.likes),
style = MaterialTheme.typography.labelMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.7f),
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
}
}
}
}

View file

@ -315,6 +315,7 @@ private fun CardDepthFineTuneSheet(
onDismiss()
},
sheetState = sheetState,
fullHeight = true,
) {
Column(
modifier = Modifier

View file

@ -0,0 +1,185 @@
package com.nuvio.app.core.ui
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.LocalRippleConfiguration
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.uikit.LocalUIViewController
import androidx.compose.ui.unit.Density
import androidx.compose.ui.window.ComposeUIViewController
import platform.UIKit.UIAdaptivePresentationControllerDelegateProtocol
import platform.UIKit.UIColor
import platform.UIKit.UIModalPresentationPageSheet
import platform.UIKit.UIPresentationController
import platform.UIKit.UISheetPresentationController
import platform.UIKit.UISheetPresentationControllerDetent
import platform.UIKit.UISheetPresentationControllerDetentIdentifierMedium
import platform.UIKit.UIViewController
import platform.UIKit.presentationController
import platform.darwin.NSObject
internal actual val usesNativeNuvioBottomSheet: Boolean = true
private var activeNativeBottomSheet: UIViewController? = null
private var activeNativeBottomSheetDelegate: NuvioNativeBottomSheetDelegate? = null
@OptIn(ExperimentalMaterial3Api::class)
@Composable
internal actual fun NuvioNativeModalBottomSheet(
onDismissRequest: () -> Unit,
modifier: Modifier,
containerColor: Color,
contentColor: Color,
showDragHandle: Boolean,
fullHeight: Boolean,
content: @Composable androidx.compose.foundation.layout.ColumnScope.() -> Unit,
) {
val parentViewController = LocalUIViewController.current
val latestContent = rememberUpdatedState(content)
val latestOnDismissRequest = rememberUpdatedState(onDismissRequest)
val latestContainerColor = rememberUpdatedState(containerColor)
val latestContentColor = rememberUpdatedState(contentColor)
val latestModifier = rememberUpdatedState(modifier)
val colorScheme = MaterialTheme.colorScheme
val typography = MaterialTheme.typography
val shapes = MaterialTheme.shapes
val themeTokens = LocalNuvioThemeTokens.current
val typeScale = LocalNuvioTypeScale.current
val appTheme = LocalAppTheme.current
val density = LocalDensity.current
val rippleConfiguration = LocalRippleConfiguration.current
val latestColorScheme = rememberUpdatedState(colorScheme)
val latestTypography = rememberUpdatedState(typography)
val latestShapes = rememberUpdatedState(shapes)
val latestThemeTokens = rememberUpdatedState(themeTokens)
val latestTypeScale = rememberUpdatedState(typeScale)
val latestAppTheme = rememberUpdatedState(appTheme)
val latestDensity = rememberUpdatedState(density)
val latestRippleConfiguration = rememberUpdatedState(rippleConfiguration)
DisposableEffect(parentViewController) {
val contentController = ComposeUIViewController {
CompositionLocalProvider(
LocalDensity provides Density(
density = latestDensity.value.density,
fontScale = latestDensity.value.fontScale,
),
LocalNuvioThemeTokens provides latestThemeTokens.value,
LocalNuvioTypeScale provides latestTypeScale.value,
LocalAppTheme provides latestAppTheme.value,
LocalRippleConfiguration provides latestRippleConfiguration.value,
) {
MaterialTheme(
colorScheme = latestColorScheme.value,
typography = latestTypography.value,
shapes = latestShapes.value,
) {
CompositionLocalProvider(
LocalContentColor provides latestContentColor.value,
) {
Column(
modifier = latestModifier.value
.fillMaxSize()
.background(latestContainerColor.value)
.padding(top = NuvioTokens.Space.s20),
) {
latestContent.value(this)
}
}
}
}
}
val sheetController = contentController
sheetController.modalPresentationStyle = UIModalPresentationPageSheet
sheetController.view.backgroundColor = latestContainerColor.value.toUIColor()
(sheetController.presentationController() as? UISheetPresentationController)?.apply {
if (fullHeight) {
detents = listOf(UISheetPresentationControllerDetent.largeDetent())
} else {
detents = listOf(
UISheetPresentationControllerDetent.mediumDetent(),
UISheetPresentationControllerDetent.largeDetent(),
)
selectedDetentIdentifier = UISheetPresentationControllerDetentIdentifierMedium
}
prefersGrabberVisible = showDragHandle
prefersScrollingExpandsWhenScrolledToEdge = false
}
val dismissalDelegate = NuvioNativeBottomSheetDelegate {
if (activeNativeBottomSheet === sheetController) {
activeNativeBottomSheet = null
activeNativeBottomSheetDelegate = null
}
latestOnDismissRequest.value()
}
sheetController.presentationController()?.delegate = dismissalDelegate
activeNativeBottomSheet?.dismissViewControllerAnimated(
flag = false,
completion = null,
)
activeNativeBottomSheet = sheetController
activeNativeBottomSheetDelegate = dismissalDelegate
parentViewController.presentViewController(
viewControllerToPresent = sheetController,
animated = true,
completion = null,
)
onDispose {
if (activeNativeBottomSheet === sheetController) {
activeNativeBottomSheet = null
activeNativeBottomSheetDelegate = null
}
if (sheetController.presentingViewController != null) {
sheetController.dismissViewControllerAnimated(
flag = true,
completion = null,
)
}
}
}
}
internal actual fun dismissNativeNuvioBottomSheet() {
activeNativeBottomSheet?.dismissViewControllerAnimated(
flag = true,
completion = null,
)
}
private class NuvioNativeBottomSheetDelegate(
private val onDismissRequest: () -> Unit,
) : NSObject(), UIAdaptivePresentationControllerDelegateProtocol {
private var didNotifyDismissal = false
override fun presentationControllerDidDismiss(presentationController: UIPresentationController) {
notifyDismissed()
}
private fun notifyDismissed() {
if (didNotifyDismissal) return
didNotifyDismissal = true
onDismissRequest()
}
}
private fun Color.toUIColor(): UIColor = UIColor(
red = red.toDouble(),
green = green.toDouble(),
blue = blue.toDouble(),
alpha = alpha.toDouble(),
)