Refines relay icon row compose

This commit is contained in:
Vitor Pamplona
2023-09-07 11:11:10 -04:00
parent 5c797858ba
commit 1465ce112c
9 changed files with 59 additions and 112 deletions
@@ -1686,6 +1686,10 @@ class Account(
.toTypedArray() .toTypedArray()
} }
fun activeWriteRelays(): List<Relay> {
return (activeRelays() ?: convertLocalRelays()).filter { it.write }
}
fun reconnectIfRelaysHaveChanged() { fun reconnectIfRelaysHaveChanged() {
val newRelaySet = activeRelays() ?: convertLocalRelays() val newRelaySet = activeRelays() ?: convertLocalRelays()
if (!Client.isSameRelaySetConfig(newRelaySet)) { if (!Client.isSameRelaySetConfig(newRelaySet)) {
@@ -734,6 +734,12 @@ class NoteLiveSet(u: Note) {
it.note.boosts.toImmutableList() it.note.boosts.toImmutableList()
}.distinctUntilChanged() }.distinctUntilChanged()
val relayInfo = innerRelays.map {
it.note.relays.map {
RelayBriefInfo(it)
}.toImmutableList()
}
fun isInUse(): Boolean { fun isInUse(): Boolean {
return metadata.hasObservers() || return metadata.hasObservers() ||
reactions.hasObservers() || reactions.hasObservers() ||
@@ -812,3 +818,10 @@ class NoteLoadingLiveData<Y>(val note: Note, initialValue: Y?) : MediatorLiveDat
@Immutable @Immutable
class NoteState(val note: Note) class NoteState(val note: Note)
@Immutable
data class RelayBriefInfo(
val url: String,
val displayUrl: String = url.trim().removePrefix("wss://").removePrefix("ws://").removeSuffix("/").intern(),
val favIcon: String = "https://$displayUrl/favicon.ico".intern()
)
@@ -79,13 +79,7 @@ fun NewMediaView(uri: Uri, onClose: () -> Unit, postViewModel: NewMediaModel, ac
var showRelaysDialog by remember { var showRelaysDialog by remember {
mutableStateOf(false) mutableStateOf(false)
} }
var relayList = account.activeRelays()?.filter { var relayList = account.activeWriteRelays()
it.write
}?.map {
it
} ?: account.convertLocalRelays().filter {
it.write
}
Dialog( Dialog(
onDismissRequest = { onClose() }, onDismissRequest = { onClose() },
@@ -101,7 +95,7 @@ fun NewMediaView(uri: Uri, onClose: () -> Unit, postViewModel: NewMediaModel, ac
) { ) {
if (showRelaysDialog) { if (showRelaysDialog) {
RelaySelectionDialog( RelaySelectionDialog(
list = relayList, preSelectedList = relayList,
onClose = { onClose = {
showRelaysDialog = false showRelaysDialog = false
}, },
@@ -133,13 +133,7 @@ fun NewPostView(
var showRelaysDialog by remember { var showRelaysDialog by remember {
mutableStateOf(false) mutableStateOf(false)
} }
var relayList = account.activeRelays()?.filter { var relayList = account.activeWriteRelays()
it.write
}?.map {
it
} ?: account.convertLocalRelays().filter {
it.write
}
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
postViewModel.load(account, baseReplyTo, quote) postViewModel.load(account, baseReplyTo, quote)
@@ -169,7 +163,7 @@ fun NewPostView(
) { ) {
if (showRelaysDialog) { if (showRelaysDialog) {
RelaySelectionDialog( RelaySelectionDialog(
list = relayList, preSelectedList = relayList,
onClose = { onClose = {
showRelaysDialog = false showRelaysDialog = false
}, },
@@ -29,6 +29,7 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties import androidx.compose.ui.window.DialogProperties
import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.RelayBriefInfo
import com.vitorpamplona.amethyst.model.RelayInformation import com.vitorpamplona.amethyst.model.RelayInformation
import com.vitorpamplona.amethyst.service.relays.Relay import com.vitorpamplona.amethyst.service.relays.Relay
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
@@ -36,12 +37,13 @@ import kotlinx.coroutines.launch
data class RelayList( data class RelayList(
val relay: Relay, val relay: Relay,
val relayInfo: RelayBriefInfo,
val isSelected: Boolean val isSelected: Boolean
) )
@Composable @Composable
fun RelaySelectionDialog( fun RelaySelectionDialog(
list: List<Relay>, preSelectedList: List<Relay>,
onClose: () -> Unit, onClose: () -> Unit,
onPost: (list: List<Relay>) -> Unit, onPost: (list: List<Relay>) -> Unit,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
@@ -49,20 +51,14 @@ fun RelaySelectionDialog(
) { ) {
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val context = LocalContext.current val context = LocalContext.current
val relayList = accountViewModel.account.activeRelays()?.filter {
it.write
}?.map {
it
} ?: accountViewModel.account.convertLocalRelays().filter {
it.write
}
var relays by remember { var relays by remember {
mutableStateOf( mutableStateOf(
relayList.map { accountViewModel.account.activeWriteRelays().map {
RelayList( RelayList(
it, relay = it,
list.any { relay -> it.url == relay.url } relayInfo = RelayBriefInfo(it.url),
isSelected = preSelectedList.any { relay -> it.url == relay.url }
) )
} }
) )
@@ -115,14 +111,14 @@ fun RelaySelectionDialog(
} }
) )
PostButton( SaveButton(
onPost = { onPost = {
val selectedRelays = relays.filter { it.isSelected } val selectedRelays = relays.filter { it.isSelected }
if (selectedRelays.isEmpty()) { if (selectedRelays.isEmpty()) {
scope.launch { scope.launch {
Toast.makeText(context, context.getString(R.string.select_a_relay_to_continue), Toast.LENGTH_SHORT).show() Toast.makeText(context, context.getString(R.string.select_a_relay_to_continue), Toast.LENGTH_SHORT).show()
} }
return@PostButton return@SaveButton
} }
onPost(selectedRelays.map { it.relay }) onPost(selectedRelays.map { it.relay })
onClose() onClose()
@@ -153,10 +149,7 @@ fun RelaySelectionDialog(
key = { _, item -> item.relay.url } key = { _, item -> item.relay.url }
) { index, item -> ) { index, item ->
RelaySwitch( RelaySwitch(
text = item.relay.url text = item.relayInfo.displayUrl,
.removePrefix("ws://")
.removePrefix("wss://")
.removeSuffix("/"),
checked = item.isSelected, checked = item.isSelected,
onClick = { onClick = {
relays = relays.mapIndexed { j, item -> relays = relays.mapIndexed { j, item ->
@@ -82,6 +82,7 @@ import com.vitorpamplona.amethyst.model.Channel
import com.vitorpamplona.amethyst.model.ConnectivityType import com.vitorpamplona.amethyst.model.ConnectivityType
import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.RelayBriefInfo
import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.service.OnlineChecker import com.vitorpamplona.amethyst.service.OnlineChecker
import com.vitorpamplona.amethyst.service.ReverseGeoLocationUtil import com.vitorpamplona.amethyst.service.ReverseGeoLocationUtil
@@ -1624,9 +1625,9 @@ fun DisplayRelaySet(
) { ) {
val noteEvent = baseNote.event as? RelaySetEvent ?: return val noteEvent = baseNote.event as? RelaySetEvent ?: return
val relays by remember { val relays by remember(baseNote) {
mutableStateOf<ImmutableList<String>>( mutableStateOf(
noteEvent.relays().toImmutableList() noteEvent.relays().map { RelayBriefInfo(it) }.toImmutableList()
) )
} }
@@ -1681,7 +1682,7 @@ fun DisplayRelaySet(
toMembersShow.forEach { relay -> toMembersShow.forEach { relay ->
Row(modifier = Modifier.fillMaxWidth(), verticalAlignment = CenterVertically) { Row(modifier = Modifier.fillMaxWidth(), verticalAlignment = CenterVertically) {
Text( Text(
relay.trim().removePrefix("wss://").removePrefix("ws://").removeSuffix("/"), text = relay.displayUrl,
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
maxLines = 1, maxLines = 1,
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis,
@@ -1691,7 +1692,7 @@ fun DisplayRelaySet(
) )
Column(modifier = Modifier.padding(start = 10.dp)) { Column(modifier = Modifier.padding(start = 10.dp)) {
RelayOptionsAction(relay, accountViewModel, nav) RelayOptionsAction(relay.url, accountViewModel, nav)
} }
} }
} }
@@ -11,17 +11,16 @@ import androidx.compose.material.MaterialTheme
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ExpandMore import androidx.compose.material.icons.filled.ExpandMore
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.Stable import androidx.compose.runtime.Stable
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.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.ui.screen.equalImmutableLists import com.vitorpamplona.amethyst.model.RelayBriefInfo
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.theme.DoubleVertSpacer import com.vitorpamplona.amethyst.ui.theme.DoubleVertSpacer
import com.vitorpamplona.amethyst.ui.theme.ShowMoreRelaysButtonBoxModifer import com.vitorpamplona.amethyst.ui.theme.ShowMoreRelaysButtonBoxModifer
@@ -29,80 +28,40 @@ import com.vitorpamplona.amethyst.ui.theme.ShowMoreRelaysButtonIconButtonModifie
import com.vitorpamplona.amethyst.ui.theme.ShowMoreRelaysButtonIconModifier import com.vitorpamplona.amethyst.ui.theme.ShowMoreRelaysButtonIconModifier
import com.vitorpamplona.amethyst.ui.theme.placeholderText import com.vitorpamplona.amethyst.ui.theme.placeholderText
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@Composable @Composable
public fun RelayBadges(baseNote: Note, accountViewModel: AccountViewModel, nav: (String) -> Unit) { public fun RelayBadges(baseNote: Note, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
var expanded by remember { mutableStateOf(false) } var expanded by remember { mutableStateOf(false) }
var showShowMore by remember { mutableStateOf(false) }
var lazyRelayList by remember { val relayList by baseNote.live().relayInfo.observeAsState(persistentListOf())
val baseNumber = baseNote.relays.map { val shortRelayList by remember {
it.removePrefix("wss://").removePrefix("ws://") derivedStateOf {
}.toImmutableList() relayList.take(3).toImmutableList()
mutableStateOf(baseNumber)
}
var shortRelayList by remember {
mutableStateOf(lazyRelayList.take(3).toImmutableList())
}
val scope = rememberCoroutineScope()
WatchRelayLists(baseNote) { relayList ->
if (!equalImmutableLists(relayList, lazyRelayList)) {
scope.launch(Dispatchers.Main) {
lazyRelayList = relayList
shortRelayList = relayList.take(3).toImmutableList()
}
}
val nextShowMore = relayList.size > 3
if (nextShowMore != showShowMore) {
scope.launch(Dispatchers.Main) {
// only triggers recomposition when actually different
showShowMore = nextShowMore
}
} }
} }
Spacer(DoubleVertSpacer) Spacer(DoubleVertSpacer)
if (expanded) { if (expanded) {
VerticalRelayPanelWithFlow(lazyRelayList, accountViewModel, nav) VerticalRelayPanelWithFlow(relayList, accountViewModel, nav)
} else { } else {
VerticalRelayPanelWithFlow(shortRelayList, accountViewModel, nav) VerticalRelayPanelWithFlow(shortRelayList, accountViewModel, nav)
} }
if (showShowMore && !expanded) { if (relayList.size > 3 && !expanded) {
ShowMoreRelaysButton { ShowMoreRelaysButton {
expanded = true expanded = true
} }
} }
} }
@Composable
private fun WatchRelayLists(baseNote: Note, onListChanges: (ImmutableList<String>) -> Unit) {
val noteRelaysState by baseNote.live().relays.observeAsState()
LaunchedEffect(key1 = noteRelaysState) {
launch(Dispatchers.IO) {
val relayList = noteRelaysState?.note?.relays?.map {
it.removePrefix("wss://").removePrefix("ws://")
} ?: emptyList()
onListChanges(relayList.toImmutableList())
}
}
}
@OptIn(ExperimentalLayoutApi::class) @OptIn(ExperimentalLayoutApi::class)
@Composable @Composable
@Stable @Stable
private fun VerticalRelayPanelWithFlow( private fun VerticalRelayPanelWithFlow(
relays: ImmutableList<String>, relays: ImmutableList<RelayBriefInfo>,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
nav: (String) -> Unit nav: (String) -> Unit
) { ) {
@@ -17,7 +17,6 @@ import androidx.compose.material.icons.filled.ChevronRight
import androidx.compose.material.ripple.rememberRipple import androidx.compose.material.ripple.rememberRipple
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState import androidx.compose.runtime.MutableState
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.mutableStateOf import androidx.compose.runtime.mutableStateOf
@@ -33,6 +32,7 @@ import androidx.compose.ui.unit.dp
import androidx.lifecycle.map 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.RelayBriefInfo
import com.vitorpamplona.amethyst.model.RelayInformation import com.vitorpamplona.amethyst.model.RelayInformation
import com.vitorpamplona.amethyst.ui.actions.RelayInformationDialog import com.vitorpamplona.amethyst.ui.actions.RelayInformationDialog
import com.vitorpamplona.amethyst.ui.actions.loadRelayInfo import com.vitorpamplona.amethyst.ui.actions.loadRelayInfo
@@ -44,6 +44,7 @@ import com.vitorpamplona.amethyst.ui.theme.Size15Modifier
import com.vitorpamplona.amethyst.ui.theme.Size15dp import com.vitorpamplona.amethyst.ui.theme.Size15dp
import com.vitorpamplona.amethyst.ui.theme.StdStartPadding import com.vitorpamplona.amethyst.ui.theme.StdStartPadding
import com.vitorpamplona.amethyst.ui.theme.placeholderText import com.vitorpamplona.amethyst.ui.theme.placeholderText
import kotlinx.collections.immutable.toImmutableList
@Composable @Composable
public fun RelayBadgesHorizontal(baseNote: Note, accountViewModel: AccountViewModel, nav: (String) -> Unit) { public fun RelayBadgesHorizontal(baseNote: Note, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
@@ -59,15 +60,13 @@ public fun RelayBadgesHorizontal(baseNote: Note, accountViewModel: AccountViewMo
@OptIn(ExperimentalLayoutApi::class) @OptIn(ExperimentalLayoutApi::class)
@Composable @Composable
fun RenderRelayList(baseNote: Note, expanded: MutableState<Boolean>, accountViewModel: AccountViewModel, nav: (String) -> Unit) { fun RenderRelayList(baseNote: Note, expanded: MutableState<Boolean>, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
val noteRelays by baseNote.live().relays.map { val noteRelays by baseNote.live().relayInfo.observeAsState()
it.note.relays
}.observeAsState(baseNote.relays)
FlowRow(StdStartPadding) { FlowRow(StdStartPadding) {
val relaysToDisplay = remember(noteRelays, expanded.value) { val relaysToDisplay = remember(noteRelays, expanded.value) {
if (expanded.value) noteRelays else noteRelays.take(3) if (expanded.value) noteRelays else noteRelays?.take(3)?.toImmutableList()
} }
relaysToDisplay.forEach { relaysToDisplay?.forEach {
RenderRelay(it, accountViewModel, nav) RenderRelay(it, accountViewModel, nav)
} }
} }
@@ -104,14 +103,7 @@ fun ChatRelayExpandButton(onClick: () -> Unit) {
} }
@Composable @Composable
fun RenderRelay(dirtyUrl: String, accountViewModel: AccountViewModel, nav: (String) -> Unit) { fun RenderRelay(relay: RelayBriefInfo, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
val iconUrl by remember(dirtyUrl) {
derivedStateOf {
val cleanUrl = dirtyUrl.trim().removePrefix("wss://").removePrefix("ws://").removeSuffix("/")
"https://$cleanUrl/favicon.ico"
}
}
var relayInfo: RelayInformation? by remember { mutableStateOf(null) } var relayInfo: RelayInformation? by remember { mutableStateOf(null) }
if (relayInfo != null) { if (relayInfo != null) {
@@ -130,7 +122,7 @@ fun RenderRelay(dirtyUrl: String, accountViewModel: AccountViewModel, nav: (Stri
val interactionSource = remember { MutableInteractionSource() } val interactionSource = remember { MutableInteractionSource() }
val ripple = rememberRipple(bounded = false, radius = Size15dp) val ripple = rememberRipple(bounded = false, radius = Size15dp)
val clickableModifier = remember(dirtyUrl) { val clickableModifier = remember(relay) {
Modifier Modifier
.padding(1.dp) .padding(1.dp)
.size(Size15dp) .size(Size15dp)
@@ -139,7 +131,7 @@ fun RenderRelay(dirtyUrl: String, accountViewModel: AccountViewModel, nav: (Stri
interactionSource = interactionSource, interactionSource = interactionSource,
indication = ripple, indication = ripple,
onClick = { onClick = {
loadRelayInfo(dirtyUrl, context, scope) { loadRelayInfo(relay.url, context, scope) {
relayInfo = it relayInfo = it
} }
} }
@@ -149,7 +141,7 @@ fun RenderRelay(dirtyUrl: String, accountViewModel: AccountViewModel, nav: (Stri
Box( Box(
modifier = clickableModifier modifier = clickableModifier
) { ) {
RenderRelayIcon(iconUrl) RenderRelayIcon(relay.favIcon)
} }
} }
@@ -416,14 +416,11 @@ private fun VideoUserOptionAction(
@OptIn(ExperimentalLayoutApi::class) @OptIn(ExperimentalLayoutApi::class)
@Composable @Composable
private fun RelayBadges(baseNote: Note, accountViewModel: AccountViewModel, nav: (String) -> Unit) { private fun RelayBadges(baseNote: Note, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
val noteRelaysState by baseNote.live().relays.observeAsState() val noteRelays by baseNote.live().relayInfo.observeAsState()
val noteRelays = remember(noteRelaysState) {
noteRelaysState?.note?.relays ?: emptySet()
}
FlowRow() { FlowRow() {
noteRelays.forEach { dirtyUrl -> noteRelays?.forEach { relayInfo ->
RenderRelay(dirtyUrl, accountViewModel, nav) RenderRelay(relayInfo, accountViewModel, nav)
} }
} }
} }