Split chat overlays to fix ART VerifyError
This commit is contained in:
@@ -1958,318 +1958,362 @@ fun ChatScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (viewerVideoUrl != null) {
|
if (viewerVideoUrl != null) {
|
||||||
val videoUrl = viewerVideoUrl.orEmpty()
|
VideoViewerOverlay(
|
||||||
var videoViewRef by remember(videoUrl) { mutableStateOf<VideoView?>(null) }
|
videoUrl = viewerVideoUrl.orEmpty(),
|
||||||
var videoPrepared by remember(videoUrl) { mutableStateOf(false) }
|
onDismiss = { viewerVideoUrl = null },
|
||||||
var videoDurationMs by remember(videoUrl) { mutableStateOf(0) }
|
)
|
||||||
var videoPositionMs by remember(videoUrl) { mutableStateOf(0) }
|
}
|
||||||
var videoPlaying by remember(videoUrl) { mutableStateOf(true) }
|
if (showEmojiPicker) {
|
||||||
var isVideoSeeking by remember(videoUrl) { mutableStateOf(false) }
|
EmojiPickerSheet(
|
||||||
var videoSeekFraction by remember(videoUrl) { mutableStateOf(0f) }
|
sheetState = actionSheetState,
|
||||||
|
emojiPickerTab = emojiPickerTab,
|
||||||
|
onEmojiPickerTabChange = { emojiPickerTab = it },
|
||||||
|
gifSearchQuery = gifSearchQuery,
|
||||||
|
onGifSearchQueryChange = { gifSearchQuery = it },
|
||||||
|
giphySearchItems = giphySearchItems,
|
||||||
|
isGiphyLoading = isGiphyLoading,
|
||||||
|
giphyErrorMessage = giphyErrorMessage,
|
||||||
|
onDismiss = { showEmojiPicker = false },
|
||||||
|
onEmojiSelected = { emoji ->
|
||||||
|
composerValue = composerValue.insertAtCursor(emoji)
|
||||||
|
onInputChanged(composerValue.text)
|
||||||
|
},
|
||||||
|
onSendPresetMediaUrl = onSendPresetMediaUrl,
|
||||||
|
onSendRemoteMedia = onSendRemoteMedia,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
DisposableEffect(videoUrl) {
|
@Composable
|
||||||
onDispose {
|
private fun VideoViewerOverlay(
|
||||||
runCatching { videoViewRef?.stopPlayback() }
|
videoUrl: String,
|
||||||
videoViewRef = null
|
onDismiss: () -> Unit,
|
||||||
}
|
) {
|
||||||
}
|
var videoViewRef by remember(videoUrl) { mutableStateOf<VideoView?>(null) }
|
||||||
|
var videoPrepared by remember(videoUrl) { mutableStateOf(false) }
|
||||||
|
var videoDurationMs by remember(videoUrl) { mutableStateOf(0) }
|
||||||
|
var videoPositionMs by remember(videoUrl) { mutableStateOf(0) }
|
||||||
|
var videoPlaying by remember(videoUrl) { mutableStateOf(true) }
|
||||||
|
var isVideoSeeking by remember(videoUrl) { mutableStateOf(false) }
|
||||||
|
var videoSeekFraction by remember(videoUrl) { mutableStateOf(0f) }
|
||||||
|
|
||||||
LaunchedEffect(videoPlaying, videoPrepared, isVideoSeeking, videoUrl) {
|
DisposableEffect(videoUrl) {
|
||||||
if (!videoPlaying || !videoPrepared || isVideoSeeking) return@LaunchedEffect
|
onDispose {
|
||||||
while (videoPlaying && viewerVideoUrl == videoUrl && !isVideoSeeking) {
|
runCatching { videoViewRef?.stopPlayback() }
|
||||||
videoPositionMs = runCatching { videoViewRef?.currentPosition ?: videoPositionMs }
|
videoViewRef = null
|
||||||
.getOrDefault(videoPositionMs)
|
}
|
||||||
delay(250)
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Surface(
|
LaunchedEffect(videoPlaying, videoPrepared, isVideoSeeking, videoUrl) {
|
||||||
|
if (!videoPlaying || !videoPrepared || isVideoSeeking) return@LaunchedEffect
|
||||||
|
while (videoPlaying && !isVideoSeeking) {
|
||||||
|
videoPositionMs = runCatching { videoViewRef?.currentPosition ?: videoPositionMs }
|
||||||
|
.getOrDefault(videoPositionMs)
|
||||||
|
delay(250)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Surface(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.background(MaterialTheme.colorScheme.scrim.copy(alpha = 0.78f)),
|
||||||
|
color = Color.Black.copy(alpha = 0.9f),
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
verticalArrangement = Arrangement.SpaceBetween,
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxWidth()
|
||||||
.background(MaterialTheme.colorScheme.scrim.copy(alpha = 0.78f)),
|
.padding(horizontal = 8.dp, vertical = 8.dp),
|
||||||
color = Color.Black.copy(alpha = 0.9f),
|
horizontalArrangement = Arrangement.End,
|
||||||
) {
|
) {
|
||||||
Column(
|
IconButton(onClick = onDismiss) {
|
||||||
modifier = Modifier.fillMaxSize(),
|
Icon(imageVector = Icons.Filled.Close, contentDescription = "Close video viewer")
|
||||||
verticalArrangement = Arrangement.SpaceBetween,
|
}
|
||||||
|
}
|
||||||
|
AndroidView(
|
||||||
|
factory = { context ->
|
||||||
|
VideoView(context).apply {
|
||||||
|
setVideoPath(videoUrl)
|
||||||
|
videoViewRef = this
|
||||||
|
setOnPreparedListener { player ->
|
||||||
|
player.isLooping = false
|
||||||
|
videoPrepared = true
|
||||||
|
videoDurationMs = runCatching { duration }.getOrDefault(player.duration.coerceAtLeast(0))
|
||||||
|
start()
|
||||||
|
videoPlaying = true
|
||||||
|
}
|
||||||
|
setOnCompletionListener {
|
||||||
|
videoPlaying = false
|
||||||
|
videoPositionMs = videoDurationMs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
update = { view ->
|
||||||
|
videoViewRef = view
|
||||||
|
if (videoPlaying && videoPrepared && !isVideoSeeking && !view.isPlaying) {
|
||||||
|
runCatching { view.start() }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.weight(1f)
|
||||||
|
.padding(horizontal = 10.dp),
|
||||||
|
)
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 14.dp, vertical = 10.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
Row(
|
IconButton(
|
||||||
modifier = Modifier
|
onClick = {
|
||||||
.fillMaxWidth()
|
val view = videoViewRef ?: return@IconButton
|
||||||
.padding(horizontal = 8.dp, vertical = 8.dp),
|
if (videoPlaying) {
|
||||||
horizontalArrangement = Arrangement.End,
|
runCatching { view.pause() }
|
||||||
) {
|
videoPlaying = false
|
||||||
IconButton(onClick = { viewerVideoUrl = null }) {
|
} else {
|
||||||
Icon(imageVector = Icons.Filled.Close, contentDescription = "Close video viewer")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
AndroidView(
|
|
||||||
factory = { context ->
|
|
||||||
VideoView(context).apply {
|
|
||||||
setVideoPath(videoUrl)
|
|
||||||
videoViewRef = this
|
|
||||||
setOnPreparedListener { player ->
|
|
||||||
player.isLooping = false
|
|
||||||
videoPrepared = true
|
|
||||||
videoDurationMs = runCatching { duration }.getOrDefault(player.duration.coerceAtLeast(0))
|
|
||||||
start()
|
|
||||||
videoPlaying = true
|
|
||||||
}
|
|
||||||
setOnCompletionListener {
|
|
||||||
videoPlaying = false
|
|
||||||
videoPositionMs = videoDurationMs
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
update = { view ->
|
|
||||||
videoViewRef = view
|
|
||||||
if (videoPlaying && videoPrepared && !isVideoSeeking && !view.isPlaying) {
|
|
||||||
runCatching { view.start() }
|
runCatching { view.start() }
|
||||||
|
videoPlaying = true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
modifier = Modifier
|
enabled = videoPrepared,
|
||||||
.fillMaxWidth()
|
|
||||||
.weight(1f)
|
|
||||||
.padding(horizontal = 10.dp),
|
|
||||||
)
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(horizontal = 14.dp, vertical = 10.dp),
|
|
||||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
|
||||||
) {
|
) {
|
||||||
Row(
|
Icon(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
imageVector = if (videoPlaying) Icons.Filled.Pause else Icons.Filled.PlayArrow,
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
contentDescription = if (videoPlaying) "Pause video" else "Play video",
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
tint = Color.White,
|
||||||
) {
|
|
||||||
IconButton(
|
|
||||||
onClick = {
|
|
||||||
val view = videoViewRef ?: return@IconButton
|
|
||||||
if (videoPlaying) {
|
|
||||||
runCatching { view.pause() }
|
|
||||||
videoPlaying = false
|
|
||||||
} else {
|
|
||||||
runCatching { view.start() }
|
|
||||||
videoPlaying = true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
enabled = videoPrepared,
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
imageVector = if (videoPlaying) Icons.Filled.Pause else Icons.Filled.PlayArrow,
|
|
||||||
contentDescription = if (videoPlaying) "Pause video" else "Play video",
|
|
||||||
tint = Color.White,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Text(
|
|
||||||
text = extractFileName(videoUrl),
|
|
||||||
modifier = Modifier.weight(1f),
|
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
|
||||||
color = Color.White,
|
|
||||||
maxLines = 1,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
val videoProgress = if (videoDurationMs <= 0) 0f else {
|
|
||||||
(videoPositionMs.toFloat() / videoDurationMs.toFloat()).coerceIn(0f, 1f)
|
|
||||||
}
|
|
||||||
Slider(
|
|
||||||
value = if (isVideoSeeking) videoSeekFraction else videoProgress,
|
|
||||||
onValueChange = { fraction ->
|
|
||||||
isVideoSeeking = true
|
|
||||||
videoSeekFraction = fraction.coerceIn(0f, 1f)
|
|
||||||
},
|
|
||||||
onValueChangeFinished = {
|
|
||||||
val targetMs = (videoDurationMs * videoSeekFraction).roundToInt()
|
|
||||||
runCatching { videoViewRef?.seekTo(targetMs) }
|
|
||||||
videoPositionMs = targetMs
|
|
||||||
isVideoSeeking = false
|
|
||||||
},
|
|
||||||
enabled = videoPrepared && videoDurationMs > 0,
|
|
||||||
)
|
)
|
||||||
Row(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
horizontalArrangement = Arrangement.SpaceBetween,
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = formatDuration(videoPositionMs),
|
|
||||||
style = MaterialTheme.typography.labelSmall,
|
|
||||||
color = Color.White.copy(alpha = 0.85f),
|
|
||||||
)
|
|
||||||
Text(
|
|
||||||
text = if (videoDurationMs > 0) formatDuration(videoDurationMs) else "--:--",
|
|
||||||
style = MaterialTheme.typography.labelSmall,
|
|
||||||
color = Color.White.copy(alpha = 0.85f),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Text(
|
||||||
|
text = extractFileName(videoUrl),
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = Color.White,
|
||||||
|
maxLines = 1,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val videoProgress = if (videoDurationMs <= 0) 0f else {
|
||||||
|
(videoPositionMs.toFloat() / videoDurationMs.toFloat()).coerceIn(0f, 1f)
|
||||||
|
}
|
||||||
|
Slider(
|
||||||
|
value = if (isVideoSeeking) videoSeekFraction else videoProgress,
|
||||||
|
onValueChange = { fraction ->
|
||||||
|
isVideoSeeking = true
|
||||||
|
videoSeekFraction = fraction.coerceIn(0f, 1f)
|
||||||
|
},
|
||||||
|
onValueChangeFinished = {
|
||||||
|
val targetMs = (videoDurationMs * videoSeekFraction).roundToInt()
|
||||||
|
runCatching { videoViewRef?.seekTo(targetMs) }
|
||||||
|
videoPositionMs = targetMs
|
||||||
|
isVideoSeeking = false
|
||||||
|
},
|
||||||
|
enabled = videoPrepared && videoDurationMs > 0,
|
||||||
|
)
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = formatDuration(videoPositionMs),
|
||||||
|
style = MaterialTheme.typography.labelSmall,
|
||||||
|
color = Color.White.copy(alpha = 0.85f),
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = if (videoDurationMs > 0) formatDuration(videoDurationMs) else "--:--",
|
||||||
|
style = MaterialTheme.typography.labelSmall,
|
||||||
|
color = Color.White.copy(alpha = 0.85f),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (showEmojiPicker) {
|
}
|
||||||
ModalBottomSheet(
|
}
|
||||||
onDismissRequest = { if (!isPickerSending) showEmojiPicker = false },
|
|
||||||
sheetState = actionSheetState,
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
) {
|
@Composable
|
||||||
Column(
|
private fun EmojiPickerSheet(
|
||||||
modifier = Modifier
|
sheetState: androidx.compose.material3.SheetState,
|
||||||
.fillMaxWidth()
|
emojiPickerTab: ComposerPickerTab,
|
||||||
.padding(horizontal = 12.dp, vertical = 8.dp),
|
onEmojiPickerTabChange: (ComposerPickerTab) -> Unit,
|
||||||
verticalArrangement = Arrangement.spacedBy(10.dp),
|
gifSearchQuery: String,
|
||||||
) {
|
onGifSearchQueryChange: (String) -> Unit,
|
||||||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
giphySearchItems: List<RemotePickerItem>,
|
||||||
ComposerPickerTab.entries.forEach { tab ->
|
isGiphyLoading: Boolean,
|
||||||
val selected = emojiPickerTab == tab
|
giphyErrorMessage: String?,
|
||||||
|
onDismiss: () -> Unit,
|
||||||
|
onEmojiSelected: (String) -> Unit,
|
||||||
|
onSendPresetMediaUrl: (String) -> Unit,
|
||||||
|
onSendRemoteMedia: (String, String, ByteArray) -> Unit,
|
||||||
|
) {
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
var isPickerSending by remember { mutableStateOf(false) }
|
||||||
|
ModalBottomSheet(
|
||||||
|
onDismissRequest = { if (!isPickerSending) onDismiss() },
|
||||||
|
sheetState = sheetState,
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 12.dp, vertical = 8.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(10.dp),
|
||||||
|
) {
|
||||||
|
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||||
|
ComposerPickerTab.entries.forEach { tab ->
|
||||||
|
val selected = emojiPickerTab == tab
|
||||||
|
Surface(
|
||||||
|
shape = RoundedCornerShape(16.dp),
|
||||||
|
color = if (selected) {
|
||||||
|
MaterialTheme.colorScheme.primary.copy(alpha = 0.22f)
|
||||||
|
} else {
|
||||||
|
MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.52f)
|
||||||
|
},
|
||||||
|
modifier = Modifier
|
||||||
|
.clip(RoundedCornerShape(16.dp))
|
||||||
|
.clickable { onEmojiPickerTabChange(tab) },
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(id = tab.titleRes),
|
||||||
|
modifier = Modifier.padding(horizontal = 12.dp, vertical = 8.dp),
|
||||||
|
style = MaterialTheme.typography.labelLarge,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
when (emojiPickerTab) {
|
||||||
|
ComposerPickerTab.Emoji -> {
|
||||||
|
val emojiSet = listOf(
|
||||||
|
"😀", "😁", "😂", "🤣", "😊", "😍", "😘", "😎",
|
||||||
|
"🤔", "🙃", "😴", "😡", "🥳", "😭", "👍", "👎",
|
||||||
|
"🔥", "❤️", "💔", "🙏", "🎉", "🤝", "💯", "✅",
|
||||||
|
)
|
||||||
|
LazyVerticalGrid(
|
||||||
|
columns = GridCells.Fixed(8),
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(240.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(6.dp),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(6.dp),
|
||||||
|
) {
|
||||||
|
items(emojiSet) { emoji ->
|
||||||
Surface(
|
Surface(
|
||||||
shape = RoundedCornerShape(16.dp),
|
shape = RoundedCornerShape(10.dp),
|
||||||
color = if (selected) {
|
color = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.45f),
|
||||||
MaterialTheme.colorScheme.primary.copy(alpha = 0.22f)
|
|
||||||
} else {
|
|
||||||
MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.52f)
|
|
||||||
},
|
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.clip(RoundedCornerShape(16.dp))
|
.clip(RoundedCornerShape(10.dp))
|
||||||
.clickable { emojiPickerTab = tab },
|
.clickable { onEmojiSelected(emoji) },
|
||||||
) {
|
) {
|
||||||
Text(
|
Box(modifier = Modifier.padding(vertical = 8.dp), contentAlignment = Alignment.Center) {
|
||||||
text = stringResource(id = tab.titleRes),
|
Text(text = emoji)
|
||||||
modifier = Modifier.padding(horizontal = 12.dp, vertical = 8.dp),
|
|
||||||
style = MaterialTheme.typography.labelLarge,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
when (emojiPickerTab) {
|
|
||||||
ComposerPickerTab.Emoji -> {
|
|
||||||
val emojiSet = listOf(
|
|
||||||
"😀", "😁", "😂", "🤣", "😊", "😍", "😘", "😎",
|
|
||||||
"🤔", "🙃", "😴", "😡", "🥳", "😭", "👍", "👎",
|
|
||||||
"🔥", "❤️", "💔", "🙏", "🎉", "🤝", "💯", "✅",
|
|
||||||
)
|
|
||||||
LazyVerticalGrid(
|
|
||||||
columns = GridCells.Fixed(8),
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.height(240.dp),
|
|
||||||
verticalArrangement = Arrangement.spacedBy(6.dp),
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(6.dp),
|
|
||||||
) {
|
|
||||||
items(emojiSet) { emoji ->
|
|
||||||
Surface(
|
|
||||||
shape = RoundedCornerShape(10.dp),
|
|
||||||
color = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.45f),
|
|
||||||
modifier = Modifier
|
|
||||||
.clip(RoundedCornerShape(10.dp))
|
|
||||||
.clickable {
|
|
||||||
composerValue = composerValue.insertAtCursor(emoji)
|
|
||||||
onInputChanged(composerValue.text)
|
|
||||||
},
|
|
||||||
) {
|
|
||||||
Box(modifier = Modifier.padding(vertical = 8.dp), contentAlignment = Alignment.Center) {
|
|
||||||
Text(text = emoji)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ComposerPickerTab.Gif,
|
ComposerPickerTab.Gif,
|
||||||
ComposerPickerTab.Sticker,
|
ComposerPickerTab.Sticker,
|
||||||
-> {
|
-> {
|
||||||
val remoteItems = if (emojiPickerTab == ComposerPickerTab.Gif) {
|
val remoteItems = if (emojiPickerTab == ComposerPickerTab.Gif) {
|
||||||
if (gifSearchQuery.isBlank()) defaultGifItems else giphySearchItems
|
if (gifSearchQuery.isBlank()) defaultGifItems else giphySearchItems
|
||||||
} else {
|
} else {
|
||||||
defaultStickerItems
|
defaultStickerItems
|
||||||
}
|
}
|
||||||
Column(
|
Column(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
) {
|
||||||
|
if (emojiPickerTab == ComposerPickerTab.Gif) {
|
||||||
|
OutlinedTextField(
|
||||||
|
value = gifSearchQuery,
|
||||||
|
onValueChange = onGifSearchQueryChange,
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
singleLine = true,
|
||||||
) {
|
placeholder = { Text(stringResource(id = R.string.chat_search_gifs)) },
|
||||||
if (emojiPickerTab == ComposerPickerTab.Gif) {
|
)
|
||||||
OutlinedTextField(
|
when {
|
||||||
value = gifSearchQuery,
|
isGiphyLoading -> {
|
||||||
onValueChange = { gifSearchQuery = it },
|
Row(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
singleLine = true,
|
horizontalArrangement = Arrangement.Center,
|
||||||
placeholder = { Text(stringResource(id = R.string.chat_search_gifs)) },
|
) {
|
||||||
)
|
CircularProgressIndicator(modifier = Modifier.size(20.dp), strokeWidth = 2.dp)
|
||||||
when {
|
|
||||||
isGiphyLoading -> {
|
|
||||||
Row(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
horizontalArrangement = Arrangement.Center,
|
|
||||||
) {
|
|
||||||
CircularProgressIndicator(modifier = Modifier.size(20.dp), strokeWidth = 2.dp)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
!giphyErrorMessage.isNullOrBlank() -> {
|
|
||||||
Text(
|
|
||||||
text = giphyErrorMessage.orEmpty(),
|
|
||||||
style = MaterialTheme.typography.bodySmall,
|
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
LazyVerticalGrid(
|
|
||||||
columns = GridCells.Fixed(3),
|
!giphyErrorMessage.isNullOrBlank() -> {
|
||||||
|
Text(
|
||||||
|
text = giphyErrorMessage,
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LazyVerticalGrid(
|
||||||
|
columns = GridCells.Fixed(3),
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(320.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(6.dp),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(6.dp),
|
||||||
|
) {
|
||||||
|
items(remoteItems) { remote ->
|
||||||
|
Surface(
|
||||||
|
shape = RoundedCornerShape(10.dp),
|
||||||
|
color = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.45f),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.clip(RoundedCornerShape(10.dp))
|
||||||
.height(320.dp),
|
.clickable(enabled = !isPickerSending) {
|
||||||
verticalArrangement = Arrangement.spacedBy(6.dp),
|
scope.launch {
|
||||||
horizontalArrangement = Arrangement.spacedBy(6.dp),
|
isPickerSending = true
|
||||||
) {
|
if (emojiPickerTab == ComposerPickerTab.Gif || emojiPickerTab == ComposerPickerTab.Sticker) {
|
||||||
items(remoteItems) { remote ->
|
onSendPresetMediaUrl(remote.url)
|
||||||
Surface(
|
onDismiss()
|
||||||
shape = RoundedCornerShape(10.dp),
|
} else {
|
||||||
color = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.45f),
|
val payload = downloadRemoteMedia(remote.url, remote.fileNamePrefix)
|
||||||
modifier = Modifier
|
if (payload != null) {
|
||||||
.clip(RoundedCornerShape(10.dp))
|
val stickerAdjustedName = "sticker_${payload.fileName}"
|
||||||
.clickable(enabled = !isPickerSending) {
|
onSendRemoteMedia(stickerAdjustedName, payload.mimeType, payload.bytes)
|
||||||
scope.launch {
|
onDismiss()
|
||||||
isPickerSending = true
|
|
||||||
if (emojiPickerTab == ComposerPickerTab.Gif || emojiPickerTab == ComposerPickerTab.Sticker) {
|
|
||||||
onSendPresetMediaUrl(remote.url)
|
|
||||||
showEmojiPicker = false
|
|
||||||
} else {
|
|
||||||
val payload = downloadRemoteMedia(remote.url, remote.fileNamePrefix)
|
|
||||||
if (payload != null) {
|
|
||||||
val stickerAdjustedName = "sticker_${payload.fileName}"
|
|
||||||
onSendRemoteMedia(stickerAdjustedName, payload.mimeType, payload.bytes)
|
|
||||||
showEmojiPicker = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
isPickerSending = false
|
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
) {
|
isPickerSending = false
|
||||||
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
|
||||||
AsyncImage(
|
|
||||||
model = remote.url,
|
|
||||||
contentDescription = remote.title,
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.aspectRatio(1f),
|
|
||||||
contentScale = ContentScale.Crop,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
) {
|
||||||
|
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
||||||
|
AsyncImage(
|
||||||
|
model = remote.url,
|
||||||
|
contentDescription = remote.title,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.aspectRatio(1f),
|
||||||
|
contentScale = ContentScale.Crop,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (isPickerSending) {
|
}
|
||||||
Row(
|
}
|
||||||
modifier = Modifier.fillMaxWidth(),
|
if (isPickerSending) {
|
||||||
horizontalArrangement = Arrangement.Center,
|
Row(
|
||||||
) {
|
modifier = Modifier.fillMaxWidth(),
|
||||||
CircularProgressIndicator(modifier = Modifier.size(20.dp), strokeWidth = 2.dp)
|
horizontalArrangement = Arrangement.Center,
|
||||||
}
|
) {
|
||||||
}
|
CircularProgressIndicator(modifier = Modifier.size(20.dp), strokeWidth = 2.dp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user