- Unifies All Badge rendering
- Constraints badge layouts on Notes - Creates a flow cache to speed up Note relay layout - Speeds up NoteQuickActionMenus - Optimizes NIP-11 pings - Better error messages when NIP-11 fails
This commit is contained in:
@@ -418,7 +418,7 @@ open class Note(
|
|||||||
fun addRelay(relay: Relay) {
|
fun addRelay(relay: Relay) {
|
||||||
if (relay.brief !in relays) {
|
if (relay.brief !in relays) {
|
||||||
addRelaySync(relay.brief)
|
addRelaySync(relay.brief)
|
||||||
liveSet?.innerRelays?.invalidateData()
|
flowSet?.relays?.invalidateData()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -847,12 +847,17 @@ class NoteFlowSet(
|
|||||||
// Observers line up here.
|
// Observers line up here.
|
||||||
val metadata = NoteBundledRefresherFlow(u)
|
val metadata = NoteBundledRefresherFlow(u)
|
||||||
val reports = NoteBundledRefresherFlow(u)
|
val reports = NoteBundledRefresherFlow(u)
|
||||||
|
val relays = NoteBundledRefresherFlow(u)
|
||||||
|
|
||||||
fun isInUse(): Boolean = metadata.stateFlow.subscriptionCount.value > 0 || reports.stateFlow.subscriptionCount.value > 0
|
fun isInUse(): Boolean =
|
||||||
|
metadata.stateFlow.subscriptionCount.value > 0 ||
|
||||||
|
reports.stateFlow.subscriptionCount.value > 0 ||
|
||||||
|
relays.stateFlow.subscriptionCount.value > 0
|
||||||
|
|
||||||
fun destroy() {
|
fun destroy() {
|
||||||
metadata.destroy()
|
metadata.destroy()
|
||||||
reports.destroy()
|
reports.destroy()
|
||||||
|
relays.destroy()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -865,7 +870,6 @@ class NoteLiveSet(
|
|||||||
val innerReactions = NoteBundledRefresherLiveData(u)
|
val innerReactions = NoteBundledRefresherLiveData(u)
|
||||||
val innerBoosts = NoteBundledRefresherLiveData(u)
|
val innerBoosts = NoteBundledRefresherLiveData(u)
|
||||||
val innerReplies = NoteBundledRefresherLiveData(u)
|
val innerReplies = NoteBundledRefresherLiveData(u)
|
||||||
val innerRelays = NoteBundledRefresherLiveData(u)
|
|
||||||
val innerZaps = NoteBundledRefresherLiveData(u)
|
val innerZaps = NoteBundledRefresherLiveData(u)
|
||||||
val innerOts = NoteBundledRefresherLiveData(u)
|
val innerOts = NoteBundledRefresherLiveData(u)
|
||||||
val innerModifications = NoteBundledRefresherLiveData(u)
|
val innerModifications = NoteBundledRefresherLiveData(u)
|
||||||
@@ -874,7 +878,6 @@ class NoteLiveSet(
|
|||||||
val reactions = innerReactions.map { it }
|
val reactions = innerReactions.map { it }
|
||||||
val boosts = innerBoosts.map { it }
|
val boosts = innerBoosts.map { it }
|
||||||
val replies = innerReplies.map { it }
|
val replies = innerReplies.map { it }
|
||||||
val relays = innerRelays.map { it }
|
|
||||||
val zaps = innerZaps.map { it }
|
val zaps = innerZaps.map { it }
|
||||||
|
|
||||||
val hasEvent = innerMetadata.map { it.note.event != null }.distinctUntilChanged()
|
val hasEvent = innerMetadata.map { it.note.event != null }.distinctUntilChanged()
|
||||||
@@ -900,8 +903,6 @@ class NoteLiveSet(
|
|||||||
|
|
||||||
val boostCount = innerBoosts.map { it.note.boosts.size }.distinctUntilChanged()
|
val boostCount = innerBoosts.map { it.note.boosts.size }.distinctUntilChanged()
|
||||||
|
|
||||||
val relayInfo = innerRelays.map { it.note.relays }
|
|
||||||
|
|
||||||
val content = innerMetadata.map { it.note.event?.content() ?: "" }
|
val content = innerMetadata.map { it.note.event?.content() ?: "" }
|
||||||
|
|
||||||
fun isInUse(): Boolean =
|
fun isInUse(): Boolean =
|
||||||
@@ -909,7 +910,6 @@ class NoteLiveSet(
|
|||||||
reactions.hasObservers() ||
|
reactions.hasObservers() ||
|
||||||
boosts.hasObservers() ||
|
boosts.hasObservers() ||
|
||||||
replies.hasObservers() ||
|
replies.hasObservers() ||
|
||||||
relays.hasObservers() ||
|
|
||||||
zaps.hasObservers() ||
|
zaps.hasObservers() ||
|
||||||
hasEvent.hasObservers() ||
|
hasEvent.hasObservers() ||
|
||||||
hasReactions.hasObservers() ||
|
hasReactions.hasObservers() ||
|
||||||
@@ -924,7 +924,6 @@ class NoteLiveSet(
|
|||||||
innerReactions.destroy()
|
innerReactions.destroy()
|
||||||
innerBoosts.destroy()
|
innerBoosts.destroy()
|
||||||
innerReplies.destroy()
|
innerReplies.destroy()
|
||||||
innerRelays.destroy()
|
|
||||||
innerZaps.destroy()
|
innerZaps.destroy()
|
||||||
innerOts.destroy()
|
innerOts.destroy()
|
||||||
innerModifications.destroy()
|
innerModifications.destroy()
|
||||||
@@ -936,6 +935,7 @@ class NoteBundledRefresherFlow(
|
|||||||
val note: Note,
|
val note: Note,
|
||||||
) {
|
) {
|
||||||
// Refreshes observers in batches.
|
// Refreshes observers in batches.
|
||||||
|
// TODO: Replace the bundler for .sample
|
||||||
private val bundler = BundledUpdate(500, Dispatchers.IO)
|
private val bundler = BundledUpdate(500, Dispatchers.IO)
|
||||||
val stateFlow = MutableStateFlow(NoteState(note))
|
val stateFlow = MutableStateFlow(NoteState(note))
|
||||||
|
|
||||||
@@ -1015,11 +1015,12 @@ object RelayBriefInfoCache {
|
|||||||
val cache = LruCache<String, RelayBriefInfo?>(50)
|
val cache = LruCache<String, RelayBriefInfo?>(50)
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
data class RelayBriefInfo(
|
class RelayBriefInfo(
|
||||||
val url: String,
|
val url: String,
|
||||||
val displayUrl: String = RelayUrlFormatter.displayUrl(url).intern(),
|
) {
|
||||||
val favIcon: String = "https://$displayUrl/favicon.ico".intern(),
|
val displayUrl: String = RelayUrlFormatter.displayUrl(url).intern()
|
||||||
)
|
val favIcon: String = "https://$displayUrl/favicon.ico".intern()
|
||||||
|
}
|
||||||
|
|
||||||
fun get(url: String): RelayBriefInfo {
|
fun get(url: String): RelayBriefInfo {
|
||||||
val info = cache[url]
|
val info = cache[url]
|
||||||
|
|||||||
@@ -87,13 +87,11 @@ fun RobohashFallbackAsyncImage(
|
|||||||
filterQuality: FilterQuality = DrawScope.DefaultFilterQuality,
|
filterQuality: FilterQuality = DrawScope.DefaultFilterQuality,
|
||||||
loadProfilePicture: Boolean,
|
loadProfilePicture: Boolean,
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
|
||||||
val isLightTheme = MaterialTheme.colorScheme.isLight
|
|
||||||
|
|
||||||
if (model != null && loadProfilePicture) {
|
if (model != null && loadProfilePicture) {
|
||||||
val isBase64 by remember { derivedStateOf { model.startsWith("data:image/jpeg;base64,") } }
|
val isBase64 by remember { derivedStateOf { model.startsWith("data:image/jpeg;base64,") } }
|
||||||
|
|
||||||
if (isBase64) {
|
if (isBase64) {
|
||||||
|
val context = LocalContext.current
|
||||||
val base64Painter =
|
val base64Painter =
|
||||||
rememberAsyncImagePainter(
|
rememberAsyncImagePainter(
|
||||||
model = Base64Requester.imageRequest(context, model),
|
model = Base64Requester.imageRequest(context, model),
|
||||||
@@ -110,7 +108,7 @@ fun RobohashFallbackAsyncImage(
|
|||||||
} else {
|
} else {
|
||||||
val painter =
|
val painter =
|
||||||
rememberVectorPainter(
|
rememberVectorPainter(
|
||||||
image = CachedRobohash.get(robot, isLightTheme),
|
image = CachedRobohash.get(robot, MaterialTheme.colorScheme.isLight),
|
||||||
)
|
)
|
||||||
|
|
||||||
AsyncImage(
|
AsyncImage(
|
||||||
@@ -128,7 +126,7 @@ fun RobohashFallbackAsyncImage(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
val robotPainter = CachedRobohash.get(robot, isLightTheme)
|
val robotPainter = CachedRobohash.get(robot, MaterialTheme.colorScheme.isLight)
|
||||||
|
|
||||||
Image(
|
Image(
|
||||||
imageVector = robotPainter,
|
imageVector = robotPainter,
|
||||||
@@ -145,9 +143,12 @@ object Base64Requester {
|
|||||||
fun imageRequest(
|
fun imageRequest(
|
||||||
context: Context,
|
context: Context,
|
||||||
message: String,
|
message: String,
|
||||||
): ImageRequest {
|
): ImageRequest =
|
||||||
return ImageRequest.Builder(context).data(message).fetcherFactory(Base64Fetcher.Factory).build()
|
ImageRequest
|
||||||
}
|
.Builder(context)
|
||||||
|
.data(message)
|
||||||
|
.fetcherFactory(Base64Fetcher.Factory)
|
||||||
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
@@ -179,8 +180,6 @@ class Base64Fetcher(
|
|||||||
data: Uri,
|
data: Uri,
|
||||||
options: Options,
|
options: Options,
|
||||||
imageLoader: ImageLoader,
|
imageLoader: ImageLoader,
|
||||||
): Fetcher {
|
): Fetcher = Base64Fetcher(options, data)
|
||||||
return Base64Fetcher(options, data)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -190,10 +190,9 @@ fun NormalChatNote(
|
|||||||
nav("User/${it.pubkeyHex}")
|
nav("User/${it.pubkeyHex}")
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
actionMenu = { popupExpanded, onDismiss ->
|
actionMenu = { onDismiss ->
|
||||||
NoteQuickActionMenu(
|
NoteQuickActionMenu(
|
||||||
note = note,
|
note = note,
|
||||||
popupExpanded = popupExpanded,
|
|
||||||
onDismiss = onDismiss,
|
onDismiss = onDismiss,
|
||||||
onWantsToEditDraft = { onWantsToEditDraft(note) },
|
onWantsToEditDraft = { onWantsToEditDraft(note) },
|
||||||
accountViewModel = accountViewModel,
|
accountViewModel = accountViewModel,
|
||||||
@@ -256,7 +255,7 @@ fun ChatBubbleLayout(
|
|||||||
parentBackgroundColor: MutableState<Color>? = null,
|
parentBackgroundColor: MutableState<Color>? = null,
|
||||||
onClick: () -> Boolean,
|
onClick: () -> Boolean,
|
||||||
onAuthorClick: () -> Unit,
|
onAuthorClick: () -> Unit,
|
||||||
actionMenu: @Composable (popupExpanded: Boolean, onDismiss: () -> Unit) -> Unit,
|
actionMenu: @Composable (onDismiss: () -> Unit) -> Unit,
|
||||||
detailRow: @Composable () -> Unit,
|
detailRow: @Composable () -> Unit,
|
||||||
drawAuthorLine: @Composable () -> Unit,
|
drawAuthorLine: @Composable () -> Unit,
|
||||||
inner: @Composable (MutableState<Color>) -> Unit,
|
inner: @Composable (MutableState<Color>) -> Unit,
|
||||||
@@ -294,7 +293,7 @@ fun ChatBubbleLayout(
|
|||||||
modifier = if (innerQuote) ChatPaddingInnerQuoteModifier else ChatPaddingModifier,
|
modifier = if (innerQuote) ChatPaddingInnerQuoteModifier else ChatPaddingModifier,
|
||||||
horizontalArrangement = alignment,
|
horizontalArrangement = alignment,
|
||||||
) {
|
) {
|
||||||
var popupExpanded by remember { mutableStateOf(false) }
|
var popupExpanded = remember { mutableStateOf(false) }
|
||||||
|
|
||||||
val modif2 = if (innerQuote) Modifier else ChatBubbleMaxSizeModifier
|
val modif2 = if (innerQuote) Modifier else ChatBubbleMaxSizeModifier
|
||||||
|
|
||||||
@@ -319,7 +318,7 @@ fun ChatBubbleLayout(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onLongClick = { popupExpanded = true },
|
onLongClick = { popupExpanded.value = true },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -357,8 +356,10 @@ fun ChatBubbleLayout(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
actionMenu(popupExpanded) {
|
if (popupExpanded.value) {
|
||||||
popupExpanded = false
|
actionMenu {
|
||||||
|
popupExpanded.value = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -381,7 +382,7 @@ private fun BubblePreview() {
|
|||||||
parentBackgroundColor = backgroundBubbleColor,
|
parentBackgroundColor = backgroundBubbleColor,
|
||||||
onClick = { false },
|
onClick = { false },
|
||||||
onAuthorClick = {},
|
onAuthorClick = {},
|
||||||
actionMenu = { popupExpanded, onDismiss ->
|
actionMenu = { onDismiss ->
|
||||||
},
|
},
|
||||||
drawAuthorLine = {
|
drawAuthorLine = {
|
||||||
UserDisplayNameLayout(
|
UserDisplayNameLayout(
|
||||||
@@ -411,7 +412,7 @@ private fun BubblePreview() {
|
|||||||
parentBackgroundColor = backgroundBubbleColor,
|
parentBackgroundColor = backgroundBubbleColor,
|
||||||
onClick = { false },
|
onClick = { false },
|
||||||
onAuthorClick = {},
|
onAuthorClick = {},
|
||||||
actionMenu = { popupExpanded, onDismiss ->
|
actionMenu = { onDismiss ->
|
||||||
},
|
},
|
||||||
drawAuthorLine = {
|
drawAuthorLine = {
|
||||||
UserDisplayNameLayout(
|
UserDisplayNameLayout(
|
||||||
|
|||||||
@@ -40,7 +40,6 @@ import androidx.compose.runtime.getValue
|
|||||||
import androidx.compose.runtime.livedata.observeAsState
|
import androidx.compose.runtime.livedata.observeAsState
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Alignment.Companion.CenterVertically
|
import androidx.compose.ui.Alignment.Companion.CenterVertically
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
@@ -121,6 +120,7 @@ import com.vitorpamplona.amethyst.ui.theme.HalfDoubleVertSpacer
|
|||||||
import com.vitorpamplona.amethyst.ui.theme.HalfEndPadding
|
import com.vitorpamplona.amethyst.ui.theme.HalfEndPadding
|
||||||
import com.vitorpamplona.amethyst.ui.theme.HalfPadding
|
import com.vitorpamplona.amethyst.ui.theme.HalfPadding
|
||||||
import com.vitorpamplona.amethyst.ui.theme.HalfStartPadding
|
import com.vitorpamplona.amethyst.ui.theme.HalfStartPadding
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.RowColSpacing5dp
|
||||||
import com.vitorpamplona.amethyst.ui.theme.Size25dp
|
import com.vitorpamplona.amethyst.ui.theme.Size25dp
|
||||||
import com.vitorpamplona.amethyst.ui.theme.Size30Modifier
|
import com.vitorpamplona.amethyst.ui.theme.Size30Modifier
|
||||||
import com.vitorpamplona.amethyst.ui.theme.Size34dp
|
import com.vitorpamplona.amethyst.ui.theme.Size34dp
|
||||||
@@ -179,7 +179,6 @@ import com.vitorpamplona.quartz.events.VideoEvent
|
|||||||
import com.vitorpamplona.quartz.events.VideoHorizontalEvent
|
import com.vitorpamplona.quartz.events.VideoHorizontalEvent
|
||||||
import com.vitorpamplona.quartz.events.VideoVerticalEvent
|
import com.vitorpamplona.quartz.events.VideoVerticalEvent
|
||||||
import com.vitorpamplona.quartz.events.WikiNoteEvent
|
import com.vitorpamplona.quartz.events.WikiNoteEvent
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun NoteCompose(
|
fun NoteCompose(
|
||||||
@@ -416,22 +415,18 @@ fun ClickableNote(
|
|||||||
nav: (String) -> Unit,
|
nav: (String) -> Unit,
|
||||||
content: @Composable () -> Unit,
|
content: @Composable () -> Unit,
|
||||||
) {
|
) {
|
||||||
val scope = rememberCoroutineScope()
|
|
||||||
|
|
||||||
val updatedModifier =
|
val updatedModifier =
|
||||||
remember(baseNote, backgroundColor.value) {
|
remember(baseNote, backgroundColor.value) {
|
||||||
modifier
|
modifier
|
||||||
.combinedClickable(
|
.combinedClickable(
|
||||||
onClick = {
|
onClick = {
|
||||||
scope.launch {
|
val redirectToNote =
|
||||||
val redirectToNote =
|
if (baseNote.event is RepostEvent || baseNote.event is GenericRepostEvent) {
|
||||||
if (baseNote.event is RepostEvent || baseNote.event is GenericRepostEvent) {
|
baseNote.replyTo?.lastOrNull() ?: baseNote
|
||||||
baseNote.replyTo?.lastOrNull() ?: baseNote
|
} else {
|
||||||
} else {
|
baseNote
|
||||||
baseNote
|
}
|
||||||
}
|
routeFor(redirectToNote, accountViewModel.userProfile())?.let { nav(it) }
|
||||||
routeFor(redirectToNote, accountViewModel.userProfile())?.let { nav(it) }
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
onLongClick = showPopup,
|
onLongClick = showPopup,
|
||||||
).background(backgroundColor.value)
|
).background(backgroundColor.value)
|
||||||
@@ -465,9 +460,7 @@ fun InnerNoteWithReactions(
|
|||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
if (notBoostedNorQuote) {
|
if (notBoostedNorQuote) {
|
||||||
Column(WidthAuthorPictureModifier) {
|
AuthorAndRelayInformation(baseNote, accountViewModel, nav)
|
||||||
AuthorAndRelayInformation(baseNote, accountViewModel, nav)
|
|
||||||
}
|
|
||||||
Spacer(modifier = DoubleHorzSpacer)
|
Spacer(modifier = DoubleHorzSpacer)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1064,12 +1057,14 @@ private fun AuthorAndRelayInformation(
|
|||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
nav: (String) -> Unit,
|
nav: (String) -> Unit,
|
||||||
) {
|
) {
|
||||||
// Draws the boosted picture outside the boosted card.
|
Column(WidthAuthorPictureModifier, verticalArrangement = RowColSpacing5dp) {
|
||||||
Box(modifier = Size55Modifier, contentAlignment = Alignment.BottomEnd) {
|
// Draws the boosted picture outside the boosted card.
|
||||||
RenderAuthorImages(baseNote, nav, accountViewModel)
|
Box(modifier = Size55Modifier, contentAlignment = Alignment.BottomEnd) {
|
||||||
}
|
RenderAuthorImages(baseNote, nav, accountViewModel)
|
||||||
|
}
|
||||||
|
|
||||||
BadgeBox(baseNote, accountViewModel, nav)
|
BadgeBox(baseNote, accountViewModel, nav)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|||||||
@@ -137,23 +137,22 @@ fun LongPressToQuickAction(
|
|||||||
content: @Composable (() -> Unit) -> Unit,
|
content: @Composable (() -> Unit) -> Unit,
|
||||||
) {
|
) {
|
||||||
val popupExpanded = remember { mutableStateOf(false) }
|
val popupExpanded = remember { mutableStateOf(false) }
|
||||||
val showPopup = remember { { popupExpanded.value = true } }
|
|
||||||
|
|
||||||
content(showPopup)
|
content { popupExpanded.value = true }
|
||||||
|
|
||||||
NoteQuickActionMenu(
|
if (popupExpanded.value) {
|
||||||
note = baseNote,
|
NoteQuickActionMenu(
|
||||||
popupExpanded = popupExpanded.value,
|
note = baseNote,
|
||||||
onDismiss = { popupExpanded.value = false },
|
onDismiss = { popupExpanded.value = false },
|
||||||
accountViewModel = accountViewModel,
|
accountViewModel = accountViewModel,
|
||||||
nav = {},
|
nav = {},
|
||||||
)
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun NoteQuickActionMenu(
|
fun NoteQuickActionMenu(
|
||||||
note: Note,
|
note: Note,
|
||||||
popupExpanded: Boolean,
|
|
||||||
onDismiss: () -> Unit,
|
onDismiss: () -> Unit,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
nav: (String) -> Unit,
|
nav: (String) -> Unit,
|
||||||
@@ -173,7 +172,6 @@ fun NoteQuickActionMenu(
|
|||||||
|
|
||||||
NoteQuickActionMenu(
|
NoteQuickActionMenu(
|
||||||
note = note,
|
note = note,
|
||||||
popupExpanded = popupExpanded,
|
|
||||||
onDismiss = onDismiss,
|
onDismiss = onDismiss,
|
||||||
onWantsToEditDraft = { editDraftDialog.value = true },
|
onWantsToEditDraft = { editDraftDialog.value = true },
|
||||||
accountViewModel = accountViewModel,
|
accountViewModel = accountViewModel,
|
||||||
@@ -184,7 +182,6 @@ fun NoteQuickActionMenu(
|
|||||||
@Composable
|
@Composable
|
||||||
fun NoteQuickActionMenu(
|
fun NoteQuickActionMenu(
|
||||||
note: Note,
|
note: Note,
|
||||||
popupExpanded: Boolean,
|
|
||||||
onDismiss: () -> Unit,
|
onDismiss: () -> Unit,
|
||||||
onWantsToEditDraft: () -> Unit,
|
onWantsToEditDraft: () -> Unit,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
@@ -195,17 +192,15 @@ fun NoteQuickActionMenu(
|
|||||||
val showBlockAlertDialog = remember { mutableStateOf(false) }
|
val showBlockAlertDialog = remember { mutableStateOf(false) }
|
||||||
val showReportDialog = remember { mutableStateOf(false) }
|
val showReportDialog = remember { mutableStateOf(false) }
|
||||||
|
|
||||||
if (popupExpanded) {
|
RenderMainPopup(
|
||||||
RenderMainPopup(
|
accountViewModel,
|
||||||
accountViewModel,
|
note,
|
||||||
note,
|
onDismiss,
|
||||||
onDismiss,
|
showBlockAlertDialog,
|
||||||
showBlockAlertDialog,
|
showDeleteAlertDialog,
|
||||||
showDeleteAlertDialog,
|
showReportDialog,
|
||||||
showReportDialog,
|
onWantsToEditDraft,
|
||||||
onWantsToEditDraft,
|
)
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (showSelectTextDialog.value) {
|
if (showSelectTextDialog.value) {
|
||||||
val decryptedNote = remember { mutableStateOf<String?>(null) }
|
val decryptedNote = remember { mutableStateOf<String?>(null) }
|
||||||
@@ -267,12 +262,12 @@ private fun RenderMainPopup(
|
|||||||
|
|
||||||
val showToast = { stringResource: Int ->
|
val showToast = { stringResource: Int ->
|
||||||
scope.launch {
|
scope.launch {
|
||||||
Toast.makeText(
|
Toast
|
||||||
context,
|
.makeText(
|
||||||
context.getString(stringResource),
|
context,
|
||||||
Toast.LENGTH_SHORT,
|
context.getString(stringResource),
|
||||||
)
|
Toast.LENGTH_SHORT,
|
||||||
.show()
|
).show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.ui.note
|
package com.vitorpamplona.amethyst.ui.note
|
||||||
|
|
||||||
|
import androidx.compose.animation.Crossfade
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
@@ -40,10 +41,8 @@ import androidx.compose.material3.IconButton
|
|||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.livedata.observeAsState
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
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.draw.clip
|
||||||
@@ -51,41 +50,91 @@ import androidx.compose.ui.graphics.Color
|
|||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.theme.ShowMoreRelaysButtonBoxModifer
|
import com.vitorpamplona.amethyst.ui.theme.ShowMoreRelaysButtonBoxModifer
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.Size17Modifier
|
||||||
import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer
|
import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.noteComposeRelayBox
|
||||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
|
||||||
@OptIn(ExperimentalLayoutApi::class)
|
|
||||||
@Composable
|
@Composable
|
||||||
fun RelayBadges(
|
fun RelayBadges(
|
||||||
baseNote: Note,
|
baseNote: Note,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
nav: (String) -> Unit,
|
nav: (String) -> Unit,
|
||||||
) {
|
) {
|
||||||
var expanded by remember { mutableStateOf(false) }
|
val expanded = remember { mutableStateOf(false) }
|
||||||
|
|
||||||
val relayList by baseNote.live().relayInfo.observeAsState(baseNote.relays)
|
Crossfade(expanded.value, modifier = noteComposeRelayBox, label = "RelayBadges") {
|
||||||
|
if (it) {
|
||||||
Spacer(StdVertSpacer)
|
RenderAllRelayList(baseNote, Modifier.fillMaxWidth(), accountViewModel = accountViewModel, nav = nav)
|
||||||
|
} else {
|
||||||
// FlowRow Seems to be a lot faster than LazyVerticalGrid
|
Column {
|
||||||
Box(modifier = Modifier.fillMaxWidth().padding(start = 2.dp, end = 1.dp)) {
|
RenderClosedRelayList(baseNote, Modifier.fillMaxWidth(), accountViewModel = accountViewModel, nav = nav)
|
||||||
FlowRow(modifier = Modifier.fillMaxWidth()) {
|
ShouldShowExpandButton(baseNote, accountViewModel) { ShowMoreRelaysButton { expanded.value = true } }
|
||||||
if (expanded) {
|
|
||||||
relayList?.forEach { RenderRelay(it, accountViewModel, nav) }
|
|
||||||
} else {
|
|
||||||
relayList?.getOrNull(0)?.let { RenderRelay(it, accountViewModel, nav) }
|
|
||||||
relayList?.getOrNull(1)?.let { RenderRelay(it, accountViewModel, nav) }
|
|
||||||
relayList?.getOrNull(2)?.let { RenderRelay(it, accountViewModel, nav) }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (relayList.size > 3 && !expanded) {
|
@OptIn(ExperimentalLayoutApi::class)
|
||||||
ShowMoreRelaysButton { expanded = true }
|
@Composable
|
||||||
|
fun RenderAllRelayList(
|
||||||
|
baseNote: Note,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
verticalArrangement: Arrangement.Vertical = Arrangement.Top,
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
|
nav: (String) -> Unit,
|
||||||
|
) {
|
||||||
|
val noteRelays by baseNote
|
||||||
|
.flow()
|
||||||
|
.relays.stateFlow
|
||||||
|
.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
|
FlowRow(modifier, verticalArrangement = verticalArrangement) {
|
||||||
|
noteRelays.note.relays.forEach { RenderRelay(it, accountViewModel, nav) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun RenderClosedRelayList(
|
||||||
|
baseNote: Note,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
verticalAlignment: Alignment.Vertical = Alignment.Top,
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
|
nav: (String) -> Unit,
|
||||||
|
) {
|
||||||
|
Row(modifier, verticalAlignment = verticalAlignment) {
|
||||||
|
WatchAndRenderRelay(baseNote, 0, accountViewModel, nav)
|
||||||
|
WatchAndRenderRelay(baseNote, 1, accountViewModel, nav)
|
||||||
|
WatchAndRenderRelay(baseNote, 2, accountViewModel, nav)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun WatchAndRenderRelay(
|
||||||
|
baseNote: Note,
|
||||||
|
relayIndex: Int,
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
|
nav: (String) -> Unit,
|
||||||
|
) {
|
||||||
|
val noteRelays by baseNote
|
||||||
|
.flow()
|
||||||
|
.relays.stateFlow
|
||||||
|
.map {
|
||||||
|
it.note.relays.getOrNull(relayIndex)
|
||||||
|
}.collectAsStateWithLifecycle(
|
||||||
|
baseNote.relays.getOrNull(relayIndex),
|
||||||
|
)
|
||||||
|
|
||||||
|
Crossfade(targetState = noteRelays, label = "RenderRelay", modifier = Size17Modifier) {
|
||||||
|
if (it != null) {
|
||||||
|
RenderRelay(it, accountViewModel, nav)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,46 +146,79 @@ fun RelayIconLayoutPreview() {
|
|||||||
Spacer(StdVertSpacer)
|
Spacer(StdVertSpacer)
|
||||||
|
|
||||||
// FlowRow Seems to be a lot faster than LazyVerticalGrid
|
// FlowRow Seems to be a lot faster than LazyVerticalGrid
|
||||||
Box(modifier = Modifier.fillMaxWidth().padding(start = 2.dp, end = 1.dp)) {
|
Box(
|
||||||
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(start = 2.dp, end = 1.dp),
|
||||||
|
) {
|
||||||
FlowRow {
|
FlowRow {
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier.size(17.dp),
|
modifier = Modifier.size(17.dp),
|
||||||
contentAlignment = Alignment.Center,
|
contentAlignment = Alignment.Center,
|
||||||
) {
|
) {
|
||||||
Box(
|
Box(
|
||||||
Modifier.size(13.dp).clip(shape = CircleShape).background(Color.Black),
|
Modifier
|
||||||
|
.size(13.dp)
|
||||||
|
.clip(shape = CircleShape)
|
||||||
|
.background(Color.Black),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier.size(17.dp),
|
modifier = Modifier.size(17.dp),
|
||||||
contentAlignment = Alignment.Center,
|
contentAlignment = Alignment.Center,
|
||||||
) {
|
) {
|
||||||
Box(Modifier.size(13.dp).clip(shape = CircleShape).background(Color.Black))
|
Box(
|
||||||
|
Modifier
|
||||||
|
.size(13.dp)
|
||||||
|
.clip(shape = CircleShape)
|
||||||
|
.background(Color.Black),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier.size(17.dp),
|
modifier = Modifier.size(17.dp),
|
||||||
contentAlignment = Alignment.Center,
|
contentAlignment = Alignment.Center,
|
||||||
) {
|
) {
|
||||||
Box(Modifier.size(13.dp).clip(shape = CircleShape).background(Color.Black))
|
Box(
|
||||||
|
Modifier
|
||||||
|
.size(13.dp)
|
||||||
|
.clip(shape = CircleShape)
|
||||||
|
.background(Color.Black),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier.size(17.dp),
|
modifier = Modifier.size(17.dp),
|
||||||
contentAlignment = Alignment.Center,
|
contentAlignment = Alignment.Center,
|
||||||
) {
|
) {
|
||||||
Box(Modifier.size(13.dp).clip(shape = CircleShape).background(Color.Black))
|
Box(
|
||||||
|
Modifier
|
||||||
|
.size(13.dp)
|
||||||
|
.clip(shape = CircleShape)
|
||||||
|
.background(Color.Black),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier.size(17.dp),
|
modifier = Modifier.size(17.dp),
|
||||||
contentAlignment = Alignment.Center,
|
contentAlignment = Alignment.Center,
|
||||||
) {
|
) {
|
||||||
Box(Modifier.size(13.dp).clip(shape = CircleShape).background(Color.Black))
|
Box(
|
||||||
|
Modifier
|
||||||
|
.size(13.dp)
|
||||||
|
.clip(shape = CircleShape)
|
||||||
|
.background(Color.Black),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier.size(17.dp),
|
modifier = Modifier.size(17.dp),
|
||||||
contentAlignment = Alignment.Center,
|
contentAlignment = Alignment.Center,
|
||||||
) {
|
) {
|
||||||
Box(Modifier.size(13.dp).clip(shape = CircleShape).background(Color.Black))
|
Box(
|
||||||
|
Modifier
|
||||||
|
.size(13.dp)
|
||||||
|
.clip(shape = CircleShape)
|
||||||
|
.background(Color.Black),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,32 +21,24 @@
|
|||||||
package com.vitorpamplona.amethyst.ui.note
|
package com.vitorpamplona.amethyst.ui.note
|
||||||
|
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
|
||||||
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.ExperimentalLayoutApi
|
|
||||||
import androidx.compose.foundation.layout.FlowRow
|
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.ChevronRight
|
import androidx.compose.material.icons.filled.ChevronRight
|
||||||
import androidx.compose.material.ripple.rememberRipple
|
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.MutableState
|
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.livedata.observeAsState
|
|
||||||
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.platform.LocalContext
|
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.semantics.Role
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import androidx.lifecycle.map
|
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
import com.vitorpamplona.amethyst.model.RelayBriefInfoCache
|
import com.vitorpamplona.amethyst.model.RelayBriefInfoCache
|
||||||
@@ -70,42 +62,25 @@ public fun RelayBadgesHorizontal(
|
|||||||
) {
|
) {
|
||||||
val expanded = remember { mutableStateOf(false) }
|
val expanded = remember { mutableStateOf(false) }
|
||||||
|
|
||||||
RenderRelayList(baseNote, expanded, accountViewModel, nav)
|
if (expanded.value) {
|
||||||
|
RenderAllRelayList(baseNote, StdStartPadding, verticalArrangement = Arrangement.Center, accountViewModel, nav)
|
||||||
RenderExpandButton(baseNote, expanded) { ChatRelayExpandButton { expanded.value = true } }
|
} else {
|
||||||
}
|
RenderClosedRelayList(baseNote, StdStartPadding, verticalAlignment = Alignment.CenterVertically, accountViewModel = accountViewModel, nav = nav)
|
||||||
|
ShouldShowExpandButton(baseNote, accountViewModel) {
|
||||||
@OptIn(ExperimentalLayoutApi::class)
|
ChatRelayExpandButton { expanded.value = true }
|
||||||
@Composable
|
|
||||||
fun RenderRelayList(
|
|
||||||
baseNote: Note,
|
|
||||||
expanded: MutableState<Boolean>,
|
|
||||||
accountViewModel: AccountViewModel,
|
|
||||||
nav: (String) -> Unit,
|
|
||||||
) {
|
|
||||||
val noteRelays by baseNote.live().relayInfo.observeAsState(baseNote.relays)
|
|
||||||
|
|
||||||
FlowRow(StdStartPadding, verticalArrangement = Arrangement.Center) {
|
|
||||||
if (expanded.value) {
|
|
||||||
noteRelays?.forEach { RenderRelay(it, accountViewModel, nav) }
|
|
||||||
} else {
|
|
||||||
noteRelays?.getOrNull(0)?.let { RenderRelay(it, accountViewModel, nav) }
|
|
||||||
noteRelays?.getOrNull(1)?.let { RenderRelay(it, accountViewModel, nav) }
|
|
||||||
noteRelays?.getOrNull(2)?.let { RenderRelay(it, accountViewModel, nav) }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun RenderExpandButton(
|
fun ShouldShowExpandButton(
|
||||||
baseNote: Note,
|
baseNote: Note,
|
||||||
expanded: MutableState<Boolean>,
|
accountViewModel: AccountViewModel,
|
||||||
content: @Composable () -> Unit,
|
content: @Composable () -> Unit,
|
||||||
) {
|
) {
|
||||||
val showExpandButton by
|
val showExpandButton by accountViewModel.createMustShowExpandButtonFlows(baseNote).collectAsStateWithLifecycle()
|
||||||
baseNote.live().relays.map { it.note.relays.size > 3 }.observeAsState(baseNote.relays.size > 3)
|
|
||||||
|
|
||||||
if (showExpandButton && !expanded.value) {
|
if (showExpandButton) {
|
||||||
content()
|
content()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -159,19 +134,11 @@ fun RenderRelay(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val context = LocalContext.current
|
|
||||||
|
|
||||||
val interactionSource = remember { MutableInteractionSource() }
|
|
||||||
val ripple = rememberRipple(bounded = false, radius = Size17dp)
|
|
||||||
|
|
||||||
val clickableModifier =
|
val clickableModifier =
|
||||||
remember(relay) {
|
remember(relay) {
|
||||||
Modifier
|
Modifier
|
||||||
.size(Size17dp)
|
.size(Size17dp)
|
||||||
.clickable(
|
.clickable(
|
||||||
role = Role.Button,
|
|
||||||
interactionSource = interactionSource,
|
|
||||||
indication = ripple,
|
|
||||||
onClick = {
|
onClick = {
|
||||||
accountViewModel.retrieveRelayDocument(
|
accountViewModel.retrieveRelayDocument(
|
||||||
relay.url,
|
relay.url,
|
||||||
@@ -179,40 +146,23 @@ fun RenderRelay(
|
|||||||
openRelayDialog = true
|
openRelayDialog = true
|
||||||
},
|
},
|
||||||
onError = { url, errorCode, exceptionMessage ->
|
onError = { url, errorCode, exceptionMessage ->
|
||||||
val msg =
|
accountViewModel.toast(
|
||||||
|
R.string.unable_to_download_relay_document,
|
||||||
when (errorCode) {
|
when (errorCode) {
|
||||||
Nip11Retriever.ErrorCode.FAIL_TO_ASSEMBLE_URL ->
|
Nip11Retriever.ErrorCode.FAIL_TO_ASSEMBLE_URL ->
|
||||||
context.getString(
|
R.string.relay_information_document_error_failed_to_assemble_url
|
||||||
R.string.relay_information_document_error_assemble_url,
|
|
||||||
url,
|
|
||||||
exceptionMessage,
|
|
||||||
)
|
|
||||||
|
|
||||||
Nip11Retriever.ErrorCode.FAIL_TO_REACH_SERVER ->
|
Nip11Retriever.ErrorCode.FAIL_TO_REACH_SERVER ->
|
||||||
context.getString(
|
R.string.relay_information_document_error_failed_to_reach_server
|
||||||
R.string.relay_information_document_error_assemble_url,
|
|
||||||
url,
|
|
||||||
exceptionMessage,
|
|
||||||
)
|
|
||||||
|
|
||||||
Nip11Retriever.ErrorCode.FAIL_TO_PARSE_RESULT ->
|
Nip11Retriever.ErrorCode.FAIL_TO_PARSE_RESULT ->
|
||||||
context.getString(
|
R.string.relay_information_document_error_failed_to_parse_response
|
||||||
R.string.relay_information_document_error_assemble_url,
|
|
||||||
url,
|
|
||||||
exceptionMessage,
|
|
||||||
)
|
|
||||||
|
|
||||||
Nip11Retriever.ErrorCode.FAIL_WITH_HTTP_STATUS ->
|
Nip11Retriever.ErrorCode.FAIL_WITH_HTTP_STATUS ->
|
||||||
context.getString(
|
R.string.relay_information_document_error_failed_with_http
|
||||||
R.string.relay_information_document_error_assemble_url,
|
},
|
||||||
url,
|
url,
|
||||||
exceptionMessage,
|
exceptionMessage ?: errorCode.toString(),
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
accountViewModel.toast(
|
|
||||||
context.getString(R.string.unable_to_download_relay_document),
|
|
||||||
msg,
|
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -111,6 +111,7 @@ import kotlinx.coroutines.flow.SharingStarted
|
|||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.combineTransform
|
import kotlinx.coroutines.flow.combineTransform
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.stateIn
|
import kotlinx.coroutines.flow.stateIn
|
||||||
import kotlinx.coroutines.joinAll
|
import kotlinx.coroutines.joinAll
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@@ -322,6 +323,23 @@ class AccountViewModel(
|
|||||||
noteIsHiddenFlows.put(note, it)
|
noteIsHiddenFlows.put(note, it)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val noteMustShowExpandButtonFlows = LruCache<Note, StateFlow<Boolean>>(300)
|
||||||
|
|
||||||
|
fun createMustShowExpandButtonFlows(note: Note): StateFlow<Boolean> =
|
||||||
|
noteMustShowExpandButtonFlows.get(note)
|
||||||
|
?: note
|
||||||
|
.flow()
|
||||||
|
.relays
|
||||||
|
.stateFlow
|
||||||
|
.map { it.note.relays.size > 3 }
|
||||||
|
.stateIn(
|
||||||
|
viewModelScope,
|
||||||
|
SharingStarted.Eagerly,
|
||||||
|
note.relays.size > 3,
|
||||||
|
).also {
|
||||||
|
noteMustShowExpandButtonFlows.put(note, it)
|
||||||
|
}
|
||||||
|
|
||||||
fun hasReactedTo(
|
fun hasReactedTo(
|
||||||
baseNote: Note,
|
baseNote: Note,
|
||||||
reaction: String,
|
reaction: String,
|
||||||
|
|||||||
@@ -26,8 +26,6 @@ import androidx.compose.foundation.ExperimentalFoundationApi
|
|||||||
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.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
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.fillMaxHeight
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
@@ -49,7 +47,6 @@ import androidx.compose.runtime.DisposableEffect
|
|||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.MutableState
|
import androidx.compose.runtime.MutableState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.livedata.observeAsState
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
@@ -74,7 +71,7 @@ import com.vitorpamplona.amethyst.ui.note.CheckHiddenFeedWatchBlockAndReport
|
|||||||
import com.vitorpamplona.amethyst.ui.note.LikeReaction
|
import com.vitorpamplona.amethyst.ui.note.LikeReaction
|
||||||
import com.vitorpamplona.amethyst.ui.note.NoteAuthorPicture
|
import com.vitorpamplona.amethyst.ui.note.NoteAuthorPicture
|
||||||
import com.vitorpamplona.amethyst.ui.note.NoteUsernameDisplay
|
import com.vitorpamplona.amethyst.ui.note.NoteUsernameDisplay
|
||||||
import com.vitorpamplona.amethyst.ui.note.RenderRelay
|
import com.vitorpamplona.amethyst.ui.note.RenderAllRelayList
|
||||||
import com.vitorpamplona.amethyst.ui.note.ReplyReaction
|
import com.vitorpamplona.amethyst.ui.note.ReplyReaction
|
||||||
import com.vitorpamplona.amethyst.ui.note.ViewCountReaction
|
import com.vitorpamplona.amethyst.ui.note.ViewCountReaction
|
||||||
import com.vitorpamplona.amethyst.ui.note.ZapReaction
|
import com.vitorpamplona.amethyst.ui.note.ZapReaction
|
||||||
@@ -312,7 +309,9 @@ private fun RenderAuthorInformation(
|
|||||||
Spacer(modifier = DoubleHorzSpacer)
|
Spacer(modifier = DoubleHorzSpacer)
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
Modifier.height(65.dp).weight(1f),
|
Modifier
|
||||||
|
.height(65.dp)
|
||||||
|
.weight(1f),
|
||||||
verticalArrangement = Arrangement.Center,
|
verticalArrangement = Arrangement.Center,
|
||||||
) {
|
) {
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
@@ -332,7 +331,7 @@ private fun RenderAuthorInformation(
|
|||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
modifier = Modifier.padding(top = 2.dp),
|
modifier = Modifier.padding(top = 2.dp),
|
||||||
) {
|
) {
|
||||||
RelayBadges(baseNote = note, accountViewModel, nav)
|
RenderAllRelayList(baseNote = note, accountViewModel = accountViewModel, nav = nav)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -369,18 +368,6 @@ private fun VideoUserOptionAction(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalLayoutApi::class)
|
|
||||||
@Composable
|
|
||||||
private fun RelayBadges(
|
|
||||||
baseNote: Note,
|
|
||||||
accountViewModel: AccountViewModel,
|
|
||||||
nav: (String) -> Unit,
|
|
||||||
) {
|
|
||||||
val noteRelays by baseNote.live().relayInfo.observeAsState(baseNote.relays)
|
|
||||||
|
|
||||||
FlowRow { noteRelays?.forEach { relayInfo -> RenderRelay(relayInfo, accountViewModel, nav) } }
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ReactionsColumn(
|
fun ReactionsColumn(
|
||||||
baseNote: Note,
|
baseNote: Note,
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import androidx.compose.foundation.layout.PaddingValues
|
|||||||
import androidx.compose.foundation.layout.defaultMinSize
|
import androidx.compose.foundation.layout.defaultMinSize
|
||||||
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.width
|
import androidx.compose.foundation.layout.width
|
||||||
@@ -167,7 +168,7 @@ val HeaderPictureModifier = Modifier.size(34.dp).clip(shape = CircleShape)
|
|||||||
|
|
||||||
val ShowMoreRelaysButtonIconButtonModifier = Modifier.size(15.dp)
|
val ShowMoreRelaysButtonIconButtonModifier = Modifier.size(15.dp)
|
||||||
val ShowMoreRelaysButtonIconModifier = Modifier.size(20.dp)
|
val ShowMoreRelaysButtonIconModifier = Modifier.size(20.dp)
|
||||||
val ShowMoreRelaysButtonBoxModifer = Modifier.fillMaxWidth().height(17.dp)
|
val ShowMoreRelaysButtonBoxModifer = Modifier.width(55.dp).height(17.dp)
|
||||||
|
|
||||||
val ChatBubbleMaxSizeModifier = Modifier.fillMaxWidth(0.85f)
|
val ChatBubbleMaxSizeModifier = Modifier.fillMaxWidth(0.85f)
|
||||||
|
|
||||||
@@ -194,7 +195,8 @@ val ButtonPadding = PaddingValues(vertical = 6.dp, horizontal = 16.dp)
|
|||||||
|
|
||||||
val ChatPaddingInnerQuoteModifier = Modifier.padding(top = 10.dp)
|
val ChatPaddingInnerQuoteModifier = Modifier.padding(top = 10.dp)
|
||||||
val ChatPaddingModifier =
|
val ChatPaddingModifier =
|
||||||
Modifier.fillMaxWidth(1f)
|
Modifier
|
||||||
|
.fillMaxWidth(1f)
|
||||||
.padding(
|
.padding(
|
||||||
start = 12.dp,
|
start = 12.dp,
|
||||||
end = 12.dp,
|
end = 12.dp,
|
||||||
@@ -217,7 +219,8 @@ val imageHeaderBannerSize = Modifier.fillMaxWidth().height(150.dp)
|
|||||||
val authorNotePictureForImageHeader = Modifier.size(75.dp).padding(10.dp)
|
val authorNotePictureForImageHeader = Modifier.size(75.dp).padding(10.dp)
|
||||||
|
|
||||||
val normalWithTopMarginNoteModifier =
|
val normalWithTopMarginNoteModifier =
|
||||||
Modifier.fillMaxWidth()
|
Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
.padding(
|
.padding(
|
||||||
start = 12.dp,
|
start = 12.dp,
|
||||||
end = 12.dp,
|
end = 12.dp,
|
||||||
@@ -225,7 +228,8 @@ val normalWithTopMarginNoteModifier =
|
|||||||
)
|
)
|
||||||
|
|
||||||
val boostedNoteModifier =
|
val boostedNoteModifier =
|
||||||
Modifier.fillMaxWidth()
|
Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
.padding(
|
.padding(
|
||||||
start = 0.dp,
|
start = 0.dp,
|
||||||
end = 0.dp,
|
end = 0.dp,
|
||||||
@@ -258,3 +262,5 @@ val incognitoIconModifier =
|
|||||||
.size(14.dp)
|
.size(14.dp)
|
||||||
|
|
||||||
val hashVerifierMark = Modifier.width(40.dp).height(40.dp).padding(10.dp)
|
val hashVerifierMark = Modifier.width(40.dp).height(40.dp).padding(10.dp)
|
||||||
|
|
||||||
|
val noteComposeRelayBox = Modifier.width(55.dp).heightIn(min = 17.dp).padding(start = 2.dp, end = 1.dp)
|
||||||
|
|||||||
@@ -600,6 +600,12 @@
|
|||||||
<string name="error_dialog_button_ok">OK</string>
|
<string name="error_dialog_button_ok">OK</string>
|
||||||
|
|
||||||
<string name="relay_information_document_error_assemble_url">Failed to reach %1$s: %2$s</string>
|
<string name="relay_information_document_error_assemble_url">Failed to reach %1$s: %2$s</string>
|
||||||
|
|
||||||
|
<string name="relay_information_document_error_failed_to_assemble_url">Failed to assemble NIP-11 url for %1$s: %2$s</string>
|
||||||
|
<string name="relay_information_document_error_failed_to_reach_server">Failed to reach %1$s: %2$s</string>
|
||||||
|
<string name="relay_information_document_error_failed_to_parse_response">Failed to parse response from %1$s: %2$s</string>
|
||||||
|
<string name="relay_information_document_error_failed_with_http">Relay declined requrest %1$s: %2$s</string>
|
||||||
|
|
||||||
<string name="relay_information_document_error_reach_server">Failed to reach %1$s: %2$s</string>
|
<string name="relay_information_document_error_reach_server">Failed to reach %1$s: %2$s</string>
|
||||||
<string name="relay_information_document_error_parse_result">Failed to parse result from %1$s: %2$s</string>
|
<string name="relay_information_document_error_parse_result">Failed to parse result from %1$s: %2$s</string>
|
||||||
<string name="relay_information_document_error_http_status">%1$s failed with code %2$s</string>
|
<string name="relay_information_document_error_http_status">%1$s failed with code %2$s</string>
|
||||||
|
|||||||
Reference in New Issue
Block a user