mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2025-04-08 20:08:06 +02:00
Refinements to ShopStr's event rendering.
This commit is contained in:
parent
fcdb863c2b
commit
6b7bb209d4
@ -833,6 +833,44 @@ private fun DialogContent(
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
fun InlineCarrousel(
|
||||
allImages: ImmutableList<String>,
|
||||
imageUrl: String
|
||||
) {
|
||||
val pagerState: PagerState = rememberPagerState() { allImages.size }
|
||||
|
||||
LaunchedEffect(key1 = pagerState, key2 = imageUrl) {
|
||||
launch {
|
||||
val page = allImages.indexOf(imageUrl)
|
||||
if (page > -1) {
|
||||
pagerState.scrollToPage(page)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (allImages.size > 1) {
|
||||
SlidingCarousel(
|
||||
pagerState = pagerState
|
||||
) { index ->
|
||||
AsyncImage(
|
||||
model = allImages[index],
|
||||
contentDescription = null,
|
||||
contentScale = ContentScale.FillWidth,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
}
|
||||
} else {
|
||||
AsyncImage(
|
||||
model = imageUrl,
|
||||
contentDescription = null,
|
||||
contentScale = ContentScale.FillWidth,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun CopyToClipboard(
|
||||
content: ZoomableContent
|
||||
|
@ -1206,7 +1206,8 @@ private fun RenderNoteRow(
|
||||
RenderClassifieds(
|
||||
noteEvent,
|
||||
baseNote,
|
||||
accountViewModel
|
||||
accountViewModel,
|
||||
nav
|
||||
)
|
||||
}
|
||||
|
||||
@ -3896,7 +3897,7 @@ private fun LongFormHeader(noteEvent: LongTextNoteEvent, note: Note, accountView
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun RenderClassifieds(noteEvent: ClassifiedsEvent, note: Note, accountViewModel: AccountViewModel) {
|
||||
private fun RenderClassifieds(noteEvent: ClassifiedsEvent, note: Note, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
|
||||
val image = remember(noteEvent) { noteEvent.image() }
|
||||
val title = remember(noteEvent) { noteEvent.title() }
|
||||
val summary = remember(noteEvent) { noteEvent.summary() ?: noteEvent.content.take(200).ifBlank { null } }
|
||||
@ -3933,18 +3934,23 @@ private fun RenderClassifieds(noteEvent: ClassifiedsEvent, note: Note, accountVi
|
||||
text = it,
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
}
|
||||
|
||||
price?.let {
|
||||
val priceTag = remember(noteEvent) {
|
||||
val newAmount = price.amount.toBigDecimalOrNull()?.let {
|
||||
showAmount(it)
|
||||
} ?: price.amount
|
||||
|
||||
if (price.frequency != null && price.currency != null) {
|
||||
"${price.amount} ${price.currency}/${price.frequency}"
|
||||
"$newAmount ${price.currency}/${price.frequency}"
|
||||
} else if (price.currency != null) {
|
||||
"${price.amount} ${price.currency}"
|
||||
"$newAmount ${price.currency}"
|
||||
} else {
|
||||
price.amount
|
||||
newAmount
|
||||
}
|
||||
}
|
||||
|
||||
@ -3956,7 +3962,6 @@ private fun RenderClassifieds(noteEvent: ClassifiedsEvent, note: Note, accountVi
|
||||
modifier = remember {
|
||||
Modifier
|
||||
.clip(SmallBorder)
|
||||
.background(Color.Black)
|
||||
.padding(start = 5.dp)
|
||||
}
|
||||
)
|
||||
@ -3977,16 +3982,46 @@ private fun RenderClassifieds(noteEvent: ClassifiedsEvent, note: Note, accountVi
|
||||
)
|
||||
}
|
||||
|
||||
location?.let {
|
||||
Text(
|
||||
text = it,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = Color.Gray,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
modifier = Modifier.padding(start = 5.dp)
|
||||
)
|
||||
/*
|
||||
Column {
|
||||
location?.let {
|
||||
Text(
|
||||
text = it,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = Color.Gray,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
modifier = Modifier.padding(start = 5.dp)
|
||||
)
|
||||
}
|
||||
|
||||
Button(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 3.dp)
|
||||
.width(50.dp),
|
||||
onClick = {
|
||||
note.author?.let {
|
||||
accountViewModel.createChatRoomFor(it) {
|
||||
nav("Room/$it")
|
||||
}
|
||||
}
|
||||
},
|
||||
contentPadding = ZeroPadding,
|
||||
colors = ButtonDefaults
|
||||
.buttonColors(
|
||||
containerColor = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_dm),
|
||||
stringResource(R.string.send_a_direct_message),
|
||||
modifier = Modifier.size(20.dp),
|
||||
tint = Color.White
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6,6 +6,7 @@ import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
@ -17,14 +18,18 @@ import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.MoreVert
|
||||
import androidx.compose.material3.Divider
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.LocalTextStyle
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.ProvideTextStyle
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextField
|
||||
import androidx.compose.material3.TextFieldDefaults
|
||||
import androidx.compose.material3.pullrefresh.PullRefreshIndicator
|
||||
import androidx.compose.material3.pullrefresh.pullRefresh
|
||||
import androidx.compose.material3.pullrefresh.rememberPullRefreshState
|
||||
@ -38,15 +43,18 @@ import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.draw.drawBehind
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.input.KeyboardCapitalization
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import androidx.compose.ui.text.style.TextDirection
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.em
|
||||
@ -55,6 +63,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import coil.compose.AsyncImage
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.ui.components.InlineCarrousel
|
||||
import com.vitorpamplona.amethyst.ui.components.ObserveDisplayNip05Status
|
||||
import com.vitorpamplona.amethyst.ui.note.AudioHeader
|
||||
import com.vitorpamplona.amethyst.ui.note.AudioTrackHeader
|
||||
@ -87,13 +96,18 @@ import com.vitorpamplona.amethyst.ui.note.RenderPostApproval
|
||||
import com.vitorpamplona.amethyst.ui.note.RenderRepost
|
||||
import com.vitorpamplona.amethyst.ui.note.RenderTextEvent
|
||||
import com.vitorpamplona.amethyst.ui.note.Reward
|
||||
import com.vitorpamplona.amethyst.ui.note.routeToMessage
|
||||
import com.vitorpamplona.amethyst.ui.note.showAmount
|
||||
import com.vitorpamplona.amethyst.ui.note.timeAgo
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.ChannelHeader
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.ThinSendButton
|
||||
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
|
||||
import com.vitorpamplona.amethyst.ui.theme.DoubleVertSpacer
|
||||
import com.vitorpamplona.amethyst.ui.theme.EditFieldBorder
|
||||
import com.vitorpamplona.amethyst.ui.theme.EditFieldTrailingIconModifier
|
||||
import com.vitorpamplona.amethyst.ui.theme.FeedPadding
|
||||
import com.vitorpamplona.amethyst.ui.theme.SmallBorder
|
||||
import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer
|
||||
import com.vitorpamplona.amethyst.ui.theme.lessImportantLink
|
||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||
import com.vitorpamplona.amethyst.ui.theme.selectedNote
|
||||
@ -117,6 +131,7 @@ import com.vitorpamplona.quartz.events.PinListEvent
|
||||
import com.vitorpamplona.quartz.events.PollNoteEvent
|
||||
import com.vitorpamplona.quartz.events.RelaySetEvent
|
||||
import com.vitorpamplona.quartz.events.RepostEvent
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableSet
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
@ -388,7 +403,7 @@ fun NoteMaster(
|
||||
} else if (noteEvent is LongTextNoteEvent) {
|
||||
RenderLongFormHeaderForThread(noteEvent)
|
||||
} else if (noteEvent is ClassifiedsEvent) {
|
||||
RenderClassifiedsReaderForThread(noteEvent, note, accountViewModel)
|
||||
RenderClassifiedsReaderForThread(noteEvent, note, accountViewModel, nav)
|
||||
}
|
||||
|
||||
Row(
|
||||
@ -517,52 +532,64 @@ fun NoteMaster(
|
||||
private fun RenderClassifiedsReaderForThread(
|
||||
noteEvent: ClassifiedsEvent,
|
||||
note: Note,
|
||||
accountViewModel: AccountViewModel
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit
|
||||
) {
|
||||
val image = remember(noteEvent) { noteEvent.image() }
|
||||
val images = remember(noteEvent) { noteEvent.images().toImmutableList() }
|
||||
val title = remember(noteEvent) { noteEvent.title() }
|
||||
val summary =
|
||||
remember(noteEvent) { noteEvent.summary() ?: noteEvent.content.take(200).ifBlank { null } }
|
||||
val summary = remember(noteEvent) {
|
||||
val sum = noteEvent.summary()
|
||||
if (sum != noteEvent.content) {
|
||||
sum
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
val price = remember(noteEvent) { noteEvent.price() }
|
||||
val location = remember(noteEvent) { noteEvent.location() }
|
||||
|
||||
Row(modifier = Modifier.padding(start = 12.dp, end = 12.dp, bottom = 12.dp)) {
|
||||
Column {
|
||||
Row() {
|
||||
image?.let {
|
||||
AsyncImage(
|
||||
model = it,
|
||||
contentDescription = stringResource(
|
||||
R.string.preview_card_image_for,
|
||||
it
|
||||
),
|
||||
contentScale = ContentScale.FillWidth,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
if (images.isNotEmpty()) {
|
||||
Row() {
|
||||
InlineCarrousel(
|
||||
images,
|
||||
images.first()
|
||||
)
|
||||
} ?: CreateImageHeader(note, accountViewModel)
|
||||
}
|
||||
} else {
|
||||
CreateImageHeader(note, accountViewModel)
|
||||
}
|
||||
|
||||
Row(
|
||||
Modifier.padding(start = 10.dp, end = 10.dp, top = 10.dp),
|
||||
Modifier.padding(top = 10.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
title?.let {
|
||||
Text(
|
||||
text = it,
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
maxLines = 1,
|
||||
style = MaterialTheme.typography.headlineSmall,
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
price?.let {
|
||||
Row(
|
||||
Modifier.padding(top = 5.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
val newAmount = price.amount.toBigDecimalOrNull()?.let {
|
||||
showAmount(it)
|
||||
} ?: price.amount
|
||||
|
||||
price?.let {
|
||||
val priceTag = remember(noteEvent) {
|
||||
if (price.frequency != null && price.currency != null) {
|
||||
"${price.amount} ${price.currency}/${price.frequency}"
|
||||
"$newAmount ${price.currency}/${price.frequency}"
|
||||
} else if (price.currency != null) {
|
||||
"${price.amount} ${price.currency}"
|
||||
"$newAmount ${price.currency}"
|
||||
} else {
|
||||
price.amount
|
||||
newAmount
|
||||
}
|
||||
}
|
||||
|
||||
@ -571,19 +598,22 @@ private fun RenderClassifiedsReaderForThread(
|
||||
maxLines = 1,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
fontWeight = FontWeight.Bold,
|
||||
modifier = remember {
|
||||
Modifier
|
||||
.clip(SmallBorder)
|
||||
.background(Color.Black)
|
||||
.padding(start = 5.dp)
|
||||
}
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
|
||||
location?.let {
|
||||
Text(
|
||||
text = it,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
summary?.let {
|
||||
Row(
|
||||
Modifier.padding(start = 10.dp, end = 10.dp, top = 5.dp),
|
||||
Modifier.padding(top = 5.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(
|
||||
@ -596,19 +626,78 @@ private fun RenderClassifiedsReaderForThread(
|
||||
}
|
||||
}
|
||||
|
||||
location?.let {
|
||||
Row(
|
||||
Modifier.padding(start = 10.dp, end = 10.dp, top = 5.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(
|
||||
text = it,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = Color.Gray,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
Row(
|
||||
Modifier
|
||||
.padding(start = 20.dp, end = 20.dp, bottom = 5.dp, top = 15.dp)
|
||||
.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_dm),
|
||||
stringResource(R.string.send_a_direct_message),
|
||||
modifier = Modifier.size(20.dp),
|
||||
tint = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
|
||||
Spacer(modifier = StdHorzSpacer)
|
||||
|
||||
Text(stringResource(id = R.string.send_the_seller_a_message))
|
||||
}
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.padding(start = 10.dp, end = 10.dp, bottom = 5.dp, top = 5.dp)
|
||||
.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
val sellerName = note.author?.bestDisplayName() ?: note.author?.bestUsername()
|
||||
|
||||
val msg = if (sellerName != null) {
|
||||
stringResource(
|
||||
id = R.string.hi_seller_is_this_still_available,
|
||||
sellerName
|
||||
)
|
||||
} else {
|
||||
stringResource(id = R.string.hi_there_is_this_still_available)
|
||||
}
|
||||
|
||||
var message by remember {
|
||||
mutableStateOf(TextFieldValue(msg))
|
||||
}
|
||||
|
||||
TextField(
|
||||
value = message,
|
||||
onValueChange = {
|
||||
message = it
|
||||
},
|
||||
keyboardOptions = KeyboardOptions.Default.copy(
|
||||
capitalization = KeyboardCapitalization.Sentences
|
||||
),
|
||||
shape = EditFieldBorder,
|
||||
modifier = Modifier.weight(1f, true),
|
||||
placeholder = {
|
||||
Text(
|
||||
text = stringResource(R.string.reply_here),
|
||||
color = MaterialTheme.colorScheme.placeholderText
|
||||
)
|
||||
},
|
||||
textStyle = LocalTextStyle.current.copy(textDirection = TextDirection.Content),
|
||||
trailingIcon = {
|
||||
ThinSendButton(
|
||||
isActive = message.text.isNotBlank(),
|
||||
modifier = EditFieldTrailingIconModifier
|
||||
) {
|
||||
note.author?.let {
|
||||
nav(routeToMessage(it, msg, accountViewModel))
|
||||
}
|
||||
}
|
||||
},
|
||||
colors = TextFieldDefaults.colors(
|
||||
focusedIndicatorColor = Color.Transparent,
|
||||
unfocusedIndicatorColor = Color.Transparent
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -664,4 +664,7 @@
|
||||
|
||||
<string name="payment_required_title">Message from %1$s</string>
|
||||
<string name="thread_title">Thread</string>
|
||||
<string name="send_the_seller_a_message">Send the seller a message</string>
|
||||
<string name="hi_seller_is_this_still_available">Hi %1$s, is this still available?</string>
|
||||
<string name="hi_there_is_this_still_available">Hi there, is this still available?</string>
|
||||
</resources>
|
||||
|
@ -19,6 +19,7 @@ class ClassifiedsEvent(
|
||||
) : BaseAddressableEvent(id, pubKey, createdAt, kind, tags, content, sig) {
|
||||
fun title() = tags.firstOrNull { it.size > 1 && it[0] == "title" }?.get(1)
|
||||
fun image() = tags.firstOrNull { it.size > 1 && it[0] == "image" }?.get(1)
|
||||
fun images() = tags.filter { it.size > 1 && it[0] == "image" }.map { it[1] }
|
||||
fun summary() = tags.firstOrNull { it.size > 1 && it[0] == "summary" }?.get(1)
|
||||
fun price() = tags.firstOrNull { it.size > 1 && it[0] == "price" }?.let {
|
||||
Price(it[1], it.getOrNull(2), it.getOrNull(3))
|
||||
|
Loading…
x
Reference in New Issue
Block a user