- Performance Improvements: Simplifies intrinsic size calculations for Image Previews and Videos
- Improves rendering of chat messages - Fixes the jittering from resizing Videos and Images during loading. - Fixes video cut off when loading - Fixes blurhash covering videos in landscape - Fixes landscape video centralization - Fixes chat using Two Pane layout for landscape and regular for portrait
This commit is contained in:
@@ -45,6 +45,10 @@ fun prepareSharedViewModel(act: MainActivity): SharedPreferencesViewModel {
|
|||||||
sharedPreferencesViewModel.updateDisplaySettings(windowSizeClass, displayFeatures)
|
sharedPreferencesViewModel.updateDisplaySettings(windowSizeClass, displayFeatures)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(key1 = displayFeatures, key2 = windowSizeClass) {
|
||||||
|
sharedPreferencesViewModel.updateDisplaySettings(windowSizeClass, displayFeatures)
|
||||||
|
}
|
||||||
|
|
||||||
LaunchedEffect(act.isOnMobileDataState) {
|
LaunchedEffect(act.isOnMobileDataState) {
|
||||||
sharedPreferencesViewModel.updateConnectivityStatusState(act.isOnMobileDataState)
|
sharedPreferencesViewModel.updateConnectivityStatusState(act.isOnMobileDataState)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -305,6 +305,7 @@ fun EditPostView(
|
|||||||
VideoView(
|
VideoView(
|
||||||
myUrlPreview,
|
myUrlPreview,
|
||||||
roundedCorner = true,
|
roundedCorner = true,
|
||||||
|
isFiniteHeight = false,
|
||||||
accountViewModel = accountViewModel,
|
accountViewModel = accountViewModel,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -252,6 +252,7 @@ fun ImageVideoPost(
|
|||||||
VideoView(
|
VideoView(
|
||||||
videoUri = it.toString(),
|
videoUri = it.toString(),
|
||||||
roundedCorner = false,
|
roundedCorner = false,
|
||||||
|
isFiniteHeight = false,
|
||||||
accountViewModel = accountViewModel,
|
accountViewModel = accountViewModel,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -426,6 +426,7 @@ fun NewPostView(
|
|||||||
VideoView(
|
VideoView(
|
||||||
myUrlPreview,
|
myUrlPreview,
|
||||||
roundedCorner = true,
|
roundedCorner = true,
|
||||||
|
isFiniteHeight = false,
|
||||||
accountViewModel = accountViewModel,
|
accountViewModel = accountViewModel,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
@@ -1736,7 +1737,7 @@ fun ImageVideoDescription(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
VideoView(uri.toString(), roundedCorner = true, accountViewModel = accountViewModel)
|
VideoView(uri.toString(), roundedCorner = true, isFiniteHeight = false, accountViewModel = accountViewModel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -84,6 +84,7 @@ fun RenderLoaded(
|
|||||||
ZoomableContentView(
|
ZoomableContentView(
|
||||||
content = MediaUrlImage(url, uri = callbackUri),
|
content = MediaUrlImage(url, uri = callbackUri),
|
||||||
roundedCorner = true,
|
roundedCorner = true,
|
||||||
|
isFiniteHeight = false,
|
||||||
accountViewModel = accountViewModel,
|
accountViewModel = accountViewModel,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -92,6 +93,7 @@ fun RenderLoaded(
|
|||||||
ZoomableContentView(
|
ZoomableContentView(
|
||||||
content = MediaUrlVideo(url, uri = callbackUri),
|
content = MediaUrlVideo(url, uri = callbackUri),
|
||||||
roundedCorner = true,
|
roundedCorner = true,
|
||||||
|
isFiniteHeight = false,
|
||||||
accountViewModel = accountViewModel,
|
accountViewModel = accountViewModel,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -446,7 +446,7 @@ private fun ZoomableContentView(
|
|||||||
) {
|
) {
|
||||||
state.imagesForPager[word]?.let {
|
state.imagesForPager[word]?.let {
|
||||||
Box(modifier = HalfVertPadding) {
|
Box(modifier = HalfVertPadding) {
|
||||||
ZoomableContentView(it, state.imageList, roundedCorner = true, accountViewModel)
|
ZoomableContentView(it, state.imageList, roundedCorner = true, isFiniteHeight = false, accountViewModel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,6 @@ import androidx.compose.animation.fadeIn
|
|||||||
import androidx.compose.animation.fadeOut
|
import androidx.compose.animation.fadeOut
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.BoxWithConstraints
|
|
||||||
import androidx.compose.foundation.layout.aspectRatio
|
import androidx.compose.foundation.layout.aspectRatio
|
||||||
import androidx.compose.foundation.layout.defaultMinSize
|
import androidx.compose.foundation.layout.defaultMinSize
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
@@ -53,7 +52,6 @@ import androidx.compose.runtime.LaunchedEffect
|
|||||||
import androidx.compose.runtime.MutableState
|
import androidx.compose.runtime.MutableState
|
||||||
import androidx.compose.runtime.Stable
|
import androidx.compose.runtime.Stable
|
||||||
import androidx.compose.runtime.State
|
import androidx.compose.runtime.State
|
||||||
import androidx.compose.runtime.derivedStateOf
|
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableIntStateOf
|
import androidx.compose.runtime.mutableIntStateOf
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
@@ -72,13 +70,10 @@ import androidx.compose.ui.layout.LayoutCoordinates
|
|||||||
import androidx.compose.ui.layout.boundsInWindow
|
import androidx.compose.ui.layout.boundsInWindow
|
||||||
import androidx.compose.ui.layout.onGloballyPositioned
|
import androidx.compose.ui.layout.onGloballyPositioned
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
|
||||||
import androidx.compose.ui.platform.LocalLifecycleOwner
|
import androidx.compose.ui.platform.LocalLifecycleOwner
|
||||||
import androidx.compose.ui.platform.LocalView
|
import androidx.compose.ui.platform.LocalView
|
||||||
import androidx.compose.ui.unit.Dp
|
import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.isFinite
|
|
||||||
import androidx.compose.ui.unit.isSpecified
|
|
||||||
import androidx.compose.ui.viewinterop.AndroidView
|
import androidx.compose.ui.viewinterop.AndroidView
|
||||||
import androidx.lifecycle.Lifecycle
|
import androidx.lifecycle.Lifecycle
|
||||||
import androidx.lifecycle.LifecycleEventObserver
|
import androidx.lifecycle.LifecycleEventObserver
|
||||||
@@ -100,7 +95,6 @@ import com.vitorpamplona.amethyst.ui.note.MuteIcon
|
|||||||
import com.vitorpamplona.amethyst.ui.note.MutedIcon
|
import com.vitorpamplona.amethyst.ui.note.MutedIcon
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.theme.PinBottomIconSize
|
import com.vitorpamplona.amethyst.ui.theme.PinBottomIconSize
|
||||||
import com.vitorpamplona.amethyst.ui.theme.Size0dp
|
|
||||||
import com.vitorpamplona.amethyst.ui.theme.Size22Modifier
|
import com.vitorpamplona.amethyst.ui.theme.Size22Modifier
|
||||||
import com.vitorpamplona.amethyst.ui.theme.Size50Modifier
|
import com.vitorpamplona.amethyst.ui.theme.Size50Modifier
|
||||||
import com.vitorpamplona.amethyst.ui.theme.Size75dp
|
import com.vitorpamplona.amethyst.ui.theme.Size75dp
|
||||||
@@ -127,6 +121,7 @@ fun LoadThumbAndThenVideoView(
|
|||||||
thumbUri: String,
|
thumbUri: String,
|
||||||
authorName: String? = null,
|
authorName: String? = null,
|
||||||
roundedCorner: Boolean,
|
roundedCorner: Boolean,
|
||||||
|
isFiniteHeight: Boolean,
|
||||||
nostrUriCallback: String? = null,
|
nostrUriCallback: String? = null,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
onDialog: ((Boolean) -> Unit)? = null,
|
onDialog: ((Boolean) -> Unit)? = null,
|
||||||
@@ -156,6 +151,7 @@ fun LoadThumbAndThenVideoView(
|
|||||||
title = title,
|
title = title,
|
||||||
thumb = VideoThumb(loadingFinished.second),
|
thumb = VideoThumb(loadingFinished.second),
|
||||||
roundedCorner = roundedCorner,
|
roundedCorner = roundedCorner,
|
||||||
|
isFiniteHeight = isFiniteHeight,
|
||||||
artworkUri = thumbUri,
|
artworkUri = thumbUri,
|
||||||
authorName = authorName,
|
authorName = authorName,
|
||||||
nostrUriCallback = nostrUriCallback,
|
nostrUriCallback = nostrUriCallback,
|
||||||
@@ -168,6 +164,7 @@ fun LoadThumbAndThenVideoView(
|
|||||||
title = title,
|
title = title,
|
||||||
thumb = null,
|
thumb = null,
|
||||||
roundedCorner = roundedCorner,
|
roundedCorner = roundedCorner,
|
||||||
|
isFiniteHeight = isFiniteHeight,
|
||||||
artworkUri = thumbUri,
|
artworkUri = thumbUri,
|
||||||
authorName = authorName,
|
authorName = authorName,
|
||||||
nostrUriCallback = nostrUriCallback,
|
nostrUriCallback = nostrUriCallback,
|
||||||
@@ -184,6 +181,7 @@ fun VideoView(
|
|||||||
title: String? = null,
|
title: String? = null,
|
||||||
thumb: VideoThumb? = null,
|
thumb: VideoThumb? = null,
|
||||||
roundedCorner: Boolean,
|
roundedCorner: Boolean,
|
||||||
|
isFiniteHeight: Boolean,
|
||||||
topPaddingForControllers: Dp = Dp.Unspecified,
|
topPaddingForControllers: Dp = Dp.Unspecified,
|
||||||
waveform: ImmutableList<Int>? = null,
|
waveform: ImmutableList<Int>? = null,
|
||||||
artworkUri: String? = null,
|
artworkUri: String? = null,
|
||||||
@@ -208,7 +206,7 @@ fun VideoView(
|
|||||||
if (blurhash == null) {
|
if (blurhash == null) {
|
||||||
val ratio = aspectRatio(dimensions)
|
val ratio = aspectRatio(dimensions)
|
||||||
val modifier =
|
val modifier =
|
||||||
if (ratio != null && roundedCorner && automaticallyStartPlayback.value) {
|
if (ratio != null && automaticallyStartPlayback.value) {
|
||||||
Modifier.aspectRatio(ratio)
|
Modifier.aspectRatio(ratio)
|
||||||
} else {
|
} else {
|
||||||
Modifier
|
Modifier
|
||||||
@@ -224,6 +222,7 @@ fun VideoView(
|
|||||||
title = title,
|
title = title,
|
||||||
thumb = thumb,
|
thumb = thumb,
|
||||||
roundedCorner = roundedCorner,
|
roundedCorner = roundedCorner,
|
||||||
|
isFiniteHeight = isFiniteHeight,
|
||||||
topPaddingForControllers = topPaddingForControllers,
|
topPaddingForControllers = topPaddingForControllers,
|
||||||
waveform = waveform,
|
waveform = waveform,
|
||||||
artworkUri = artworkUri,
|
artworkUri = artworkUri,
|
||||||
@@ -239,21 +238,31 @@ fun VideoView(
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
val ratio = aspectRatio(dimensions)
|
val ratio = aspectRatio(dimensions)
|
||||||
|
|
||||||
val modifier =
|
val modifier =
|
||||||
if (ratio != null && roundedCorner) {
|
if (ratio != null) {
|
||||||
Modifier.aspectRatio(ratio)
|
Modifier.aspectRatio(ratio)
|
||||||
} else {
|
} else {
|
||||||
Modifier
|
Modifier
|
||||||
}
|
}
|
||||||
|
|
||||||
Box(modifier, contentAlignment = Alignment.Center) {
|
Box(modifier, contentAlignment = Alignment.Center) {
|
||||||
|
val image =
|
||||||
|
if (roundedCorner) {
|
||||||
|
MaterialTheme.colorScheme.imageModifier
|
||||||
|
} else {
|
||||||
|
Modifier.fillMaxWidth()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Always displays Blurharh to avoid size flickering
|
||||||
|
DisplayBlurHash(
|
||||||
|
blurhash,
|
||||||
|
null,
|
||||||
|
if (isFiniteHeight) ContentScale.FillWidth else ContentScale.FillWidth,
|
||||||
|
if (ratio != null) image.aspectRatio(ratio) else modifier,
|
||||||
|
)
|
||||||
|
|
||||||
if (!automaticallyStartPlayback.value) {
|
if (!automaticallyStartPlayback.value) {
|
||||||
DisplayBlurHash(
|
|
||||||
blurhash,
|
|
||||||
null,
|
|
||||||
ContentScale.Crop,
|
|
||||||
MaterialTheme.colorScheme.imageModifier,
|
|
||||||
)
|
|
||||||
IconButton(
|
IconButton(
|
||||||
modifier = Modifier.size(Size75dp),
|
modifier = Modifier.size(Size75dp),
|
||||||
onClick = { automaticallyStartPlayback.value = true },
|
onClick = { automaticallyStartPlayback.value = true },
|
||||||
@@ -267,6 +276,7 @@ fun VideoView(
|
|||||||
title = title,
|
title = title,
|
||||||
thumb = thumb,
|
thumb = thumb,
|
||||||
roundedCorner = roundedCorner,
|
roundedCorner = roundedCorner,
|
||||||
|
isFiniteHeight = isFiniteHeight,
|
||||||
topPaddingForControllers = topPaddingForControllers,
|
topPaddingForControllers = topPaddingForControllers,
|
||||||
waveform = waveform,
|
waveform = waveform,
|
||||||
artworkUri = artworkUri,
|
artworkUri = artworkUri,
|
||||||
@@ -291,6 +301,7 @@ fun VideoViewInner(
|
|||||||
title: String? = null,
|
title: String? = null,
|
||||||
thumb: VideoThumb? = null,
|
thumb: VideoThumb? = null,
|
||||||
roundedCorner: Boolean,
|
roundedCorner: Boolean,
|
||||||
|
isFiniteHeight: Boolean,
|
||||||
topPaddingForControllers: Dp = Dp.Unspecified,
|
topPaddingForControllers: Dp = Dp.Unspecified,
|
||||||
waveform: ImmutableList<Int>? = null,
|
waveform: ImmutableList<Int>? = null,
|
||||||
artworkUri: String? = null,
|
artworkUri: String? = null,
|
||||||
@@ -314,6 +325,7 @@ fun VideoViewInner(
|
|||||||
controller = controller,
|
controller = controller,
|
||||||
thumbData = thumb,
|
thumbData = thumb,
|
||||||
roundedCorner = roundedCorner,
|
roundedCorner = roundedCorner,
|
||||||
|
isFiniteHeight = isFiniteHeight,
|
||||||
dimensions = dimensions,
|
dimensions = dimensions,
|
||||||
blurhash = blurhash,
|
blurhash = blurhash,
|
||||||
topPaddingForControllers = topPaddingForControllers,
|
topPaddingForControllers = topPaddingForControllers,
|
||||||
@@ -638,6 +650,7 @@ private fun RenderVideoPlayer(
|
|||||||
controller: MediaController,
|
controller: MediaController,
|
||||||
thumbData: VideoThumb?,
|
thumbData: VideoThumb?,
|
||||||
roundedCorner: Boolean,
|
roundedCorner: Boolean,
|
||||||
|
isFiniteHeight: Boolean,
|
||||||
dimensions: String? = null,
|
dimensions: String? = null,
|
||||||
blurhash: String? = null,
|
blurhash: String? = null,
|
||||||
topPaddingForControllers: Dp = Dp.Unspecified,
|
topPaddingForControllers: Dp = Dp.Unspecified,
|
||||||
@@ -653,20 +666,11 @@ private fun RenderVideoPlayer(
|
|||||||
|
|
||||||
val controllerVisible = remember(controller) { mutableStateOf(false) }
|
val controllerVisible = remember(controller) { mutableStateOf(false) }
|
||||||
|
|
||||||
val videoPlaybackHeight = remember { mutableStateOf<Dp>(Dp.Unspecified) }
|
Box {
|
||||||
|
|
||||||
val localDensity = LocalDensity.current
|
|
||||||
|
|
||||||
BoxWithConstraints(
|
|
||||||
modifier =
|
|
||||||
Modifier.onGloballyPositioned { coordinates ->
|
|
||||||
videoPlaybackHeight.value = with(localDensity) { coordinates.size.height.toDp() }
|
|
||||||
},
|
|
||||||
) {
|
|
||||||
val borders = MaterialTheme.colorScheme.imageModifier
|
val borders = MaterialTheme.colorScheme.imageModifier
|
||||||
|
|
||||||
val myModifier =
|
val myModifier =
|
||||||
remember {
|
remember(controller) {
|
||||||
if (roundedCorner) {
|
if (roundedCorner) {
|
||||||
modifier.then(
|
modifier.then(
|
||||||
borders.defaultMinSize(minHeight = 75.dp).align(Alignment.Center),
|
borders.defaultMinSize(minHeight = 75.dp).align(Alignment.Center),
|
||||||
@@ -676,17 +680,6 @@ private fun RenderVideoPlayer(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val ratio = remember { aspectRatio(dimensions) }
|
|
||||||
|
|
||||||
if (ratio != null) {
|
|
||||||
DisplayBlurHash(
|
|
||||||
blurhash,
|
|
||||||
null,
|
|
||||||
ContentScale.Crop,
|
|
||||||
myModifier.aspectRatio(ratio),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
AndroidView(
|
AndroidView(
|
||||||
modifier = myModifier,
|
modifier = myModifier,
|
||||||
factory = { context: Context ->
|
factory = { context: Context ->
|
||||||
@@ -703,7 +696,7 @@ private fun RenderVideoPlayer(
|
|||||||
thumbData?.thumb?.let { defaultArtwork = it }
|
thumbData?.thumb?.let { defaultArtwork = it }
|
||||||
hideController()
|
hideController()
|
||||||
resizeMode =
|
resizeMode =
|
||||||
if (maxHeight.isFinite) {
|
if (isFiniteHeight) {
|
||||||
AspectRatioFrameLayout.RESIZE_MODE_FIT
|
AspectRatioFrameLayout.RESIZE_MODE_FIT
|
||||||
} else {
|
} else {
|
||||||
AspectRatioFrameLayout.RESIZE_MODE_FIXED_WIDTH
|
AspectRatioFrameLayout.RESIZE_MODE_FIXED_WIDTH
|
||||||
@@ -728,26 +721,9 @@ private fun RenderVideoPlayer(
|
|||||||
|
|
||||||
val startingMuteState = remember(controller) { controller.volume < 0.001 }
|
val startingMuteState = remember(controller) { controller.volume < 0.001 }
|
||||||
|
|
||||||
val topPadding =
|
|
||||||
remember {
|
|
||||||
derivedStateOf {
|
|
||||||
if (topPaddingForControllers.isSpecified && videoPlaybackHeight.value.value > 0) {
|
|
||||||
val space = (abs(this.maxHeight.value - videoPlaybackHeight.value.value) / 2).dp
|
|
||||||
if (space > topPaddingForControllers) {
|
|
||||||
Size0dp
|
|
||||||
} else {
|
|
||||||
topPaddingForControllers - space
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Size0dp
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MuteButton(
|
MuteButton(
|
||||||
controllerVisible,
|
controllerVisible,
|
||||||
startingMuteState,
|
startingMuteState,
|
||||||
topPadding,
|
|
||||||
) { mute: Boolean ->
|
) { mute: Boolean ->
|
||||||
// makes the new setting the default for new creations.
|
// makes the new setting the default for new creations.
|
||||||
DEFAULT_MUTED_SETTING.value = mute
|
DEFAULT_MUTED_SETTING.value = mute
|
||||||
@@ -765,7 +741,6 @@ private fun RenderVideoPlayer(
|
|||||||
KeepPlayingButton(
|
KeepPlayingButton(
|
||||||
keepPlaying,
|
keepPlaying,
|
||||||
controllerVisible,
|
controllerVisible,
|
||||||
topPadding,
|
|
||||||
Modifier.align(Alignment.TopEnd),
|
Modifier.align(Alignment.TopEnd),
|
||||||
) { newKeepPlaying: Boolean ->
|
) { newKeepPlaying: Boolean ->
|
||||||
// If something else is playing and the user marks this video to keep playing, stops the other
|
// If something else is playing and the user marks this video to keep playing, stops the other
|
||||||
@@ -951,7 +926,6 @@ fun LayoutCoordinates.getDistanceToVertCenterIfVisible(view: View): Float? {
|
|||||||
private fun MuteButton(
|
private fun MuteButton(
|
||||||
controllerVisible: MutableState<Boolean>,
|
controllerVisible: MutableState<Boolean>,
|
||||||
startingMuteState: Boolean,
|
startingMuteState: Boolean,
|
||||||
topPadding: State<Dp>,
|
|
||||||
toggle: (Boolean) -> Unit,
|
toggle: (Boolean) -> Unit,
|
||||||
) {
|
) {
|
||||||
val holdOn =
|
val holdOn =
|
||||||
@@ -972,7 +946,6 @@ private fun MuteButton(
|
|||||||
|
|
||||||
AnimatedVisibility(
|
AnimatedVisibility(
|
||||||
visible = holdOn.value || controllerVisible.value,
|
visible = holdOn.value || controllerVisible.value,
|
||||||
modifier = Modifier.padding(top = topPadding.value),
|
|
||||||
enter = remember { fadeIn() },
|
enter = remember { fadeIn() },
|
||||||
exit = remember { fadeOut() },
|
exit = remember { fadeOut() },
|
||||||
) {
|
) {
|
||||||
@@ -1005,7 +978,6 @@ private fun MuteButton(
|
|||||||
private fun KeepPlayingButton(
|
private fun KeepPlayingButton(
|
||||||
keepPlayingStart: MutableState<Boolean>,
|
keepPlayingStart: MutableState<Boolean>,
|
||||||
controllerVisible: MutableState<Boolean>,
|
controllerVisible: MutableState<Boolean>,
|
||||||
topPadding: State<Dp>,
|
|
||||||
alignment: Modifier,
|
alignment: Modifier,
|
||||||
toggle: (Boolean) -> Unit,
|
toggle: (Boolean) -> Unit,
|
||||||
) {
|
) {
|
||||||
@@ -1013,7 +985,7 @@ private fun KeepPlayingButton(
|
|||||||
|
|
||||||
AnimatedVisibility(
|
AnimatedVisibility(
|
||||||
visible = controllerVisible.value,
|
visible = controllerVisible.value,
|
||||||
modifier = alignment.padding(top = topPadding.value),
|
modifier = alignment,
|
||||||
enter = remember { fadeIn() },
|
enter = remember { fadeIn() },
|
||||||
exit = remember { fadeOut() },
|
exit = remember { fadeOut() },
|
||||||
) {
|
) {
|
||||||
|
|||||||
+215
-255
@@ -37,22 +37,17 @@ import androidx.compose.foundation.clickable
|
|||||||
import androidx.compose.foundation.combinedClickable
|
import androidx.compose.foundation.combinedClickable
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.BoxWithConstraints
|
|
||||||
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
|
||||||
import androidx.compose.foundation.layout.FlowRow
|
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.aspectRatio
|
import androidx.compose.foundation.layout.aspectRatio
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.heightIn
|
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.layout.statusBarsPadding
|
import androidx.compose.foundation.layout.statusBarsPadding
|
||||||
import androidx.compose.foundation.layout.systemBarsPadding
|
import androidx.compose.foundation.layout.systemBarsPadding
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.layout.widthIn
|
|
||||||
import androidx.compose.foundation.pager.PagerState
|
import androidx.compose.foundation.pager.PagerState
|
||||||
import androidx.compose.foundation.pager.rememberPagerState
|
import androidx.compose.foundation.pager.rememberPagerState
|
||||||
import androidx.compose.foundation.text.InlineTextContent
|
import androidx.compose.foundation.text.InlineTextContent
|
||||||
@@ -94,7 +89,6 @@ import androidx.compose.ui.text.buildAnnotatedString
|
|||||||
import androidx.compose.ui.text.withStyle
|
import androidx.compose.ui.text.withStyle
|
||||||
import androidx.compose.ui.unit.Dp
|
import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.isFinite
|
|
||||||
import androidx.compose.ui.unit.isSpecified
|
import androidx.compose.ui.unit.isSpecified
|
||||||
import androidx.compose.ui.window.Dialog
|
import androidx.compose.ui.window.Dialog
|
||||||
import androidx.compose.ui.window.DialogProperties
|
import androidx.compose.ui.window.DialogProperties
|
||||||
@@ -104,6 +98,8 @@ import androidx.core.view.ViewCompat
|
|||||||
import coil.annotation.ExperimentalCoilApi
|
import coil.annotation.ExperimentalCoilApi
|
||||||
import coil.compose.AsyncImage
|
import coil.compose.AsyncImage
|
||||||
import coil.compose.AsyncImagePainter
|
import coil.compose.AsyncImagePainter
|
||||||
|
import coil.compose.SubcomposeAsyncImage
|
||||||
|
import coil.compose.SubcomposeAsyncImageContent
|
||||||
import com.vitorpamplona.amethyst.Amethyst
|
import com.vitorpamplona.amethyst.Amethyst
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.commons.richtext.BaseMediaContent
|
import com.vitorpamplona.amethyst.commons.richtext.BaseMediaContent
|
||||||
@@ -132,6 +128,7 @@ import com.vitorpamplona.amethyst.ui.theme.Size55dp
|
|||||||
import com.vitorpamplona.amethyst.ui.theme.Size5dp
|
import com.vitorpamplona.amethyst.ui.theme.Size5dp
|
||||||
import com.vitorpamplona.amethyst.ui.theme.Size75dp
|
import com.vitorpamplona.amethyst.ui.theme.Size75dp
|
||||||
import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer
|
import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.hashVerifierMark
|
||||||
import com.vitorpamplona.amethyst.ui.theme.imageModifier
|
import com.vitorpamplona.amethyst.ui.theme.imageModifier
|
||||||
import com.vitorpamplona.quartz.crypto.CryptoUtils
|
import com.vitorpamplona.quartz.crypto.CryptoUtils
|
||||||
import com.vitorpamplona.quartz.encoders.toHexKey
|
import com.vitorpamplona.quartz.encoders.toHexKey
|
||||||
@@ -151,6 +148,7 @@ fun ZoomableContentView(
|
|||||||
content: BaseMediaContent,
|
content: BaseMediaContent,
|
||||||
images: ImmutableList<BaseMediaContent> = remember(content) { listOf(content).toImmutableList() },
|
images: ImmutableList<BaseMediaContent> = remember(content) { listOf(content).toImmutableList() },
|
||||||
roundedCorner: Boolean,
|
roundedCorner: Boolean,
|
||||||
|
isFiniteHeight: Boolean,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
) {
|
) {
|
||||||
// store the dialog open or close state
|
// store the dialog open or close state
|
||||||
@@ -189,37 +187,43 @@ fun ZoomableContentView(
|
|||||||
when (content) {
|
when (content) {
|
||||||
is MediaUrlImage ->
|
is MediaUrlImage ->
|
||||||
SensitivityWarning(content.contentWarning != null, accountViewModel) {
|
SensitivityWarning(content.contentWarning != null, accountViewModel) {
|
||||||
UrlImageView(content, mainImageModifier, accountViewModel = accountViewModel)
|
UrlImageView(content, mainImageModifier, isFiniteHeight, accountViewModel = accountViewModel)
|
||||||
}
|
}
|
||||||
is MediaUrlVideo ->
|
is MediaUrlVideo ->
|
||||||
SensitivityWarning(content.contentWarning != null, accountViewModel) {
|
SensitivityWarning(content.contentWarning != null, accountViewModel) {
|
||||||
VideoView(
|
Box(modifier = Modifier.fillMaxWidth(), contentAlignment = Alignment.Center) {
|
||||||
videoUri = content.url,
|
VideoView(
|
||||||
title = content.description,
|
videoUri = content.url,
|
||||||
artworkUri = content.artworkUri,
|
title = content.description,
|
||||||
authorName = content.authorName,
|
artworkUri = content.artworkUri,
|
||||||
dimensions = content.dim,
|
authorName = content.authorName,
|
||||||
blurhash = content.blurhash,
|
dimensions = content.dim,
|
||||||
roundedCorner = roundedCorner,
|
blurhash = content.blurhash,
|
||||||
nostrUriCallback = content.uri,
|
roundedCorner = roundedCorner,
|
||||||
onDialog = { dialogOpen = true },
|
isFiniteHeight = isFiniteHeight,
|
||||||
accountViewModel = accountViewModel,
|
nostrUriCallback = content.uri,
|
||||||
)
|
onDialog = { dialogOpen = true },
|
||||||
|
accountViewModel = accountViewModel,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
is MediaLocalImage ->
|
is MediaLocalImage ->
|
||||||
LocalImageView(content, mainImageModifier, accountViewModel = accountViewModel)
|
LocalImageView(content, mainImageModifier, isFiniteHeight, accountViewModel = accountViewModel)
|
||||||
is MediaLocalVideo ->
|
is MediaLocalVideo ->
|
||||||
content.localFile?.let {
|
content.localFile?.let {
|
||||||
VideoView(
|
Box(modifier = Modifier.fillMaxWidth(), contentAlignment = Alignment.Center) {
|
||||||
videoUri = it.toUri().toString(),
|
VideoView(
|
||||||
title = content.description,
|
videoUri = it.toUri().toString(),
|
||||||
artworkUri = content.artworkUri,
|
title = content.description,
|
||||||
authorName = content.authorName,
|
artworkUri = content.artworkUri,
|
||||||
roundedCorner = roundedCorner,
|
authorName = content.authorName,
|
||||||
nostrUriCallback = content.uri,
|
roundedCorner = roundedCorner,
|
||||||
onDialog = { dialogOpen = true },
|
isFiniteHeight = isFiniteHeight,
|
||||||
accountViewModel = accountViewModel,
|
nostrUriCallback = content.uri,
|
||||||
)
|
onDialog = { dialogOpen = true },
|
||||||
|
accountViewModel = accountViewModel,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -232,12 +236,13 @@ fun ZoomableContentView(
|
|||||||
private fun LocalImageView(
|
private fun LocalImageView(
|
||||||
content: MediaLocalImage,
|
content: MediaLocalImage,
|
||||||
mainImageModifier: Modifier,
|
mainImageModifier: Modifier,
|
||||||
|
isFiniteHeight: Boolean,
|
||||||
topPaddingForControllers: Dp = Dp.Unspecified,
|
topPaddingForControllers: Dp = Dp.Unspecified,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
alwayShowImage: Boolean = false,
|
alwayShowImage: Boolean = false,
|
||||||
) {
|
) {
|
||||||
if (content.localFileExists()) {
|
if (content.localFileExists()) {
|
||||||
BoxWithConstraints(contentAlignment = Alignment.Center) {
|
Box(contentAlignment = Alignment.Center) {
|
||||||
val showImage =
|
val showImage =
|
||||||
remember {
|
remember {
|
||||||
mutableStateOf(
|
mutableStateOf(
|
||||||
@@ -245,50 +250,85 @@ private fun LocalImageView(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val myModifier =
|
|
||||||
remember {
|
|
||||||
mainImageModifier.widthIn(max = maxWidth).heightIn(max = maxHeight)
|
|
||||||
/*
|
|
||||||
.run {
|
|
||||||
aspectRatio(content.dim)?.let { ratio ->
|
|
||||||
this.aspectRatio(ratio, false)
|
|
||||||
} ?: this
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
|
|
||||||
val contentScale =
|
val contentScale =
|
||||||
remember {
|
remember {
|
||||||
if (maxHeight.isFinite) ContentScale.Fit else ContentScale.FillWidth
|
if (isFiniteHeight) ContentScale.Fit else ContentScale.FillWidth
|
||||||
}
|
}
|
||||||
|
|
||||||
val verifierModifier =
|
val ratio = remember(content) { aspectRatio(content.dim) }
|
||||||
if (topPaddingForControllers.isSpecified) {
|
|
||||||
Modifier.padding(top = topPaddingForControllers).align(Alignment.TopEnd)
|
|
||||||
} else {
|
|
||||||
Modifier.align(Alignment.TopEnd)
|
|
||||||
}
|
|
||||||
|
|
||||||
val painterState = remember { mutableStateOf<AsyncImagePainter.State?>(null) }
|
|
||||||
|
|
||||||
if (showImage.value) {
|
if (showImage.value) {
|
||||||
AsyncImage(
|
SubcomposeAsyncImage(
|
||||||
model = content.localFile,
|
model = content.localFile,
|
||||||
contentDescription = content.description,
|
contentDescription = content.description,
|
||||||
contentScale = contentScale,
|
contentScale = contentScale,
|
||||||
modifier = myModifier,
|
modifier = mainImageModifier,
|
||||||
onState = { painterState.value = it },
|
) {
|
||||||
)
|
when (painter.state) {
|
||||||
}
|
is AsyncImagePainter.State.Loading,
|
||||||
|
-> {
|
||||||
|
if (content.blurhash != null) {
|
||||||
|
if (ratio != null) {
|
||||||
|
DisplayBlurHash(
|
||||||
|
content.blurhash,
|
||||||
|
content.description,
|
||||||
|
contentScale,
|
||||||
|
Modifier.aspectRatio(ratio),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
DisplayBlurHash(
|
||||||
|
content.blurhash,
|
||||||
|
content.description,
|
||||||
|
contentScale,
|
||||||
|
Modifier,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
DisplayUrlWithLoadingSymbol(content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is AsyncImagePainter.State.Error -> {
|
||||||
|
BlankNote()
|
||||||
|
}
|
||||||
|
is AsyncImagePainter.State.Success -> {
|
||||||
|
SubcomposeAsyncImageContent()
|
||||||
|
|
||||||
AddedImageFeatures(
|
content.isVerified?.let {
|
||||||
painterState,
|
val verifierModifier =
|
||||||
content,
|
if (topPaddingForControllers.isSpecified) {
|
||||||
contentScale,
|
Modifier.align(Alignment.TopEnd).padding(top = topPaddingForControllers)
|
||||||
myModifier,
|
} else {
|
||||||
verifierModifier,
|
Modifier.align(Alignment.TopEnd)
|
||||||
showImage,
|
}
|
||||||
)
|
|
||||||
|
Box(verifierModifier, contentAlignment = Alignment.TopEnd) {
|
||||||
|
HashVerificationSymbol(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (content.blurhash != null && ratio != null) {
|
||||||
|
DisplayBlurHash(
|
||||||
|
content.blurhash,
|
||||||
|
content.description,
|
||||||
|
ContentScale.Crop,
|
||||||
|
mainImageModifier
|
||||||
|
.aspectRatio(ratio)
|
||||||
|
.clickable { showImage.value = true },
|
||||||
|
)
|
||||||
|
IconButton(
|
||||||
|
modifier = Modifier.size(Size75dp),
|
||||||
|
onClick = { showImage.value = true },
|
||||||
|
) {
|
||||||
|
DownloadForOfflineIcon(Size75dp, Color.White)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ImageUrlWithDownloadButton(content.uri, showImage)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
BlankNote()
|
BlankNote()
|
||||||
@@ -299,11 +339,12 @@ private fun LocalImageView(
|
|||||||
private fun UrlImageView(
|
private fun UrlImageView(
|
||||||
content: MediaUrlImage,
|
content: MediaUrlImage,
|
||||||
mainImageModifier: Modifier,
|
mainImageModifier: Modifier,
|
||||||
|
isFiniteHeight: Boolean,
|
||||||
topPaddingForControllers: Dp = Dp.Unspecified,
|
topPaddingForControllers: Dp = Dp.Unspecified,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
alwayShowImage: Boolean = false,
|
alwayShowImage: Boolean = false,
|
||||||
) {
|
) {
|
||||||
BoxWithConstraints(contentAlignment = Alignment.Center) {
|
Box(contentAlignment = Alignment.Center) {
|
||||||
val showImage =
|
val showImage =
|
||||||
remember {
|
remember {
|
||||||
mutableStateOf(
|
mutableStateOf(
|
||||||
@@ -311,42 +352,80 @@ private fun UrlImageView(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val myModifier =
|
val contentScale = if (isFiniteHeight) ContentScale.Fit else ContentScale.FillWidth
|
||||||
remember {
|
|
||||||
mainImageModifier.widthIn(max = maxWidth).heightIn(max = maxHeight)
|
|
||||||
}
|
|
||||||
|
|
||||||
val contentScale = if (maxHeight.isFinite) ContentScale.Fit else ContentScale.FillWidth
|
val ratio = remember(content) { aspectRatio(content.dim) }
|
||||||
|
|
||||||
val verifierModifier =
|
|
||||||
if (topPaddingForControllers.isSpecified) {
|
|
||||||
Modifier.padding(top = topPaddingForControllers).align(Alignment.TopEnd)
|
|
||||||
} else {
|
|
||||||
Modifier.align(Alignment.TopEnd)
|
|
||||||
}
|
|
||||||
|
|
||||||
val painterState = remember { mutableStateOf<AsyncImagePainter.State?>(null) }
|
|
||||||
|
|
||||||
if (showImage.value) {
|
if (showImage.value) {
|
||||||
AsyncImage(
|
SubcomposeAsyncImage(
|
||||||
model = content.url,
|
model = content.url,
|
||||||
contentDescription = content.description,
|
contentDescription = content.description,
|
||||||
contentScale = contentScale,
|
contentScale = contentScale,
|
||||||
modifier = myModifier,
|
modifier = mainImageModifier,
|
||||||
onState = {
|
) {
|
||||||
painterState.value = it
|
when (painter.state) {
|
||||||
},
|
is AsyncImagePainter.State.Loading,
|
||||||
)
|
-> {
|
||||||
}
|
if (content.blurhash != null) {
|
||||||
|
if (ratio != null) {
|
||||||
|
DisplayBlurHash(
|
||||||
|
content.blurhash,
|
||||||
|
content.description,
|
||||||
|
ContentScale.Crop,
|
||||||
|
Modifier.aspectRatio(ratio),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
DisplayBlurHash(
|
||||||
|
content.blurhash,
|
||||||
|
content.description,
|
||||||
|
ContentScale.Crop,
|
||||||
|
Modifier,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
DisplayUrlWithLoadingSymbol(content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is AsyncImagePainter.State.Error -> {
|
||||||
|
ClickableUrl(urlText = "${content.url} ", url = content.url)
|
||||||
|
}
|
||||||
|
is AsyncImagePainter.State.Success -> {
|
||||||
|
SubcomposeAsyncImageContent()
|
||||||
|
|
||||||
AddedImageFeatures(
|
val verifierModifier =
|
||||||
painterState,
|
if (topPaddingForControllers.isSpecified) {
|
||||||
content,
|
Modifier.align(Alignment.TopEnd).padding(top = topPaddingForControllers)
|
||||||
contentScale,
|
} else {
|
||||||
myModifier,
|
Modifier.align(Alignment.TopEnd)
|
||||||
verifierModifier,
|
}
|
||||||
showImage,
|
|
||||||
)
|
Box(verifierModifier, contentAlignment = Alignment.TopEnd) {
|
||||||
|
ShowHash(content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (content.blurhash != null && ratio != null) {
|
||||||
|
DisplayBlurHash(
|
||||||
|
content.blurhash,
|
||||||
|
content.description,
|
||||||
|
ContentScale.Crop,
|
||||||
|
mainImageModifier
|
||||||
|
.aspectRatio(ratio)
|
||||||
|
.clickable { showImage.value = true },
|
||||||
|
)
|
||||||
|
IconButton(
|
||||||
|
modifier = Modifier.size(Size75dp),
|
||||||
|
onClick = { showImage.value = true },
|
||||||
|
) {
|
||||||
|
DownloadForOfflineIcon(Size75dp, Color.White)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ImageUrlWithDownloadButton(content.url, showImage)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -411,134 +490,7 @@ private fun InlineDownloadIcon(showImage: MutableState<Boolean>) =
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@OptIn(ExperimentalLayoutApi::class)
|
fun ShowHash(content: MediaUrlContent) {
|
||||||
private fun AddedImageFeatures(
|
|
||||||
painter: MutableState<AsyncImagePainter.State?>,
|
|
||||||
content: MediaLocalImage,
|
|
||||||
contentScale: ContentScale,
|
|
||||||
myModifier: Modifier,
|
|
||||||
verifiedModifier: Modifier,
|
|
||||||
showImage: MutableState<Boolean>,
|
|
||||||
) {
|
|
||||||
val ratio = remember { aspectRatio(content.dim) }
|
|
||||||
|
|
||||||
if (!showImage.value) {
|
|
||||||
if (content.blurhash != null && ratio != null) {
|
|
||||||
DisplayBlurHash(
|
|
||||||
content.blurhash,
|
|
||||||
content.description,
|
|
||||||
ContentScale.Crop,
|
|
||||||
myModifier.aspectRatio(ratio).clickable { showImage.value = true },
|
|
||||||
)
|
|
||||||
IconButton(
|
|
||||||
modifier = Modifier.size(Size75dp),
|
|
||||||
onClick = { showImage.value = true },
|
|
||||||
) {
|
|
||||||
DownloadForOfflineIcon(Size75dp, Color.White)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ImageUrlWithDownloadButton(content.uri, showImage)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
when (painter.value) {
|
|
||||||
null,
|
|
||||||
is AsyncImagePainter.State.Loading,
|
|
||||||
-> {
|
|
||||||
if (content.blurhash != null) {
|
|
||||||
if (ratio != null) {
|
|
||||||
DisplayBlurHash(
|
|
||||||
content.blurhash,
|
|
||||||
content.description,
|
|
||||||
ContentScale.Crop,
|
|
||||||
myModifier.aspectRatio(ratio),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
DisplayBlurHash(content.blurhash, content.description, contentScale, myModifier)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
FlowRow { DisplayUrlWithLoadingSymbol(content) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is AsyncImagePainter.State.Error -> {
|
|
||||||
BlankNote()
|
|
||||||
}
|
|
||||||
is AsyncImagePainter.State.Success -> {
|
|
||||||
content.isVerified?.let {
|
|
||||||
HashVerificationSymbol(it, verifiedModifier)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
@OptIn(ExperimentalLayoutApi::class)
|
|
||||||
private fun AddedImageFeatures(
|
|
||||||
painter: MutableState<AsyncImagePainter.State?>,
|
|
||||||
content: MediaUrlImage,
|
|
||||||
contentScale: ContentScale,
|
|
||||||
myModifier: Modifier,
|
|
||||||
verifiedModifier: Modifier,
|
|
||||||
showImage: MutableState<Boolean>,
|
|
||||||
) {
|
|
||||||
val ratio = remember(content.url) { aspectRatio(content.dim) }
|
|
||||||
|
|
||||||
if (!showImage.value) {
|
|
||||||
if (content.blurhash != null && ratio != null) {
|
|
||||||
DisplayBlurHash(
|
|
||||||
content.blurhash,
|
|
||||||
content.description,
|
|
||||||
ContentScale.Crop,
|
|
||||||
myModifier.aspectRatio(ratio).clickable { showImage.value = true },
|
|
||||||
)
|
|
||||||
IconButton(
|
|
||||||
modifier = Modifier.size(Size75dp),
|
|
||||||
onClick = { showImage.value = true },
|
|
||||||
) {
|
|
||||||
DownloadForOfflineIcon(Size75dp, Color.White)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ImageUrlWithDownloadButton(content.url, showImage)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
when (painter.value) {
|
|
||||||
null,
|
|
||||||
is AsyncImagePainter.State.Loading,
|
|
||||||
-> {
|
|
||||||
if (content.blurhash != null) {
|
|
||||||
if (ratio != null) {
|
|
||||||
DisplayBlurHash(
|
|
||||||
content.blurhash,
|
|
||||||
content.description,
|
|
||||||
ContentScale.Crop,
|
|
||||||
myModifier.aspectRatio(ratio),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
DisplayBlurHash(content.blurhash, content.description, contentScale, myModifier)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
FlowRow(Modifier.fillMaxWidth()) { DisplayUrlWithLoadingSymbol(content) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is AsyncImagePainter.State.Error -> {
|
|
||||||
FlowRow(Modifier.fillMaxWidth()) {
|
|
||||||
ClickableUrl(urlText = "${content.url} ", url = content.url)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is AsyncImagePainter.State.Success -> {
|
|
||||||
ShowHash(content, verifiedModifier)
|
|
||||||
}
|
|
||||||
else -> {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun ShowHash(
|
|
||||||
content: MediaUrlContent,
|
|
||||||
verifiedModifier: Modifier,
|
|
||||||
) {
|
|
||||||
var verifiedHash by remember(content.url) { mutableStateOf<Boolean?>(null) }
|
var verifiedHash by remember(content.url) { mutableStateOf<Boolean?>(null) }
|
||||||
|
|
||||||
if (content.hash != null) {
|
if (content.hash != null) {
|
||||||
@@ -553,7 +505,7 @@ fun ShowHash(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
verifiedHash?.let { HashVerificationSymbol(it, verifiedModifier) }
|
verifiedHash?.let { HashVerificationSymbol(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun aspectRatio(dim: String?): Float? {
|
fun aspectRatio(dim: String?): Float? {
|
||||||
@@ -777,6 +729,7 @@ private fun DialogContent(
|
|||||||
RenderImageOrVideo(
|
RenderImageOrVideo(
|
||||||
content = it,
|
content = it,
|
||||||
roundedCorner = false,
|
roundedCorner = false,
|
||||||
|
isFiniteHeight = true,
|
||||||
topPaddingForControllers = Size55dp,
|
topPaddingForControllers = Size55dp,
|
||||||
onControllerVisibilityChanged = { controllerVisible.value = it },
|
onControllerVisibilityChanged = { controllerVisible.value = it },
|
||||||
onToggleControllerVisibility = { controllerVisible.value = !controllerVisible.value },
|
onToggleControllerVisibility = { controllerVisible.value = !controllerVisible.value },
|
||||||
@@ -788,6 +741,7 @@ private fun DialogContent(
|
|||||||
RenderImageOrVideo(
|
RenderImageOrVideo(
|
||||||
content = imageUrl,
|
content = imageUrl,
|
||||||
roundedCorner = false,
|
roundedCorner = false,
|
||||||
|
isFiniteHeight = true,
|
||||||
topPaddingForControllers = Size55dp,
|
topPaddingForControllers = Size55dp,
|
||||||
onControllerVisibilityChanged = { controllerVisible.value = it },
|
onControllerVisibilityChanged = { controllerVisible.value = it },
|
||||||
onToggleControllerVisibility = { controllerVisible.value = !controllerVisible.value },
|
onToggleControllerVisibility = { controllerVisible.value = !controllerVisible.value },
|
||||||
@@ -801,7 +755,12 @@ private fun DialogContent(
|
|||||||
exit = remember { fadeOut() },
|
exit = remember { fadeOut() },
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.padding(10.dp).statusBarsPadding().systemBarsPadding().fillMaxWidth(),
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.padding(10.dp)
|
||||||
|
.statusBarsPadding()
|
||||||
|
.systemBarsPadding()
|
||||||
|
.fillMaxWidth(),
|
||||||
horizontalArrangement = Arrangement.SpaceBetween,
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
@@ -928,6 +887,7 @@ private fun ShareImageAction(
|
|||||||
private fun RenderImageOrVideo(
|
private fun RenderImageOrVideo(
|
||||||
content: BaseMediaContent,
|
content: BaseMediaContent,
|
||||||
roundedCorner: Boolean,
|
roundedCorner: Boolean,
|
||||||
|
isFiniteHeight: Boolean,
|
||||||
topPaddingForControllers: Dp = Dp.Unspecified,
|
topPaddingForControllers: Dp = Dp.Unspecified,
|
||||||
onControllerVisibilityChanged: ((Boolean) -> Unit)? = null,
|
onControllerVisibilityChanged: ((Boolean) -> Unit)? = null,
|
||||||
onToggleControllerVisibility: (() -> Unit)? = null,
|
onToggleControllerVisibility: (() -> Unit)? = null,
|
||||||
@@ -937,7 +897,8 @@ private fun RenderImageOrVideo(
|
|||||||
|
|
||||||
if (content is MediaUrlImage) {
|
if (content is MediaUrlImage) {
|
||||||
val mainModifier =
|
val mainModifier =
|
||||||
Modifier.fillMaxSize()
|
Modifier
|
||||||
|
.fillMaxSize()
|
||||||
.zoomable(
|
.zoomable(
|
||||||
rememberZoomState(),
|
rememberZoomState(),
|
||||||
onTap = {
|
onTap = {
|
||||||
@@ -950,18 +911,20 @@ private fun RenderImageOrVideo(
|
|||||||
UrlImageView(
|
UrlImageView(
|
||||||
content = content,
|
content = content,
|
||||||
mainImageModifier = mainModifier,
|
mainImageModifier = mainModifier,
|
||||||
|
isFiniteHeight = isFiniteHeight,
|
||||||
topPaddingForControllers = topPaddingForControllers,
|
topPaddingForControllers = topPaddingForControllers,
|
||||||
accountViewModel,
|
accountViewModel = accountViewModel,
|
||||||
alwayShowImage = true,
|
alwayShowImage = true,
|
||||||
)
|
)
|
||||||
} else if (content is MediaUrlVideo) {
|
} else if (content is MediaUrlVideo) {
|
||||||
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxSize(1f)) {
|
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Center, modifier = Modifier.fillMaxSize(1f)) {
|
||||||
VideoViewInner(
|
VideoViewInner(
|
||||||
videoUri = content.url,
|
videoUri = content.url,
|
||||||
title = content.description,
|
title = content.description,
|
||||||
artworkUri = content.artworkUri,
|
artworkUri = content.artworkUri,
|
||||||
authorName = content.authorName,
|
authorName = content.authorName,
|
||||||
roundedCorner = roundedCorner,
|
roundedCorner = roundedCorner,
|
||||||
|
isFiniteHeight = isFiniteHeight,
|
||||||
topPaddingForControllers = topPaddingForControllers,
|
topPaddingForControllers = topPaddingForControllers,
|
||||||
automaticallyStartPlayback = automaticallyStartPlayback,
|
automaticallyStartPlayback = automaticallyStartPlayback,
|
||||||
onControllerVisibilityChanged = onControllerVisibilityChanged,
|
onControllerVisibilityChanged = onControllerVisibilityChanged,
|
||||||
@@ -969,7 +932,8 @@ private fun RenderImageOrVideo(
|
|||||||
}
|
}
|
||||||
} else if (content is MediaLocalImage) {
|
} else if (content is MediaLocalImage) {
|
||||||
val mainModifier =
|
val mainModifier =
|
||||||
Modifier.fillMaxSize()
|
Modifier
|
||||||
|
.fillMaxSize()
|
||||||
.zoomable(
|
.zoomable(
|
||||||
rememberZoomState(),
|
rememberZoomState(),
|
||||||
onTap = {
|
onTap = {
|
||||||
@@ -982,12 +946,13 @@ private fun RenderImageOrVideo(
|
|||||||
LocalImageView(
|
LocalImageView(
|
||||||
content = content,
|
content = content,
|
||||||
mainImageModifier = mainModifier,
|
mainImageModifier = mainModifier,
|
||||||
|
isFiniteHeight = isFiniteHeight,
|
||||||
topPaddingForControllers = topPaddingForControllers,
|
topPaddingForControllers = topPaddingForControllers,
|
||||||
accountViewModel,
|
accountViewModel = accountViewModel,
|
||||||
alwayShowImage = true,
|
alwayShowImage = true,
|
||||||
)
|
)
|
||||||
} else if (content is MediaLocalVideo) {
|
} else if (content is MediaLocalVideo) {
|
||||||
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxSize(1f)) {
|
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Center, modifier = Modifier.fillMaxSize(1f)) {
|
||||||
content.localFile?.let {
|
content.localFile?.let {
|
||||||
VideoViewInner(
|
VideoViewInner(
|
||||||
videoUri = it.toUri().toString(),
|
videoUri = it.toUri().toString(),
|
||||||
@@ -995,6 +960,7 @@ private fun RenderImageOrVideo(
|
|||||||
artworkUri = content.artworkUri,
|
artworkUri = content.artworkUri,
|
||||||
authorName = content.authorName,
|
authorName = content.authorName,
|
||||||
roundedCorner = roundedCorner,
|
roundedCorner = roundedCorner,
|
||||||
|
isFiniteHeight = isFiniteHeight,
|
||||||
topPaddingForControllers = topPaddingForControllers,
|
topPaddingForControllers = topPaddingForControllers,
|
||||||
automaticallyStartPlayback = automaticallyStartPlayback,
|
automaticallyStartPlayback = automaticallyStartPlayback,
|
||||||
onControllerVisibilityChanged = onControllerVisibilityChanged,
|
onControllerVisibilityChanged = onControllerVisibilityChanged,
|
||||||
@@ -1020,12 +986,8 @@ private suspend fun verifyHash(content: MediaUrlContent): Boolean? {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun HashVerificationSymbol(
|
private fun HashVerificationSymbol(verifiedHash: Boolean) {
|
||||||
verifiedHash: Boolean,
|
|
||||||
modifier: Modifier,
|
|
||||||
) {
|
|
||||||
val localContext = LocalContext.current
|
val localContext = LocalContext.current
|
||||||
|
|
||||||
val openDialogMsg = remember { mutableStateOf<String?>(null) }
|
val openDialogMsg = remember { mutableStateOf<String?>(null) }
|
||||||
|
|
||||||
openDialogMsg.value?.let {
|
openDialogMsg.value?.let {
|
||||||
@@ -1037,25 +999,23 @@ private fun HashVerificationSymbol(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Box(
|
if (verifiedHash) {
|
||||||
modifier.width(40.dp).height(40.dp).padding(10.dp),
|
IconButton(
|
||||||
) {
|
modifier = hashVerifierMark,
|
||||||
if (verifiedHash) {
|
onClick = {
|
||||||
IconButton(
|
openDialogMsg.value = localContext.getString(R.string.hash_verification_passed)
|
||||||
onClick = {
|
},
|
||||||
openDialogMsg.value = localContext.getString(R.string.hash_verification_passed)
|
) {
|
||||||
},
|
HashCheckIcon(Size30dp)
|
||||||
) {
|
}
|
||||||
HashCheckIcon(Size30dp)
|
} else {
|
||||||
}
|
IconButton(
|
||||||
} else {
|
modifier = hashVerifierMark,
|
||||||
IconButton(
|
onClick = {
|
||||||
onClick = {
|
openDialogMsg.value = localContext.getString(R.string.hash_verification_failed)
|
||||||
openDialogMsg.value = localContext.getString(R.string.hash_verification_failed)
|
},
|
||||||
},
|
) {
|
||||||
) {
|
HashCheckFailedIcon(Size30dp)
|
||||||
HashCheckFailedIcon(Size30dp)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
@@ -95,6 +95,7 @@ class MarkdownMediaRenderer(
|
|||||||
ZoomableContentView(
|
ZoomableContentView(
|
||||||
content = content,
|
content = content,
|
||||||
roundedCorner = true,
|
roundedCorner = true,
|
||||||
|
isFiniteHeight = false,
|
||||||
accountViewModel = accountViewModel,
|
accountViewModel = accountViewModel,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -116,6 +117,7 @@ class MarkdownMediaRenderer(
|
|||||||
ZoomableContentView(
|
ZoomableContentView(
|
||||||
content = content,
|
content = content,
|
||||||
roundedCorner = true,
|
roundedCorner = true,
|
||||||
|
isFiniteHeight = false,
|
||||||
accountViewModel = accountViewModel,
|
accountViewModel = accountViewModel,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,10 +29,10 @@ import androidx.compose.foundation.layout.Column
|
|||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.layout.widthIn
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.Person
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Surface
|
import androidx.compose.material3.Surface
|
||||||
@@ -40,32 +40,28 @@ import androidx.compose.material3.Text
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.MutableState
|
import androidx.compose.runtime.MutableState
|
||||||
import androidx.compose.runtime.State
|
|
||||||
import androidx.compose.runtime.derivedStateOf
|
import androidx.compose.runtime.derivedStateOf
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.livedata.observeAsState
|
import androidx.compose.runtime.livedata.observeAsState
|
||||||
import androidx.compose.runtime.mutableIntStateOf
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.produceState
|
import androidx.compose.runtime.produceState
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.Shape
|
import androidx.compose.ui.graphics.Shape
|
||||||
import androidx.compose.ui.graphics.compositeOver
|
import androidx.compose.ui.graphics.compositeOver
|
||||||
import androidx.compose.ui.layout.onSizeChanged
|
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.model.FeatureSetType
|
import com.vitorpamplona.amethyst.model.FeatureSetType
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
import com.vitorpamplona.amethyst.model.User
|
import com.vitorpamplona.amethyst.model.User
|
||||||
import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji
|
import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji
|
||||||
import com.vitorpamplona.amethyst.ui.components.RobohashFallbackAsyncImage
|
|
||||||
import com.vitorpamplona.amethyst.ui.components.SensitivityWarning
|
import com.vitorpamplona.amethyst.ui.components.SensitivityWarning
|
||||||
import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer
|
import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
@@ -79,14 +75,16 @@ import com.vitorpamplona.amethyst.ui.theme.Font12SP
|
|||||||
import com.vitorpamplona.amethyst.ui.theme.HalfTopPadding
|
import com.vitorpamplona.amethyst.ui.theme.HalfTopPadding
|
||||||
import com.vitorpamplona.amethyst.ui.theme.ReactionRowHeightChat
|
import com.vitorpamplona.amethyst.ui.theme.ReactionRowHeightChat
|
||||||
import com.vitorpamplona.amethyst.ui.theme.RowColSpacing
|
import com.vitorpamplona.amethyst.ui.theme.RowColSpacing
|
||||||
import com.vitorpamplona.amethyst.ui.theme.Size10dp
|
import com.vitorpamplona.amethyst.ui.theme.RowColSpacing5dp
|
||||||
import com.vitorpamplona.amethyst.ui.theme.Size15Modifier
|
import com.vitorpamplona.amethyst.ui.theme.Size15Modifier
|
||||||
import com.vitorpamplona.amethyst.ui.theme.Size20dp
|
import com.vitorpamplona.amethyst.ui.theme.Size20dp
|
||||||
import com.vitorpamplona.amethyst.ui.theme.Size5dp
|
import com.vitorpamplona.amethyst.ui.theme.Size5dp
|
||||||
import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer
|
import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.StdTopPadding
|
||||||
import com.vitorpamplona.amethyst.ui.theme.chatAuthorBox
|
import com.vitorpamplona.amethyst.ui.theme.chatAuthorBox
|
||||||
import com.vitorpamplona.amethyst.ui.theme.chatAuthorImage
|
import com.vitorpamplona.amethyst.ui.theme.incognitoIconModifier
|
||||||
import com.vitorpamplona.amethyst.ui.theme.mediumImportanceLink
|
import com.vitorpamplona.amethyst.ui.theme.mediumImportanceLink
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.messageBubbleLimits
|
||||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||||
import com.vitorpamplona.amethyst.ui.theme.subtleBorder
|
import com.vitorpamplona.amethyst.ui.theme.subtleBorder
|
||||||
import com.vitorpamplona.quartz.events.ChannelCreateEvent
|
import com.vitorpamplona.quartz.events.ChannelCreateEvent
|
||||||
@@ -132,7 +130,6 @@ fun ChatroomMessageCompose(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalFoundationApi::class)
|
|
||||||
@Composable
|
@Composable
|
||||||
fun NormalChatNote(
|
fun NormalChatNote(
|
||||||
note: Note,
|
note: Note,
|
||||||
@@ -145,6 +142,17 @@ fun NormalChatNote(
|
|||||||
onWantsToReply: (Note) -> Unit,
|
onWantsToReply: (Note) -> Unit,
|
||||||
onWantsToEditDraft: (Note) -> Unit,
|
onWantsToEditDraft: (Note) -> Unit,
|
||||||
) {
|
) {
|
||||||
|
val isLoggedInUser =
|
||||||
|
remember(note.author) {
|
||||||
|
accountViewModel.isLoggedUser(note.author)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (routeForLastRead != null) {
|
||||||
|
LaunchedEffect(key1 = routeForLastRead) {
|
||||||
|
accountViewModel.loadAndMarkAsRead(routeForLastRead, note.createdAt())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val drawAuthorInfo by
|
val drawAuthorInfo by
|
||||||
remember(note) {
|
remember(note) {
|
||||||
derivedStateOf {
|
derivedStateOf {
|
||||||
@@ -162,13 +170,104 @@ fun NormalChatNote(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ChatBubbleLayout(
|
||||||
|
isLoggedInUser = isLoggedInUser,
|
||||||
|
innerQuote = innerQuote,
|
||||||
|
isSimplified = accountViewModel.settings.featureSet == FeatureSetType.SIMPLIFIED,
|
||||||
|
hasDetailsToShow = note.zaps.isNotEmpty() || note.zapPayments.isNotEmpty() || note.reactions.isNotEmpty(),
|
||||||
|
drawAuthorInfo = drawAuthorInfo,
|
||||||
|
parentBackgroundColor = parentBackgroundColor,
|
||||||
|
onClick = {
|
||||||
|
if (note.event is ChannelCreateEvent) {
|
||||||
|
nav("Channel/${note.idHex}")
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onAuthorClick = {
|
||||||
|
note.author?.let {
|
||||||
|
nav("User/${it.pubkeyHex}")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
actionMenu = { popupExpanded, onDismiss ->
|
||||||
|
NoteQuickActionMenu(
|
||||||
|
note = note,
|
||||||
|
popupExpanded = popupExpanded,
|
||||||
|
onDismiss = onDismiss,
|
||||||
|
onWantsToEditDraft = { onWantsToEditDraft(note) },
|
||||||
|
accountViewModel = accountViewModel,
|
||||||
|
nav = nav,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
drawAuthorLine = {
|
||||||
|
DrawAuthorInfo(
|
||||||
|
note,
|
||||||
|
accountViewModel,
|
||||||
|
nav,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
detailRow = {
|
||||||
|
if (note.isDraft()) {
|
||||||
|
DisplayDraftChat()
|
||||||
|
}
|
||||||
|
IncognitoBadge(note)
|
||||||
|
ChatTimeAgo(note)
|
||||||
|
RelayBadgesHorizontal(note, accountViewModel, nav = nav)
|
||||||
|
Spacer(modifier = DoubleHorzSpacer)
|
||||||
|
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = RowColSpacing) {
|
||||||
|
ReplyReaction(
|
||||||
|
baseNote = note,
|
||||||
|
grayTint = MaterialTheme.colorScheme.placeholderText,
|
||||||
|
accountViewModel = accountViewModel,
|
||||||
|
showCounter = false,
|
||||||
|
iconSizeModifier = Size15Modifier,
|
||||||
|
) {
|
||||||
|
onWantsToReply(note)
|
||||||
|
}
|
||||||
|
Spacer(modifier = StdHorzSpacer)
|
||||||
|
LikeReaction(note, MaterialTheme.colorScheme.placeholderText, accountViewModel, nav)
|
||||||
|
|
||||||
|
ZapReaction(note, MaterialTheme.colorScheme.placeholderText, accountViewModel, nav = nav)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
) { backgroundBubbleColor ->
|
||||||
|
MessageBubbleLines(
|
||||||
|
note,
|
||||||
|
innerQuote,
|
||||||
|
backgroundBubbleColor,
|
||||||
|
onWantsToReply,
|
||||||
|
onWantsToEditDraft,
|
||||||
|
canPreview,
|
||||||
|
accountViewModel,
|
||||||
|
nav,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
|
@Composable
|
||||||
|
fun ChatBubbleLayout(
|
||||||
|
isLoggedInUser: Boolean,
|
||||||
|
innerQuote: Boolean,
|
||||||
|
isSimplified: Boolean,
|
||||||
|
hasDetailsToShow: Boolean,
|
||||||
|
drawAuthorInfo: Boolean,
|
||||||
|
parentBackgroundColor: MutableState<Color>? = null,
|
||||||
|
onClick: () -> Boolean,
|
||||||
|
onAuthorClick: () -> Unit,
|
||||||
|
actionMenu: @Composable (popupExpanded: Boolean, onDismiss: () -> Unit) -> Unit,
|
||||||
|
detailRow: @Composable () -> Unit,
|
||||||
|
drawAuthorLine: @Composable () -> Unit,
|
||||||
|
inner: @Composable (MutableState<Color>) -> Unit,
|
||||||
|
) {
|
||||||
val loggedInColors = MaterialTheme.colorScheme.mediumImportanceLink
|
val loggedInColors = MaterialTheme.colorScheme.mediumImportanceLink
|
||||||
val otherColors = MaterialTheme.colorScheme.subtleBorder
|
val otherColors = MaterialTheme.colorScheme.subtleBorder
|
||||||
val defaultBackground = MaterialTheme.colorScheme.background
|
val defaultBackground = MaterialTheme.colorScheme.background
|
||||||
|
|
||||||
val backgroundBubbleColor =
|
val backgroundBubbleColor =
|
||||||
remember {
|
remember {
|
||||||
if (accountViewModel.isLoggedUser(note.author)) {
|
if (isLoggedInUser) {
|
||||||
mutableStateOf(
|
mutableStateOf(
|
||||||
loggedInColors.compositeOver(parentBackgroundColor?.value ?: defaultBackground),
|
loggedInColors.compositeOver(parentBackgroundColor?.value ?: defaultBackground),
|
||||||
)
|
)
|
||||||
@@ -176,182 +275,176 @@ fun NormalChatNote(
|
|||||||
mutableStateOf(otherColors.compositeOver(parentBackgroundColor?.value ?: defaultBackground))
|
mutableStateOf(otherColors.compositeOver(parentBackgroundColor?.value ?: defaultBackground))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val alignment: Arrangement.Horizontal =
|
val alignment: Arrangement.Horizontal =
|
||||||
remember {
|
if (isLoggedInUser) {
|
||||||
if (accountViewModel.isLoggedUser(note.author)) {
|
Arrangement.End
|
||||||
Arrangement.End
|
} else {
|
||||||
} else {
|
Arrangement.Start
|
||||||
Arrangement.Start
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val shape: Shape =
|
val shape: Shape =
|
||||||
remember {
|
if (isLoggedInUser) {
|
||||||
if (accountViewModel.isLoggedUser(note.author)) {
|
ChatBubbleShapeMe
|
||||||
ChatBubbleShapeMe
|
} else {
|
||||||
} else {
|
ChatBubbleShapeThem
|
||||||
ChatBubbleShapeThem
|
}
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier = if (innerQuote) ChatPaddingInnerQuoteModifier else ChatPaddingModifier,
|
||||||
|
horizontalArrangement = alignment,
|
||||||
|
) {
|
||||||
|
var popupExpanded by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
val modif2 = if (innerQuote) Modifier else ChatBubbleMaxSizeModifier
|
||||||
|
|
||||||
|
val showDetails =
|
||||||
|
remember {
|
||||||
|
mutableStateOf(
|
||||||
|
if (isSimplified) {
|
||||||
|
hasDetailsToShow
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (routeForLastRead != null) {
|
val clickableModifier =
|
||||||
LaunchedEffect(key1 = routeForLastRead) {
|
remember {
|
||||||
accountViewModel.loadAndMarkAsRead(routeForLastRead, note.createdAt())
|
Modifier.combinedClickable(
|
||||||
}
|
onClick = {
|
||||||
}
|
if (!onClick()) {
|
||||||
|
if (isSimplified) {
|
||||||
Column {
|
showDetails.value = !showDetails.value
|
||||||
Row(
|
|
||||||
modifier = if (innerQuote) ChatPaddingInnerQuoteModifier else ChatPaddingModifier,
|
|
||||||
horizontalArrangement = alignment,
|
|
||||||
) {
|
|
||||||
val availableBubbleSize = remember { mutableIntStateOf(0) }
|
|
||||||
var popupExpanded by remember { mutableStateOf(false) }
|
|
||||||
|
|
||||||
val modif2 = if (innerQuote) Modifier else ChatBubbleMaxSizeModifier
|
|
||||||
|
|
||||||
val showDetails =
|
|
||||||
remember {
|
|
||||||
mutableStateOf(
|
|
||||||
if (accountViewModel.settings.featureSet == FeatureSetType.SIMPLIFIED) {
|
|
||||||
note.zaps.isNotEmpty() || note.zapPayments.isNotEmpty() || note.reactions.isNotEmpty()
|
|
||||||
} else {
|
|
||||||
true
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
val clickableModifier =
|
|
||||||
remember {
|
|
||||||
Modifier.combinedClickable(
|
|
||||||
onClick = {
|
|
||||||
if (note.event is ChannelCreateEvent) {
|
|
||||||
nav("Channel/${note.idHex}")
|
|
||||||
} else {
|
|
||||||
if (accountViewModel.settings.featureSet == FeatureSetType.SIMPLIFIED) {
|
|
||||||
showDetails.value = !showDetails.value
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
|
||||||
onLongClick = { popupExpanded = true },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
Row(
|
|
||||||
horizontalArrangement = alignment,
|
|
||||||
modifier =
|
|
||||||
modif2.onSizeChanged {
|
|
||||||
if (availableBubbleSize.intValue != it.width) {
|
|
||||||
availableBubbleSize.intValue = it.width
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
) {
|
onLongClick = { popupExpanded = true },
|
||||||
Surface(
|
)
|
||||||
color = backgroundBubbleColor.value,
|
|
||||||
shape = shape,
|
|
||||||
modifier = clickableModifier,
|
|
||||||
) {
|
|
||||||
RenderBubble(
|
|
||||||
note,
|
|
||||||
drawAuthorInfo,
|
|
||||||
alignment,
|
|
||||||
innerQuote,
|
|
||||||
backgroundBubbleColor,
|
|
||||||
onWantsToReply,
|
|
||||||
onWantsToEditDraft,
|
|
||||||
canPreview,
|
|
||||||
availableBubbleSize,
|
|
||||||
showDetails,
|
|
||||||
accountViewModel,
|
|
||||||
nav,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
NoteQuickActionMenu(
|
Row(
|
||||||
note = note,
|
horizontalArrangement = alignment,
|
||||||
popupExpanded = popupExpanded,
|
modifier = modif2,
|
||||||
onDismiss = { popupExpanded = false },
|
) {
|
||||||
onWantsToEditDraft = { onWantsToEditDraft(note) },
|
Surface(
|
||||||
accountViewModel = accountViewModel,
|
color = backgroundBubbleColor.value,
|
||||||
nav = nav,
|
shape = shape,
|
||||||
)
|
modifier = clickableModifier,
|
||||||
|
) {
|
||||||
|
Column(modifier = messageBubbleLimits, verticalArrangement = RowColSpacing5dp) {
|
||||||
|
if (drawAuthorInfo) {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = alignment,
|
||||||
|
modifier = StdTopPadding.then(Modifier.clickable(onClick = onAuthorClick)),
|
||||||
|
) {
|
||||||
|
drawAuthorLine()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inner(backgroundBubbleColor)
|
||||||
|
|
||||||
|
if (showDetails.value) {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
modifier = ReactionRowHeightChat,
|
||||||
|
) {
|
||||||
|
detailRow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
actionMenu(popupExpanded) {
|
||||||
|
popupExpanded = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
@Composable
|
@Composable
|
||||||
private fun RenderBubble(
|
private fun BubblePreview() {
|
||||||
baseNote: Note,
|
val backgroundBubbleColor =
|
||||||
drawAuthorInfo: Boolean,
|
|
||||||
alignment: Arrangement.Horizontal,
|
|
||||||
innerQuote: Boolean,
|
|
||||||
backgroundBubbleColor: MutableState<Color>,
|
|
||||||
onWantsToReply: (Note) -> Unit,
|
|
||||||
onWantsToEditDraft: (Note) -> Unit,
|
|
||||||
canPreview: Boolean,
|
|
||||||
availableBubbleSize: MutableState<Int>,
|
|
||||||
showDetails: State<Boolean>,
|
|
||||||
accountViewModel: AccountViewModel,
|
|
||||||
nav: (String) -> Unit,
|
|
||||||
) {
|
|
||||||
val bubbleSize = remember { mutableIntStateOf(0) }
|
|
||||||
|
|
||||||
val bubbleModifier =
|
|
||||||
remember {
|
remember {
|
||||||
Modifier
|
mutableStateOf<Color>(Color.Transparent)
|
||||||
.padding(start = 10.dp, end = 10.dp, bottom = 5.dp)
|
|
||||||
.onSizeChanged {
|
|
||||||
if (bubbleSize.intValue != it.width) {
|
|
||||||
bubbleSize.intValue = it.width
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Column(modifier = bubbleModifier) {
|
Column {
|
||||||
MessageBubbleLines(
|
ChatBubbleLayout(
|
||||||
drawAuthorInfo,
|
isLoggedInUser = false,
|
||||||
baseNote,
|
innerQuote = false,
|
||||||
alignment,
|
isSimplified = false,
|
||||||
availableBubbleSize,
|
hasDetailsToShow = true,
|
||||||
innerQuote,
|
drawAuthorInfo = true,
|
||||||
backgroundBubbleColor,
|
parentBackgroundColor = backgroundBubbleColor,
|
||||||
bubbleSize,
|
onClick = { false },
|
||||||
onWantsToReply,
|
onAuthorClick = {},
|
||||||
onWantsToEditDraft,
|
actionMenu = { popupExpanded, onDismiss ->
|
||||||
canPreview,
|
},
|
||||||
showDetails,
|
drawAuthorLine = {
|
||||||
accountViewModel,
|
UserDisplayNameLayout(
|
||||||
nav,
|
picture = {
|
||||||
)
|
Icon(
|
||||||
|
imageVector = Icons.Default.Person,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(Size20dp).clip(CircleShape),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
name = {
|
||||||
|
Text("Me", fontWeight = FontWeight.Bold)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
},
|
||||||
|
detailRow = { Text("Relays and Actions") },
|
||||||
|
) { backgroundBubbleColor ->
|
||||||
|
Text("This is my note")
|
||||||
|
}
|
||||||
|
|
||||||
|
ChatBubbleLayout(
|
||||||
|
isLoggedInUser = true,
|
||||||
|
innerQuote = false,
|
||||||
|
isSimplified = false,
|
||||||
|
hasDetailsToShow = true,
|
||||||
|
drawAuthorInfo = true,
|
||||||
|
parentBackgroundColor = backgroundBubbleColor,
|
||||||
|
onClick = { false },
|
||||||
|
onAuthorClick = {},
|
||||||
|
actionMenu = { popupExpanded, onDismiss ->
|
||||||
|
},
|
||||||
|
drawAuthorLine = {
|
||||||
|
UserDisplayNameLayout(
|
||||||
|
picture = {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Person,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(Size20dp).clip(CircleShape),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
name = {
|
||||||
|
Text("Me", fontWeight = FontWeight.Bold)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
},
|
||||||
|
detailRow = { Text("Relays and Actions") },
|
||||||
|
) { backgroundBubbleColor ->
|
||||||
|
Text("This is a very long long loong note")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun MessageBubbleLines(
|
private fun MessageBubbleLines(
|
||||||
drawAuthorInfo: Boolean,
|
|
||||||
baseNote: Note,
|
baseNote: Note,
|
||||||
alignment: Arrangement.Horizontal,
|
|
||||||
availableBubbleSize: MutableState<Int>,
|
|
||||||
innerQuote: Boolean,
|
innerQuote: Boolean,
|
||||||
backgroundBubbleColor: MutableState<Color>,
|
backgroundBubbleColor: MutableState<Color>,
|
||||||
bubbleSize: MutableState<Int>,
|
|
||||||
onWantsToReply: (Note) -> Unit,
|
onWantsToReply: (Note) -> Unit,
|
||||||
onWantsToEditDraft: (Note) -> Unit,
|
onWantsToEditDraft: (Note) -> Unit,
|
||||||
canPreview: Boolean,
|
canPreview: Boolean,
|
||||||
showDetails: State<Boolean>,
|
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
nav: (String) -> Unit,
|
nav: (String) -> Unit,
|
||||||
) {
|
) {
|
||||||
if (drawAuthorInfo) {
|
|
||||||
DrawAuthorInfo(
|
|
||||||
baseNote,
|
|
||||||
alignment,
|
|
||||||
accountViewModel.settings.showProfilePictures.value,
|
|
||||||
accountViewModel,
|
|
||||||
nav,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (baseNote.event !is DraftEvent) {
|
if (baseNote.event !is DraftEvent) {
|
||||||
RenderReplyRow(
|
RenderReplyRow(
|
||||||
note = baseNote,
|
note = baseNote,
|
||||||
@@ -374,41 +467,6 @@ private fun MessageBubbleLines(
|
|||||||
accountViewModel = accountViewModel,
|
accountViewModel = accountViewModel,
|
||||||
nav = nav,
|
nav = nav,
|
||||||
)
|
)
|
||||||
|
|
||||||
if (showDetails.value) {
|
|
||||||
ConstrainedStatusRow(
|
|
||||||
bubbleSize = bubbleSize,
|
|
||||||
availableBubbleSize = availableBubbleSize,
|
|
||||||
firstColumn = {
|
|
||||||
if (baseNote.isDraft()) {
|
|
||||||
DisplayDraftChat()
|
|
||||||
}
|
|
||||||
IncognitoBadge(baseNote)
|
|
||||||
ChatTimeAgo(baseNote)
|
|
||||||
RelayBadgesHorizontal(baseNote, accountViewModel, nav = nav)
|
|
||||||
Spacer(modifier = DoubleHorzSpacer)
|
|
||||||
},
|
|
||||||
secondColumn = {
|
|
||||||
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = RowColSpacing) {
|
|
||||||
LikeReaction(baseNote, MaterialTheme.colorScheme.placeholderText, accountViewModel, nav)
|
|
||||||
}
|
|
||||||
ZapReaction(baseNote, MaterialTheme.colorScheme.placeholderText, accountViewModel, nav = nav)
|
|
||||||
Spacer(modifier = StdHorzSpacer)
|
|
||||||
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = RowColSpacing) {
|
|
||||||
ReplyReaction(
|
|
||||||
baseNote = baseNote,
|
|
||||||
grayTint = MaterialTheme.colorScheme.placeholderText,
|
|
||||||
accountViewModel = accountViewModel,
|
|
||||||
showCounter = false,
|
|
||||||
iconSizeModifier = Size15Modifier,
|
|
||||||
) {
|
|
||||||
onWantsToReply(baseNote)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Spacer(modifier = StdHorzSpacer)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -536,43 +594,9 @@ private fun RenderDraftEvent(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun ConstrainedStatusRow(
|
private fun ConstrainedStatusRow(
|
||||||
bubbleSize: MutableState<Int>,
|
|
||||||
availableBubbleSize: MutableState<Int>,
|
|
||||||
firstColumn: @Composable () -> Unit,
|
firstColumn: @Composable () -> Unit,
|
||||||
secondColumn: @Composable () -> Unit,
|
secondColumn: @Composable () -> Unit,
|
||||||
) {
|
) {
|
||||||
Row(
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
horizontalArrangement = Arrangement.SpaceBetween,
|
|
||||||
modifier =
|
|
||||||
with(LocalDensity.current) {
|
|
||||||
Modifier
|
|
||||||
.padding(top = Size5dp)
|
|
||||||
.height(Size20dp)
|
|
||||||
.widthIn(
|
|
||||||
bubbleSize.value.toDp(),
|
|
||||||
availableBubbleSize.value.toDp(),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
) {
|
|
||||||
Column(modifier = ReactionRowHeightChat) {
|
|
||||||
Row(
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
modifier = ReactionRowHeightChat,
|
|
||||||
) {
|
|
||||||
firstColumn()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Column(modifier = ReactionRowHeightChat) {
|
|
||||||
Row(
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
modifier = ReactionRowHeightChat,
|
|
||||||
) {
|
|
||||||
secondColumn()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -703,68 +727,56 @@ private fun RenderCreateChannelNote(note: Note) {
|
|||||||
@Composable
|
@Composable
|
||||||
private fun DrawAuthorInfo(
|
private fun DrawAuthorInfo(
|
||||||
baseNote: Note,
|
baseNote: Note,
|
||||||
alignment: Arrangement.Horizontal,
|
|
||||||
loadProfilePicture: Boolean,
|
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
nav: (String) -> Unit,
|
nav: (String) -> Unit,
|
||||||
) {
|
) {
|
||||||
baseNote.author?.let {
|
baseNote.author?.let {
|
||||||
Row(
|
WatchAndDisplayUser(it, accountViewModel, nav)
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
horizontalArrangement = alignment,
|
|
||||||
modifier =
|
|
||||||
Modifier
|
|
||||||
.padding(top = Size10dp)
|
|
||||||
.clickable {
|
|
||||||
nav("User/${baseNote.author?.pubkeyHex}")
|
|
||||||
},
|
|
||||||
) {
|
|
||||||
WatchAndDisplayUser(it, loadProfilePicture, accountViewModel, nav)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun UserDisplayNameLayout(
|
||||||
|
picture: @Composable () -> Unit,
|
||||||
|
name: @Composable () -> Unit,
|
||||||
|
) {
|
||||||
|
Box(chatAuthorBox, contentAlignment = Alignment.TopEnd) {
|
||||||
|
picture()
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = StdHorzSpacer)
|
||||||
|
|
||||||
|
name()
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun WatchAndDisplayUser(
|
private fun WatchAndDisplayUser(
|
||||||
author: User,
|
author: User,
|
||||||
loadProfilePicture: Boolean,
|
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
nav: (String) -> Unit,
|
nav: (String) -> Unit,
|
||||||
) {
|
) {
|
||||||
val userState by author.live().userMetadataInfo.observeAsState()
|
val userState by author.live().userMetadataInfo.observeAsState()
|
||||||
|
|
||||||
Box(chatAuthorBox, contentAlignment = Alignment.TopEnd) {
|
UserDisplayNameLayout(
|
||||||
InnerUserPicture(
|
picture = {
|
||||||
userHex = author.pubkeyHex,
|
InnerUserPicture(
|
||||||
userPicture = userState?.picture,
|
userHex = author.pubkeyHex,
|
||||||
userName = userState?.bestName(),
|
userPicture = userState?.picture,
|
||||||
size = Size20dp,
|
userName = userState?.bestName(),
|
||||||
modifier = Modifier,
|
size = Size20dp,
|
||||||
accountViewModel = accountViewModel,
|
modifier = Modifier,
|
||||||
)
|
accountViewModel = accountViewModel,
|
||||||
|
)
|
||||||
|
|
||||||
ObserveAndDisplayFollowingMark(author.pubkeyHex, Size5dp, accountViewModel)
|
ObserveAndDisplayFollowingMark(author.pubkeyHex, Size5dp, accountViewModel)
|
||||||
}
|
},
|
||||||
|
name = {
|
||||||
if (userState != null) {
|
if (userState != null) {
|
||||||
DisplayMessageUsername(userState?.bestName() ?: author.pubkeyDisplayHex(), userState?.tags ?: EmptyTagList)
|
DisplayMessageUsername(userState?.bestName() ?: author.pubkeyDisplayHex(), userState?.tags ?: EmptyTagList)
|
||||||
} else {
|
} else {
|
||||||
DisplayMessageUsername(author.pubkeyDisplayHex(), EmptyTagList)
|
DisplayMessageUsername(author.pubkeyDisplayHex(), EmptyTagList)
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun UserIcon(
|
|
||||||
pubkeyHex: String,
|
|
||||||
userProfilePicture: String?,
|
|
||||||
loadProfilePicture: Boolean,
|
|
||||||
) {
|
|
||||||
RobohashFallbackAsyncImage(
|
|
||||||
robot = pubkeyHex,
|
|
||||||
model = userProfilePicture,
|
|
||||||
contentDescription = stringResource(id = R.string.profile_image),
|
|
||||||
loadProfilePicture = loadProfilePicture,
|
|
||||||
modifier = chatAuthorImage,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -773,7 +785,6 @@ private fun DisplayMessageUsername(
|
|||||||
userDisplayName: String,
|
userDisplayName: String,
|
||||||
userTags: ImmutableListOfLists<String>,
|
userTags: ImmutableListOfLists<String>,
|
||||||
) {
|
) {
|
||||||
Spacer(modifier = StdHorzSpacer)
|
|
||||||
CreateTextWithEmoji(
|
CreateTextWithEmoji(
|
||||||
text = userDisplayName,
|
text = userDisplayName,
|
||||||
tags = userTags,
|
tags = userTags,
|
||||||
|
|||||||
@@ -303,8 +303,8 @@ fun AcceptableNote(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
is BadgeDefinitionEvent -> BadgeDisplay(baseNote = baseNote)
|
is BadgeDefinitionEvent -> BadgeDisplay(baseNote = baseNote)
|
||||||
is FileHeaderEvent -> FileHeaderDisplay(baseNote, false, accountViewModel)
|
is FileHeaderEvent -> FileHeaderDisplay(baseNote, false, false, accountViewModel)
|
||||||
is FileStorageHeaderEvent -> FileStorageHeaderDisplay(baseNote, false, accountViewModel)
|
is FileStorageHeaderEvent -> FileStorageHeaderDisplay(baseNote, false, false, accountViewModel)
|
||||||
else ->
|
else ->
|
||||||
LongPressToQuickAction(baseNote = baseNote, accountViewModel = accountViewModel) { showPopup ->
|
LongPressToQuickAction(baseNote = baseNote, accountViewModel = accountViewModel) { showPopup ->
|
||||||
CheckNewAndRenderNote(
|
CheckNewAndRenderNote(
|
||||||
@@ -587,8 +587,8 @@ private fun RenderNoteRow(
|
|||||||
val noteEvent = baseNote.event
|
val noteEvent = baseNote.event
|
||||||
when (noteEvent) {
|
when (noteEvent) {
|
||||||
is AppDefinitionEvent -> RenderAppDefinition(baseNote, accountViewModel, nav)
|
is AppDefinitionEvent -> RenderAppDefinition(baseNote, accountViewModel, nav)
|
||||||
is AudioTrackEvent -> RenderAudioTrack(baseNote, accountViewModel, nav)
|
is AudioTrackEvent -> RenderAudioTrack(baseNote, false, accountViewModel, nav)
|
||||||
is AudioHeaderEvent -> RenderAudioHeader(baseNote, accountViewModel, nav)
|
is AudioHeaderEvent -> RenderAudioHeader(baseNote, false, accountViewModel, nav)
|
||||||
is DraftEvent -> RenderDraft(baseNote, quotesLeft, unPackReply, backgroundColor, accountViewModel, nav)
|
is DraftEvent -> RenderDraft(baseNote, quotesLeft, unPackReply, backgroundColor, accountViewModel, nav)
|
||||||
is ReactionEvent -> RenderReaction(baseNote, quotesLeft, backgroundColor, accountViewModel, nav)
|
is ReactionEvent -> RenderReaction(baseNote, quotesLeft, backgroundColor, accountViewModel, nav)
|
||||||
is RepostEvent -> RenderRepost(baseNote, quotesLeft, backgroundColor, accountViewModel, nav)
|
is RepostEvent -> RenderRepost(baseNote, quotesLeft, backgroundColor, accountViewModel, nav)
|
||||||
@@ -706,10 +706,10 @@ private fun RenderNoteRow(
|
|||||||
nav,
|
nav,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
is FileHeaderEvent -> FileHeaderDisplay(baseNote, true, accountViewModel)
|
is FileHeaderEvent -> FileHeaderDisplay(baseNote, true, false, accountViewModel)
|
||||||
is VideoHorizontalEvent -> VideoDisplay(baseNote, makeItShort, canPreview, backgroundColor, accountViewModel, nav)
|
is VideoHorizontalEvent -> VideoDisplay(baseNote, makeItShort, canPreview, backgroundColor, false, accountViewModel, nav)
|
||||||
is VideoVerticalEvent -> VideoDisplay(baseNote, makeItShort, canPreview, backgroundColor, accountViewModel, nav)
|
is VideoVerticalEvent -> VideoDisplay(baseNote, makeItShort, canPreview, backgroundColor, false, accountViewModel, nav)
|
||||||
is FileStorageHeaderEvent -> FileStorageHeaderDisplay(baseNote, true, accountViewModel)
|
is FileStorageHeaderEvent -> FileStorageHeaderDisplay(baseNote, true, false, accountViewModel)
|
||||||
is CommunityPostApprovalEvent -> {
|
is CommunityPostApprovalEvent -> {
|
||||||
RenderPostApproval(
|
RenderPostApproval(
|
||||||
baseNote,
|
baseNote,
|
||||||
|
|||||||
@@ -60,18 +60,20 @@ import java.util.Locale
|
|||||||
@Composable
|
@Composable
|
||||||
fun RenderAudioTrack(
|
fun RenderAudioTrack(
|
||||||
note: Note,
|
note: Note,
|
||||||
|
isFiniteHeight: Boolean,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
nav: (String) -> Unit,
|
nav: (String) -> Unit,
|
||||||
) {
|
) {
|
||||||
val noteEvent = note.event as? AudioTrackEvent ?: return
|
val noteEvent = note.event as? AudioTrackEvent ?: return
|
||||||
|
|
||||||
AudioTrackHeader(noteEvent, note, accountViewModel, nav)
|
AudioTrackHeader(noteEvent, note, isFiniteHeight, accountViewModel, nav)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun AudioTrackHeader(
|
fun AudioTrackHeader(
|
||||||
noteEvent: AudioTrackEvent,
|
noteEvent: AudioTrackEvent,
|
||||||
note: Note,
|
note: Note,
|
||||||
|
isFiniteHeight: Boolean,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
nav: (String) -> Unit,
|
nav: (String) -> Unit,
|
||||||
) {
|
) {
|
||||||
@@ -144,17 +146,20 @@ fun AudioTrackHeader(
|
|||||||
thumbUri = cover,
|
thumbUri = cover,
|
||||||
authorName = note.author?.toBestDisplayName(),
|
authorName = note.author?.toBestDisplayName(),
|
||||||
roundedCorner = true,
|
roundedCorner = true,
|
||||||
|
isFiniteHeight = isFiniteHeight,
|
||||||
nostrUriCallback = "nostr:${note.toNEvent()}",
|
nostrUriCallback = "nostr:${note.toNEvent()}",
|
||||||
accountViewModel = accountViewModel,
|
accountViewModel = accountViewModel,
|
||||||
)
|
)
|
||||||
}
|
} ?: run {
|
||||||
?: VideoView(
|
VideoView(
|
||||||
videoUri = media,
|
videoUri = media,
|
||||||
title = noteEvent.subject(),
|
title = noteEvent.subject(),
|
||||||
authorName = note.author?.toBestDisplayName(),
|
authorName = note.author?.toBestDisplayName(),
|
||||||
roundedCorner = true,
|
roundedCorner = true,
|
||||||
|
isFiniteHeight = isFiniteHeight,
|
||||||
accountViewModel = accountViewModel,
|
accountViewModel = accountViewModel,
|
||||||
)
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -164,18 +169,20 @@ fun AudioTrackHeader(
|
|||||||
@Composable
|
@Composable
|
||||||
fun RenderAudioHeader(
|
fun RenderAudioHeader(
|
||||||
note: Note,
|
note: Note,
|
||||||
|
isFiniteHeight: Boolean,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
nav: (String) -> Unit,
|
nav: (String) -> Unit,
|
||||||
) {
|
) {
|
||||||
val noteEvent = note.event as? AudioHeaderEvent ?: return
|
val noteEvent = note.event as? AudioHeaderEvent ?: return
|
||||||
|
|
||||||
AudioHeader(noteEvent, note, accountViewModel, nav)
|
AudioHeader(noteEvent, note, isFiniteHeight, accountViewModel, nav)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun AudioHeader(
|
fun AudioHeader(
|
||||||
noteEvent: AudioHeaderEvent,
|
noteEvent: AudioHeaderEvent,
|
||||||
note: Note,
|
note: Note,
|
||||||
|
isFiniteHeight: Boolean,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
nav: (String) -> Unit,
|
nav: (String) -> Unit,
|
||||||
) {
|
) {
|
||||||
@@ -199,6 +206,7 @@ fun AudioHeader(
|
|||||||
title = noteEvent.subject(),
|
title = noteEvent.subject(),
|
||||||
authorName = note.author?.toBestDisplayName(),
|
authorName = note.author?.toBestDisplayName(),
|
||||||
roundedCorner = true,
|
roundedCorner = true,
|
||||||
|
isFiniteHeight = isFiniteHeight,
|
||||||
accountViewModel = accountViewModel,
|
accountViewModel = accountViewModel,
|
||||||
nostrUriCallback = note.toNostrUri(),
|
nostrUriCallback = note.toNostrUri(),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ import com.vitorpamplona.quartz.events.FileHeaderEvent
|
|||||||
fun FileHeaderDisplay(
|
fun FileHeaderDisplay(
|
||||||
note: Note,
|
note: Note,
|
||||||
roundedCorner: Boolean,
|
roundedCorner: Boolean,
|
||||||
|
isFiniteHeight: Boolean,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
) {
|
) {
|
||||||
val event = (note.event as? FileHeaderEvent) ?: return
|
val event = (note.event as? FileHeaderEvent) ?: return
|
||||||
@@ -80,6 +81,7 @@ fun FileHeaderDisplay(
|
|||||||
ZoomableContentView(
|
ZoomableContentView(
|
||||||
content = content,
|
content = content,
|
||||||
roundedCorner = roundedCorner,
|
roundedCorner = roundedCorner,
|
||||||
|
isFiniteHeight = isFiniteHeight,
|
||||||
accountViewModel = accountViewModel,
|
accountViewModel = accountViewModel,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ import java.io.File
|
|||||||
fun FileStorageHeaderDisplay(
|
fun FileStorageHeaderDisplay(
|
||||||
baseNote: Note,
|
baseNote: Note,
|
||||||
roundedCorner: Boolean,
|
roundedCorner: Boolean,
|
||||||
|
isFiniteHeight: Boolean,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
) {
|
) {
|
||||||
val eventHeader = (baseNote.event as? FileStorageHeaderEvent) ?: return
|
val eventHeader = (baseNote.event as? FileStorageHeaderEvent) ?: return
|
||||||
@@ -49,7 +50,7 @@ fun FileStorageHeaderDisplay(
|
|||||||
|
|
||||||
LoadNote(baseNoteHex = dataEventId, accountViewModel) { contentNote ->
|
LoadNote(baseNoteHex = dataEventId, accountViewModel) { contentNote ->
|
||||||
if (contentNote != null) {
|
if (contentNote != null) {
|
||||||
ObserverAndRenderNIP95(baseNote, contentNote, roundedCorner, accountViewModel)
|
ObserverAndRenderNIP95(baseNote, contentNote, roundedCorner, isFiniteHeight, accountViewModel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -59,6 +60,7 @@ private fun ObserverAndRenderNIP95(
|
|||||||
header: Note,
|
header: Note,
|
||||||
content: Note,
|
content: Note,
|
||||||
roundedCorner: Boolean,
|
roundedCorner: Boolean,
|
||||||
|
isFiniteHeight: Boolean,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
) {
|
) {
|
||||||
val eventHeader = (header.event as? FileStorageHeaderEvent) ?: return
|
val eventHeader = (header.event as? FileStorageHeaderEvent) ?: return
|
||||||
@@ -110,6 +112,7 @@ private fun ObserverAndRenderNIP95(
|
|||||||
ZoomableContentView(
|
ZoomableContentView(
|
||||||
content = it,
|
content = it,
|
||||||
roundedCorner = roundedCorner,
|
roundedCorner = roundedCorner,
|
||||||
|
isFiniteHeight = isFiniteHeight,
|
||||||
accountViewModel = accountViewModel,
|
accountViewModel = accountViewModel,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -156,6 +156,7 @@ fun RenderLiveActivityEventInner(
|
|||||||
artworkUri = cover,
|
artworkUri = cover,
|
||||||
authorName = baseNote.author?.toBestDisplayName(),
|
authorName = baseNote.author?.toBestDisplayName(),
|
||||||
roundedCorner = true,
|
roundedCorner = true,
|
||||||
|
isFiniteHeight = false,
|
||||||
accountViewModel = accountViewModel,
|
accountViewModel = accountViewModel,
|
||||||
nostrUriCallback = "nostr:${baseNote.toNEvent()}",
|
nostrUriCallback = "nostr:${baseNote.toNEvent()}",
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ fun VideoDisplay(
|
|||||||
makeItShort: Boolean,
|
makeItShort: Boolean,
|
||||||
canPreview: Boolean,
|
canPreview: Boolean,
|
||||||
backgroundColor: MutableState<Color>,
|
backgroundColor: MutableState<Color>,
|
||||||
|
isFiniteHeight: Boolean,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
nav: (String) -> Unit,
|
nav: (String) -> Unit,
|
||||||
) {
|
) {
|
||||||
@@ -143,6 +144,7 @@ fun VideoDisplay(
|
|||||||
ZoomableContentView(
|
ZoomableContentView(
|
||||||
content = content,
|
content = content,
|
||||||
roundedCorner = true,
|
roundedCorner = true,
|
||||||
|
isFiniteHeight = isFiniteHeight,
|
||||||
accountViewModel = accountViewModel,
|
accountViewModel = accountViewModel,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -504,17 +504,17 @@ fun NoteMaster(
|
|||||||
nav = nav,
|
nav = nav,
|
||||||
)
|
)
|
||||||
} else if (noteEvent is VideoEvent) {
|
} else if (noteEvent is VideoEvent) {
|
||||||
VideoDisplay(baseNote, false, true, backgroundColor, accountViewModel, nav)
|
VideoDisplay(baseNote, false, true, backgroundColor, false, accountViewModel, nav)
|
||||||
} else if (noteEvent is FileHeaderEvent) {
|
} else if (noteEvent is FileHeaderEvent) {
|
||||||
FileHeaderDisplay(baseNote, true, accountViewModel)
|
FileHeaderDisplay(baseNote, true, false, accountViewModel)
|
||||||
} else if (noteEvent is FileStorageHeaderEvent) {
|
} else if (noteEvent is FileStorageHeaderEvent) {
|
||||||
FileStorageHeaderDisplay(baseNote, true, accountViewModel)
|
FileStorageHeaderDisplay(baseNote, true, false, accountViewModel)
|
||||||
} else if (noteEvent is PeopleListEvent) {
|
} else if (noteEvent is PeopleListEvent) {
|
||||||
DisplayPeopleList(baseNote, backgroundColor, accountViewModel, nav)
|
DisplayPeopleList(baseNote, backgroundColor, accountViewModel, nav)
|
||||||
} else if (noteEvent is AudioTrackEvent) {
|
} else if (noteEvent is AudioTrackEvent) {
|
||||||
AudioTrackHeader(noteEvent, baseNote, accountViewModel, nav)
|
AudioTrackHeader(noteEvent, baseNote, false, accountViewModel, nav)
|
||||||
} else if (noteEvent is AudioHeaderEvent) {
|
} else if (noteEvent is AudioHeaderEvent) {
|
||||||
AudioHeader(noteEvent, baseNote, accountViewModel, nav)
|
AudioHeader(noteEvent, baseNote, false, accountViewModel, nav)
|
||||||
} else if (noteEvent is CommunityPostApprovalEvent) {
|
} else if (noteEvent is CommunityPostApprovalEvent) {
|
||||||
RenderPostApproval(
|
RenderPostApproval(
|
||||||
baseNote,
|
baseNote,
|
||||||
|
|||||||
@@ -743,6 +743,7 @@ fun ShowVideoStreaming(
|
|||||||
ZoomableContentView(
|
ZoomableContentView(
|
||||||
content = zoomableUrlVideo,
|
content = zoomableUrlVideo,
|
||||||
roundedCorner = false,
|
roundedCorner = false,
|
||||||
|
isFiniteHeight = false,
|
||||||
accountViewModel = accountViewModel,
|
accountViewModel = accountViewModel,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -86,7 +86,7 @@ fun ChatroomListScreen(
|
|||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
nav: (String) -> Unit,
|
nav: (String) -> Unit,
|
||||||
) {
|
) {
|
||||||
val windowSizeClass = accountViewModel.settings.windowSizeClass.value
|
val windowSizeClass by accountViewModel.settings.windowSizeClass
|
||||||
|
|
||||||
val twoPane by remember {
|
val twoPane by remember {
|
||||||
derivedStateOf {
|
derivedStateOf {
|
||||||
@@ -104,7 +104,7 @@ fun ChatroomListScreen(
|
|||||||
ChatroomListTwoPane(
|
ChatroomListTwoPane(
|
||||||
knownFeedViewModel = knownFeedViewModel,
|
knownFeedViewModel = knownFeedViewModel,
|
||||||
newFeedViewModel = newFeedViewModel,
|
newFeedViewModel = newFeedViewModel,
|
||||||
widthSizeClass = windowSizeClass.widthSizeClass,
|
widthSizeClass = windowSizeClass!!.widthSizeClass,
|
||||||
accountViewModel = accountViewModel,
|
accountViewModel = accountViewModel,
|
||||||
nav = nav,
|
nav = nav,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -325,12 +325,12 @@ private fun RenderVideoOrPictureNote(
|
|||||||
nav: (String) -> Unit,
|
nav: (String) -> Unit,
|
||||||
) {
|
) {
|
||||||
Column(Modifier.fillMaxSize(1f), verticalArrangement = Arrangement.Center) {
|
Column(Modifier.fillMaxSize(1f), verticalArrangement = Arrangement.Center) {
|
||||||
Row(Modifier.weight(1f), verticalAlignment = Alignment.CenterVertically) {
|
Row(Modifier.weight(1f), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Center) {
|
||||||
val noteEvent = remember { note.event }
|
val noteEvent = remember { note.event }
|
||||||
if (noteEvent is FileHeaderEvent) {
|
if (noteEvent is FileHeaderEvent) {
|
||||||
FileHeaderDisplay(note, false, accountViewModel)
|
FileHeaderDisplay(note, false, true, accountViewModel)
|
||||||
} else if (noteEvent is FileStorageHeaderEvent) {
|
} else if (noteEvent is FileStorageHeaderEvent) {
|
||||||
FileStorageHeaderDisplay(note, false, accountViewModel)
|
FileStorageHeaderDisplay(note, false, true, accountViewModel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -108,6 +108,7 @@ val StdPadding = Modifier.padding(10.dp)
|
|||||||
val BigPadding = Modifier.padding(15.dp)
|
val BigPadding = Modifier.padding(15.dp)
|
||||||
|
|
||||||
val RowColSpacing = Arrangement.spacedBy(3.dp)
|
val RowColSpacing = Arrangement.spacedBy(3.dp)
|
||||||
|
val RowColSpacing5dp = Arrangement.spacedBy(5.dp)
|
||||||
|
|
||||||
val HalfHorzPadding = Modifier.padding(horizontal = 5.dp)
|
val HalfHorzPadding = Modifier.padding(horizontal = 5.dp)
|
||||||
val HalfVertPadding = Modifier.padding(vertical = 5.dp)
|
val HalfVertPadding = Modifier.padding(vertical = 5.dp)
|
||||||
@@ -236,6 +237,9 @@ val chatAuthorBox = Modifier.size(20.dp)
|
|||||||
val chatAuthorImage = Modifier.size(20.dp).clip(shape = CircleShape)
|
val chatAuthorImage = Modifier.size(20.dp).clip(shape = CircleShape)
|
||||||
val AuthorInfoVideoFeed = Modifier.width(75.dp).padding(end = 15.dp)
|
val AuthorInfoVideoFeed = Modifier.width(75.dp).padding(end = 15.dp)
|
||||||
|
|
||||||
|
val messageDetailsModifier = Modifier.height(Size25dp)
|
||||||
|
val messageBubbleLimits = Modifier.padding(start = 10.dp, end = 10.dp, bottom = 5.dp)
|
||||||
|
|
||||||
val inlinePlaceholder =
|
val inlinePlaceholder =
|
||||||
Placeholder(
|
Placeholder(
|
||||||
width = Font17SP,
|
width = Font17SP,
|
||||||
@@ -247,3 +251,5 @@ val incognitoIconModifier =
|
|||||||
Modifier
|
Modifier
|
||||||
.padding(top = 1.dp)
|
.padding(top = 1.dp)
|
||||||
.size(14.dp)
|
.size(14.dp)
|
||||||
|
|
||||||
|
val hashVerifierMark = Modifier.width(40.dp).height(40.dp).padding(10.dp)
|
||||||
|
|||||||
Reference in New Issue
Block a user