Adds performance mode for older devices.

This commit is contained in:
Vitor Pamplona
2024-06-18 09:13:48 -04:00
parent 08869ac350
commit e18945b378
64 changed files with 860 additions and 568 deletions
@@ -34,17 +34,20 @@ data class Settings(
val automaticallyShowProfilePictures: ConnectivityType = ConnectivityType.ALWAYS,
val dontShowPushNotificationSelector: Boolean = false,
val dontAskForNotificationPermissions: Boolean = false,
val featureSet: FeatureSetType = FeatureSetType.COMPLETE,
val featureSet: FeatureSetType = FeatureSetType.SIMPLIFIED,
)
enum class ThemeType(val screenCode: Int, val resourceId: Int) {
enum class ThemeType(
val screenCode: Int,
val resourceId: Int,
) {
SYSTEM(0, R.string.system),
LIGHT(1, R.string.light),
DARK(2, R.string.dark),
}
fun parseThemeType(code: Int?): ThemeType {
return when (code) {
fun parseThemeType(code: Int?): ThemeType =
when (code) {
ThemeType.SYSTEM.screenCode -> ThemeType.SYSTEM
ThemeType.LIGHT.screenCode -> ThemeType.LIGHT
ThemeType.DARK.screenCode -> ThemeType.DARK
@@ -52,21 +55,28 @@ fun parseThemeType(code: Int?): ThemeType {
ThemeType.SYSTEM
}
}
}
enum class ConnectivityType(val prefCode: Boolean?, val screenCode: Int, val resourceId: Int) {
enum class ConnectivityType(
val prefCode: Boolean?,
val screenCode: Int,
val resourceId: Int,
) {
ALWAYS(null, 0, R.string.connectivity_type_always),
WIFI_ONLY(true, 1, R.string.connectivity_type_wifi_only),
NEVER(false, 2, R.string.connectivity_type_never),
}
enum class FeatureSetType(val screenCode: Int, val resourceId: Int) {
enum class FeatureSetType(
val screenCode: Int,
val resourceId: Int,
) {
COMPLETE(0, R.string.ui_feature_set_type_complete),
SIMPLIFIED(1, R.string.ui_feature_set_type_simplified),
PERFORMANCE(2, R.string.ui_feature_set_type_performance),
}
fun parseConnectivityType(code: Boolean?): ConnectivityType {
return when (code) {
fun parseConnectivityType(code: Boolean?): ConnectivityType =
when (code) {
ConnectivityType.ALWAYS.prefCode -> ConnectivityType.ALWAYS
ConnectivityType.WIFI_ONLY.prefCode -> ConnectivityType.WIFI_ONLY
ConnectivityType.NEVER.prefCode -> ConnectivityType.NEVER
@@ -74,10 +84,9 @@ fun parseConnectivityType(code: Boolean?): ConnectivityType {
ConnectivityType.ALWAYS
}
}
}
fun parseConnectivityType(screenCode: Int): ConnectivityType {
return when (screenCode) {
fun parseConnectivityType(screenCode: Int): ConnectivityType =
when (screenCode) {
ConnectivityType.ALWAYS.screenCode -> ConnectivityType.ALWAYS
ConnectivityType.WIFI_ONLY.screenCode -> ConnectivityType.WIFI_ONLY
ConnectivityType.NEVER.screenCode -> ConnectivityType.NEVER
@@ -85,39 +94,40 @@ fun parseConnectivityType(screenCode: Int): ConnectivityType {
ConnectivityType.ALWAYS
}
}
}
fun parseFeatureSetType(screenCode: Int): FeatureSetType {
return when (screenCode) {
fun parseFeatureSetType(screenCode: Int): FeatureSetType =
when (screenCode) {
FeatureSetType.COMPLETE.screenCode -> FeatureSetType.COMPLETE
FeatureSetType.SIMPLIFIED.screenCode -> FeatureSetType.SIMPLIFIED
FeatureSetType.PERFORMANCE.screenCode -> FeatureSetType.PERFORMANCE
else -> {
FeatureSetType.COMPLETE
}
}
}
enum class BooleanType(val prefCode: Boolean?, val screenCode: Int, val reourceId: Int) {
enum class BooleanType(
val prefCode: Boolean?,
val screenCode: Int,
val reourceId: Int,
) {
ALWAYS(null, 0, R.string.connectivity_type_always),
NEVER(false, 1, R.string.connectivity_type_never),
}
fun parseBooleanType(code: Boolean?): BooleanType {
return when (code) {
fun parseBooleanType(code: Boolean?): BooleanType =
when (code) {
BooleanType.ALWAYS.prefCode -> BooleanType.ALWAYS
BooleanType.NEVER.prefCode -> BooleanType.NEVER
else -> {
BooleanType.ALWAYS
}
}
}
fun parseBooleanType(screenCode: Int): BooleanType {
return when (screenCode) {
fun parseBooleanType(screenCode: Int): BooleanType =
when (screenCode) {
BooleanType.ALWAYS.screenCode -> BooleanType.ALWAYS
BooleanType.NEVER.screenCode -> BooleanType.NEVER
else -> {
BooleanType.ALWAYS
}
}
}
@@ -0,0 +1,45 @@
/**
* Copyright (c) 2024 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.amethyst.ui.actions
import androidx.compose.animation.Crossfade
import androidx.compose.animation.core.FiniteAnimationSpec
import androidx.compose.animation.core.tween
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.vitorpamplona.amethyst.model.FeatureSetType
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
@Composable
fun <T> CrossfadeIfEnabled(
targetState: T,
modifier: Modifier = Modifier,
animationSpec: FiniteAnimationSpec<Float> = tween(),
label: String = "Crossfade",
accountViewModel: AccountViewModel,
content: @Composable (T) -> Unit,
) {
if (accountViewModel.settings.featureSet == FeatureSetType.PERFORMANCE) {
content(targetState)
} else {
Crossfade(targetState, modifier, animationSpec, label, content)
}
}
@@ -73,6 +73,7 @@ import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.FeatureSetType
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.service.NostrSearchEventOrUserDataSource
@@ -331,11 +332,6 @@ private fun RenderSearchResults(
val users by searchBarViewModel.searchResultsUsers.collectAsStateWithLifecycle()
val channels by searchBarViewModel.searchResultsChannels.collectAsStateWithLifecycle()
val automaticallyShowProfilePicture =
remember {
accountViewModel.settings.showProfilePictures.value
}
Row(
modifier =
Modifier
@@ -367,7 +363,11 @@ private fun RenderSearchResults(
channels,
key = { _, item -> "c" + item.idHex },
) { _, item ->
RenderChannel(item, automaticallyShowProfilePicture) {
RenderChannel(
item,
loadProfilePicture = accountViewModel.settings.showProfilePictures.value,
loadRobohash = accountViewModel.settings.featureSet != FeatureSetType.PERFORMANCE,
) {
nav("Channel/${item.idHex}")
searchBarViewModel.clear()
}
@@ -385,6 +385,7 @@ private fun RenderSearchResults(
private fun RenderChannel(
item: com.vitorpamplona.amethyst.model.Channel,
loadProfilePicture: Boolean,
loadRobohash: Boolean,
onClick: () -> Unit,
) {
val hasNewMessages = remember { mutableStateOf(false) }
@@ -403,6 +404,7 @@ private fun RenderChannel(
hasNewMessages,
onClick = onClick,
loadProfilePicture = loadProfilePicture,
loadRobohash = loadRobohash,
)
}
@@ -414,14 +416,15 @@ fun UserComposeForChat(
) {
Row(
modifier =
Modifier.clickable(
onClick = onClick,
).padding(
start = 12.dp,
end = 12.dp,
top = 10.dp,
bottom = 10.dp,
),
Modifier
.clickable(
onClick = onClick,
).padding(
start = 12.dp,
end = 12.dp,
top = 10.dp,
bottom = 10.dp,
),
verticalAlignment = Alignment.CenterVertically,
) {
ClickableUserPicture(baseUser, Size55dp, accountViewModel)
@@ -432,7 +435,7 @@ fun UserComposeForChat(
.padding(start = 10.dp)
.weight(1f),
) {
Row(verticalAlignment = Alignment.CenterVertically) { UsernameDisplay(baseUser) }
Row(verticalAlignment = Alignment.CenterVertically) { UsernameDisplay(baseUser, accountViewModel = accountViewModel) }
DisplayUserAboutInfo(baseUser)
}
@@ -324,8 +324,7 @@ fun NewPostView(
top = pad.calculateTopPadding(),
end = Size10dp,
bottom = pad.calculateBottomPadding(),
)
.fillMaxSize(),
).fillMaxSize(),
) {
Column(
modifier =
@@ -676,8 +675,7 @@ private fun MessageField(postViewModel: NewPostViewModel) {
width = 1.dp,
color = MaterialTheme.colorScheme.surface,
shape = RoundedCornerShape(8.dp),
)
.focusRequester(focusRequester)
).focusRequester(focusRequester)
.onFocusChanged {
if (it.isFocused) {
keyboardController?.show()
@@ -1130,7 +1128,7 @@ fun FowardZapTo(
Spacer(modifier = DoubleHorzSpacer)
Column(modifier = Modifier.weight(1f)) {
UsernameDisplay(splitItem.key)
UsernameDisplay(splitItem.key, accountViewModel = accountViewModel)
Text(
text = String.format("%.0f%%", splitItem.percentage * 100),
maxLines = 1,
@@ -46,6 +46,7 @@ import com.vitorpamplona.amethyst.ui.theme.largeRelayIconModifier
fun BasicRelaySetupInfoClickableRow(
item: BasicRelaySetupInfo,
loadProfilePicture: Boolean,
loadRobohash: Boolean,
onDelete: (BasicRelaySetupInfo) -> Unit,
onClick: () -> Unit,
accountViewModel: AccountViewModel,
@@ -65,6 +66,7 @@ fun BasicRelaySetupInfoClickableRow(
item.briefInfo.displayUrl,
iconUrlFromRelayInfoDoc ?: item.briefInfo.favIcon,
loadProfilePicture,
loadRobohash,
MaterialTheme.colorScheme.largeRelayIconModifier,
)
}
@@ -27,6 +27,7 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.platform.LocalContext
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.FeatureSetType
import com.vitorpamplona.amethyst.model.RelayBriefInfoCache
import com.vitorpamplona.amethyst.service.Nip11Retriever
import com.vitorpamplona.amethyst.ui.actions.RelayInfoDialog
@@ -52,14 +53,10 @@ fun BasicRelaySetupInfoDialog(
)
}
val automaticallyShowProfilePicture =
remember {
accountViewModel.settings.showProfilePictures.value
}
BasicRelaySetupInfoClickableRow(
item = item,
loadProfilePicture = automaticallyShowProfilePicture,
loadProfilePicture = accountViewModel.settings.showProfilePictures.value,
loadRobohash = accountViewModel.settings.featureSet != FeatureSetType.PERFORMANCE,
onDelete = onDelete,
accountViewModel = accountViewModel,
onClick = {
@@ -67,6 +67,7 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.FeatureSetType
import com.vitorpamplona.amethyst.model.RelayBriefInfoCache
import com.vitorpamplona.amethyst.service.Nip11CachedRetriever
import com.vitorpamplona.amethyst.service.Nip11Retriever
@@ -146,6 +147,7 @@ fun LazyListScope.renderKind3Items(
fun ServerConfigPreview() {
ClickableRelayItem(
loadProfilePicture = true,
loadRobohash = false,
item =
Kind3BasicRelaySetupInfo(
url = "nostr.mom",
@@ -200,14 +202,10 @@ fun LoadRelayInfo(
)
}
val automaticallyShowProfilePicture =
remember {
accountViewModel.settings.showProfilePictures.value
}
ClickableRelayItem(
item = item,
loadProfilePicture = automaticallyShowProfilePicture,
loadProfilePicture = accountViewModel.settings.showProfilePictures.value,
loadRobohash = accountViewModel.settings.featureSet != FeatureSetType.PERFORMANCE,
onToggleDownload = onToggleDownload,
onToggleUpload = onToggleUpload,
onToggleFollows = onToggleFollows,
@@ -268,6 +266,7 @@ fun LoadRelayInfo(
fun ClickableRelayItem(
item: Kind3BasicRelaySetupInfo,
loadProfilePicture: Boolean,
loadRobohash: Boolean,
onToggleDownload: (Kind3BasicRelaySetupInfo) -> Unit,
onToggleUpload: (Kind3BasicRelaySetupInfo) -> Unit,
onToggleFollows: (Kind3BasicRelaySetupInfo) -> Unit,
@@ -293,6 +292,7 @@ fun ClickableRelayItem(
item.briefInfo.displayUrl,
iconUrlFromRelayInfoDoc ?: item.briefInfo.favIcon,
loadProfilePicture,
loadRobohash,
MaterialTheme.colorScheme.largeRelayIconModifier,
)
}
@@ -348,17 +348,18 @@ private fun StatusRow(
imageVector = Icons.Default.Download,
contentDescription = stringResource(R.string.read_from_relay),
modifier =
Modifier.size(15.dp)
Modifier
.size(15.dp)
.combinedClickable(
onClick = { onToggleDownload(item) },
onLongClick = {
scope.launch {
Toast.makeText(
context,
context.getString(R.string.read_from_relay),
Toast.LENGTH_SHORT,
)
.show()
Toast
.makeText(
context,
context.getString(R.string.read_from_relay),
Toast.LENGTH_SHORT,
).show()
}
},
),
@@ -384,17 +385,18 @@ private fun StatusRow(
imageVector = Icons.Default.Upload,
stringResource(R.string.write_to_relay),
modifier =
Modifier.size(15.dp)
Modifier
.size(15.dp)
.combinedClickable(
onClick = { onToggleUpload(item) },
onLongClick = {
scope.launch {
Toast.makeText(
context,
context.getString(R.string.write_to_relay),
Toast.LENGTH_SHORT,
)
.show()
Toast
.makeText(
context,
context.getString(R.string.write_to_relay),
Toast.LENGTH_SHORT,
).show()
}
},
),
@@ -420,17 +422,18 @@ private fun StatusRow(
imageVector = Icons.Default.SyncProblem,
stringResource(R.string.errors),
modifier =
Modifier.size(15.dp)
Modifier
.size(15.dp)
.combinedClickable(
onClick = {},
onLongClick = {
scope.launch {
Toast.makeText(
context,
context.getString(R.string.errors),
Toast.LENGTH_SHORT,
)
.show()
Toast
.makeText(
context,
context.getString(R.string.errors),
Toast.LENGTH_SHORT,
).show()
}
},
),
@@ -454,17 +457,18 @@ private fun StatusRow(
imageVector = Icons.Default.DeleteSweep,
stringResource(R.string.spam),
modifier =
Modifier.size(15.dp)
Modifier
.size(15.dp)
.combinedClickable(
onClick = {},
onLongClick = {
scope.launch {
Toast.makeText(
context,
context.getString(R.string.spam),
Toast.LENGTH_SHORT,
)
.show()
Toast
.makeText(
context,
context.getString(R.string.spam),
Toast.LENGTH_SHORT,
).show()
}
},
),
@@ -514,18 +518,19 @@ private fun ActiveToggles(
painterResource(R.drawable.ic_home),
stringResource(R.string.home_feed),
modifier =
Modifier.padding(horizontal = 5.dp)
Modifier
.padding(horizontal = 5.dp)
.size(15.dp)
.combinedClickable(
onClick = { onToggleFollows(item) },
onLongClick = {
scope.launch {
Toast.makeText(
context,
context.getString(R.string.home_feed),
Toast.LENGTH_SHORT,
)
.show()
Toast
.makeText(
context,
context.getString(R.string.home_feed),
Toast.LENGTH_SHORT,
).show()
}
},
),
@@ -547,18 +552,19 @@ private fun ActiveToggles(
painterResource(R.drawable.ic_dm),
stringResource(R.string.private_message_feed),
modifier =
Modifier.padding(horizontal = 5.dp)
Modifier
.padding(horizontal = 5.dp)
.size(15.dp)
.combinedClickable(
onClick = { onTogglePrivateDMs(item) },
onLongClick = {
scope.launch {
Toast.makeText(
context,
context.getString(R.string.private_message_feed),
Toast.LENGTH_SHORT,
)
.show()
Toast
.makeText(
context,
context.getString(R.string.private_message_feed),
Toast.LENGTH_SHORT,
).show()
}
},
),
@@ -580,18 +586,19 @@ private fun ActiveToggles(
imageVector = Icons.Default.Groups,
contentDescription = stringResource(R.string.public_chat_feed),
modifier =
Modifier.padding(horizontal = 5.dp)
Modifier
.padding(horizontal = 5.dp)
.size(15.dp)
.combinedClickable(
onClick = { onTogglePublicChats(item) },
onLongClick = {
scope.launch {
Toast.makeText(
context,
context.getString(R.string.public_chat_feed),
Toast.LENGTH_SHORT,
)
.show()
Toast
.makeText(
context,
context.getString(R.string.public_chat_feed),
Toast.LENGTH_SHORT,
).show()
}
},
),
@@ -613,18 +620,19 @@ private fun ActiveToggles(
imageVector = Icons.Default.Public,
stringResource(R.string.global_feed),
modifier =
Modifier.padding(horizontal = 5.dp)
Modifier
.padding(horizontal = 5.dp)
.size(15.dp)
.combinedClickable(
onClick = { onToggleGlobal(item) },
onLongClick = {
scope.launch {
Toast.makeText(
context,
context.getString(R.string.global_feed),
Toast.LENGTH_SHORT,
)
.show()
Toast
.makeText(
context,
context.getString(R.string.global_feed),
Toast.LENGTH_SHORT,
).show()
}
},
),
@@ -20,7 +20,6 @@
*/
package com.vitorpamplona.amethyst.ui.actions.relays
import androidx.compose.animation.Crossfade
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
@@ -50,9 +49,11 @@ import androidx.compose.ui.unit.sp
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.FeatureSetType
import com.vitorpamplona.amethyst.model.RelayBriefInfoCache
import com.vitorpamplona.amethyst.service.relays.RelayStats
import com.vitorpamplona.amethyst.ui.actions.CloseButton
import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled
import com.vitorpamplona.amethyst.ui.components.ClickableEmail
import com.vitorpamplona.amethyst.ui.components.ClickableUrl
import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer
@@ -81,12 +82,13 @@ fun RelayInformationDialog(
) {
val messages =
remember(relayBriefInfo) {
RelayStats.get(url = relayBriefInfo.url).messages.snapshot().values.sortedByDescending { it.time }.toImmutableList()
}
val automaticallyShowProfilePicture =
remember {
accountViewModel.settings.showProfilePictures.value
RelayStats
.get(url = relayBriefInfo.url)
.messages
.snapshot()
.values
.sortedByDescending { it.time }
.toImmutableList()
}
Dialog(
@@ -124,10 +126,11 @@ fun RelayInformationDialog(
) {
Column {
RenderRelayIcon(
relayBriefInfo.displayUrl,
relayInfo.icon ?: relayBriefInfo.favIcon,
automaticallyShowProfilePicture,
MaterialTheme.colorScheme.largeRelayIconModifier,
displayUrl = relayBriefInfo.displayUrl,
iconUrl = relayInfo.icon ?: relayBriefInfo.favIcon,
loadProfilePicture = accountViewModel.settings.showProfilePictures.value,
loadRobohash = accountViewModel.settings.featureSet != FeatureSetType.PERFORMANCE,
iconModifier = MaterialTheme.colorScheme.largeRelayIconModifier,
)
}
@@ -346,7 +349,7 @@ private fun DisplayOwnerInformation(
nav: (String) -> Unit,
) {
LoadUser(baseUserHex = userHex, accountViewModel) {
Crossfade(it) {
CrossfadeIfEnabled(it, accountViewModel = accountViewModel) {
if (it != null) {
UserCompose(
baseUser = it,
@@ -23,7 +23,6 @@ package com.vitorpamplona.amethyst.ui.components
import android.content.Context
import android.content.Intent
import android.net.Uri
import androidx.compose.animation.Crossfade
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
@@ -69,6 +68,7 @@ import com.vitorpamplona.amethyst.commons.hashtags.CustomHashTagIcons
import com.vitorpamplona.amethyst.model.ThemeType
import com.vitorpamplona.amethyst.service.CachedCashuProcessor
import com.vitorpamplona.amethyst.service.CashuToken
import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled
import com.vitorpamplona.amethyst.ui.actions.LoadingAnimation
import com.vitorpamplona.amethyst.ui.note.CashuIcon
import com.vitorpamplona.amethyst.ui.note.CopyIcon
@@ -106,7 +106,7 @@ fun CashuPreview(
}
}
Crossfade(targetState = cashuData, label = "CashuPreview") {
CrossfadeIfEnabled(targetState = cashuData, label = "CashuPreview", accountViewModel = accountViewModel) {
when (it) {
is GenericLoadable.Loaded<CashuToken> -> CashuPreview(it.loaded, accountViewModel)
is GenericLoadable.Error<CashuToken> ->
@@ -20,7 +20,6 @@
*/
package com.vitorpamplona.amethyst.ui.components
import androidx.compose.animation.Crossfade
import androidx.compose.foundation.text.ClickableText
import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.MaterialTheme
@@ -35,21 +34,26 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.style.TextDirection
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled
import com.vitorpamplona.amethyst.ui.note.ErrorMessageDialog
import com.vitorpamplona.amethyst.ui.note.payViaIntent
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.quartz.encoders.LnWithdrawalUtil
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@Composable
fun MayBeWithdrawal(lnurlWord: String) {
fun MayBeWithdrawal(
lnurlWord: String,
accountViewModel: AccountViewModel,
) {
var lnWithdrawal by remember { mutableStateOf<String?>(null) }
LaunchedEffect(key1 = lnurlWord) {
launch(Dispatchers.IO) { lnWithdrawal = LnWithdrawalUtil.findWithdrawal(lnurlWord) }
}
Crossfade(targetState = lnWithdrawal) {
CrossfadeIfEnabled(targetState = lnWithdrawal, accountViewModel = accountViewModel) {
if (it != null) {
ClickableWithdrawal(withdrawalString = it)
} else {
@@ -0,0 +1,79 @@
/**
* Copyright (c) 2024 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.amethyst.ui.components
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.DefaultAlpha
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.graphics.painter.Painter
/**
* Create and return a new [Painter] that wraps [painter] with its [alpha], [colorFilter], or [onDraw] overwritten.
*/
fun forwardingPainter(
painter: Painter,
alpha: Float = DefaultAlpha,
colorFilter: ColorFilter? = null,
onDraw: DrawScope.(ForwardingDrawInfo) -> Unit = DefaultOnDraw,
): Painter = ForwardingPainter(painter, alpha, colorFilter, onDraw)
data class ForwardingDrawInfo(
val painter: Painter,
val alpha: Float,
val colorFilter: ColorFilter?,
)
private class ForwardingPainter(
private val painter: Painter,
private var alpha: Float,
private var colorFilter: ColorFilter?,
private val onDraw: DrawScope.(ForwardingDrawInfo) -> Unit,
) : Painter() {
private var info = newInfo()
override val intrinsicSize get() = painter.intrinsicSize
override fun applyAlpha(alpha: Float): Boolean {
if (alpha != DefaultAlpha) {
this.alpha = alpha
this.info = newInfo()
}
return true
}
override fun applyColorFilter(colorFilter: ColorFilter?): Boolean {
if (colorFilter == null) {
this.colorFilter = colorFilter
this.info = newInfo()
}
return true
}
override fun DrawScope.onDraw() = onDraw(info)
private fun newInfo() = ForwardingDrawInfo(painter, alpha, colorFilter)
}
private val DefaultOnDraw: DrawScope.(ForwardingDrawInfo) -> Unit = { info ->
with(info.painter) {
draw(size, info.alpha, info.colorFilter)
}
}
@@ -20,7 +20,6 @@
*/
package com.vitorpamplona.amethyst.ui.components
import androidx.compose.animation.Crossfade
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
@@ -54,8 +53,10 @@ import com.vitorpamplona.amethyst.commons.hashtags.CustomHashTagIcons
import com.vitorpamplona.amethyst.commons.hashtags.Lightning
import com.vitorpamplona.amethyst.service.lnurl.CachedLnInvoiceParser
import com.vitorpamplona.amethyst.service.lnurl.InvoiceAmount
import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled
import com.vitorpamplona.amethyst.ui.note.ErrorMessageDialog
import com.vitorpamplona.amethyst.ui.note.payViaIntent
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
import com.vitorpamplona.amethyst.ui.theme.QuoteBorder
import com.vitorpamplona.amethyst.ui.theme.Size20Modifier
@@ -82,9 +83,12 @@ fun LoadValueFromInvoice(
}
@Composable
fun MayBeInvoicePreview(lnbcWord: String) {
fun MayBeInvoicePreview(
lnbcWord: String,
accountViewModel: AccountViewModel,
) {
LoadValueFromInvoice(lnbcWord = lnbcWord) { invoiceAmount ->
Crossfade(targetState = invoiceAmount, label = "MayBeInvoicePreview") {
CrossfadeIfEnabled(targetState = invoiceAmount, label = "MayBeInvoicePreview", accountViewModel = accountViewModel) {
if (it != null) {
InvoicePreview(it.invoice, it.amount)
} else {
@@ -20,8 +20,6 @@
*/
package com.vitorpamplona.amethyst.ui.components
import androidx.compose.animation.Crossfade
import androidx.compose.animation.core.tween
import androidx.compose.foundation.layout.Box
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
@@ -30,6 +28,7 @@ import androidx.compose.runtime.remember
import com.vitorpamplona.amethyst.commons.richtext.MediaUrlImage
import com.vitorpamplona.amethyst.commons.richtext.MediaUrlVideo
import com.vitorpamplona.amethyst.model.UrlCachedPreviewer
import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.theme.HalfVertPadding
@@ -55,10 +54,10 @@ fun LoadUrlPreview(
}
}
Crossfade(
CrossfadeIfEnabled(
targetState = urlPreviewState,
animationSpec = tween(durationMillis = 100),
label = "UrlPreview",
accountViewModel = accountViewModel,
) { state ->
when (state) {
is UrlPreviewState.Loaded -> {
@@ -20,7 +20,6 @@
*/
package com.vitorpamplona.amethyst.ui.components
import androidx.compose.animation.Crossfade
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
@@ -86,6 +85,7 @@ import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.model.checkForHashtagWithIcon
import com.vitorpamplona.amethyst.service.CachedRichTextParser
import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled
import com.vitorpamplona.amethyst.ui.components.markdown.RenderContentAsMarkdown
import com.vitorpamplona.amethyst.ui.note.LoadUser
import com.vitorpamplona.amethyst.ui.note.NoteCompose
@@ -103,15 +103,14 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
fun isMarkdown(content: String): Boolean {
return content.startsWith("> ") ||
fun isMarkdown(content: String): Boolean =
content.startsWith("> ") ||
content.startsWith("# ") ||
content.contains("##") ||
content.contains("__") ||
content.contains("**") ||
content.contains("```") ||
content.contains("](")
}
@Composable
fun RichTextViewer(
@@ -172,8 +171,8 @@ fun RenderRegularPreview() {
// is ImageSegment -> ZoomableContentView(word.segmentText, state, accountViewModel)
// is LinkSegment -> LoadUrlPreview(word.segmentText, word.segmentText, accountViewModel)
is EmojiSegment -> RenderCustomEmoji(word.segmentText, state)
is InvoiceSegment -> MayBeInvoicePreview(word.segmentText)
is WithdrawSegment -> MayBeWithdrawal(word.segmentText)
// is InvoiceSegment -> MayBeInvoicePreview(word.segmentText)
// is WithdrawSegment -> MayBeWithdrawal(word.segmentText)
// is CashuSegment -> CashuPreview(word.segmentText, accountViewModel)
is EmailSegment -> ClickableEmail(word.segmentText)
is PhoneSegment -> ClickablePhone(word.segmentText)
@@ -209,8 +208,8 @@ fun RenderRegularPreview2() {
// is ImageSegment -> ZoomableContentView(word.segmentText, state, accountViewModel)
// is LinkSegment -> LoadUrlPreview(word.segmentText, word.segmentText, accountViewModel)
is EmojiSegment -> RenderCustomEmoji(word.segmentText, state)
is InvoiceSegment -> MayBeInvoicePreview(word.segmentText)
is WithdrawSegment -> MayBeWithdrawal(word.segmentText)
// is InvoiceSegment -> MayBeInvoicePreview(word.segmentText)
// is WithdrawSegment -> MayBeWithdrawal(word.segmentText)
// is CashuSegment -> CashuPreview(word.segmentText, accountViewModel)
is EmailSegment -> ClickableEmail(word.segmentText)
is PhoneSegment -> ClickablePhone(word.segmentText)
@@ -270,8 +269,8 @@ fun RenderRegularPreview3() {
// is ImageSegment -> ZoomableContentView(word.segmentText, state, accountViewModel)
is LinkSegment -> LoadUrlPreview(word.segmentText, word.segmentText, null, accountViewModel)
is EmojiSegment -> RenderCustomEmoji(word.segmentText, state)
is InvoiceSegment -> MayBeInvoicePreview(word.segmentText)
is WithdrawSegment -> MayBeWithdrawal(word.segmentText)
// is InvoiceSegment -> MayBeInvoicePreview(word.segmentText)
// is WithdrawSegment -> MayBeWithdrawal(word.segmentText)
// is CashuSegment -> CashuPreview(word.segmentText, accountViewModel)
is EmailSegment -> ClickableEmail(word.segmentText)
is PhoneSegment -> ClickablePhone(word.segmentText)
@@ -424,8 +423,8 @@ private fun RenderWordWithPreview(
is ImageSegment -> ZoomableContentView(word.segmentText, state, accountViewModel)
is LinkSegment -> LoadUrlPreview(word.segmentText, word.segmentText, callbackUri, accountViewModel)
is EmojiSegment -> RenderCustomEmoji(word.segmentText, state)
is InvoiceSegment -> MayBeInvoicePreview(word.segmentText)
is WithdrawSegment -> MayBeWithdrawal(word.segmentText)
is InvoiceSegment -> MayBeInvoicePreview(word.segmentText, accountViewModel)
is WithdrawSegment -> MayBeWithdrawal(word.segmentText, accountViewModel)
is CashuSegment -> CashuPreview(word.segmentText, accountViewModel)
is EmailSegment -> ClickableEmail(word.segmentText)
is PhoneSegment -> ClickablePhone(word.segmentText)
@@ -598,7 +597,7 @@ fun TagLink(
Text(text = word.segmentText)
} else {
Row {
DisplayUserFromTag(it, nav)
DisplayUserFromTag(it, accountViewModel, nav)
word.extras?.let {
Text(text = it)
}
@@ -683,11 +682,12 @@ private fun DisplayNoteFromTag(
@Composable
private fun DisplayUserFromTag(
baseUser: User,
accountViewModel: AccountViewModel,
nav: (String) -> Unit,
) {
val meta by baseUser.live().userMetadataInfo.observeAsState(baseUser.info)
Crossfade(targetState = meta, label = "DisplayUserFromTag") {
CrossfadeIfEnabled(targetState = meta, label = "DisplayUserFromTag", accountViewModel = accountViewModel) {
Row {
CreateClickableTextWithEmoji(
clickablePart = remember(meta) { it?.bestName() ?: baseUser.pubkeyDisplayHex() },
@@ -24,11 +24,12 @@ import android.content.Context
import android.graphics.BitmapFactory
import android.net.Uri
import androidx.compose.foundation.Image
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Face
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.ColorFilter
@@ -51,6 +52,7 @@ import coil.request.Options
import com.vitorpamplona.amethyst.commons.robohash.CachedRobohash
import com.vitorpamplona.amethyst.service.checkNotInMainThread
import com.vitorpamplona.amethyst.ui.theme.isLight
import com.vitorpamplona.amethyst.ui.theme.onBackgroundColorFilter
import java.util.Base64
@Composable
@@ -58,19 +60,22 @@ fun RobohashAsyncImage(
robot: String,
modifier: Modifier = Modifier,
contentDescription: String? = null,
loadRobohash: Boolean,
) {
val isLightTheme = MaterialTheme.colorScheme.isLight
val robotPainter =
remember(robot) {
CachedRobohash.get(robot, isLightTheme)
}
Image(
imageVector = robotPainter,
contentDescription = contentDescription,
modifier = modifier,
)
if (loadRobohash) {
Image(
imageVector = CachedRobohash.get(robot, MaterialTheme.colorScheme.isLight),
contentDescription = contentDescription,
modifier = modifier,
)
} else {
Image(
imageVector = Icons.Default.Face,
contentDescription = contentDescription,
colorFilter = MaterialTheme.colorScheme.onBackgroundColorFilter,
modifier = modifier,
)
}
}
@Composable
@@ -85,6 +90,7 @@ fun RobohashFallbackAsyncImage(
colorFilter: ColorFilter? = null,
filterQuality: FilterQuality = DrawScope.DefaultFilterQuality,
loadProfilePicture: Boolean,
loadRobohash: Boolean,
) {
if (model != null && loadProfilePicture) {
val isBase64 = model.startsWith("data:image/jpeg;base64,")
@@ -106,9 +112,20 @@ fun RobohashFallbackAsyncImage(
)
} else {
val painter =
rememberVectorPainter(
image = CachedRobohash.get(robot, MaterialTheme.colorScheme.isLight),
)
if (loadRobohash) {
rememberVectorPainter(
image = CachedRobohash.get(robot, MaterialTheme.colorScheme.isLight),
)
} else {
forwardingPainter(
painter =
rememberVectorPainter(
image = Icons.Default.Face,
),
colorFilter = MaterialTheme.colorScheme.onBackgroundColorFilter,
alpha = 0.5f,
)
}
AsyncImage(
model = model,
@@ -125,16 +142,25 @@ fun RobohashFallbackAsyncImage(
)
}
} else {
val robotPainter = CachedRobohash.get(robot, MaterialTheme.colorScheme.isLight)
Image(
imageVector = robotPainter,
contentDescription = contentDescription,
modifier = modifier,
alignment = alignment,
contentScale = contentScale,
colorFilter = colorFilter,
)
if (loadRobohash) {
Image(
imageVector = CachedRobohash.get(robot, MaterialTheme.colorScheme.isLight),
contentDescription = contentDescription,
modifier = modifier,
alignment = alignment,
contentScale = contentScale,
colorFilter = colorFilter,
)
} else {
Image(
imageVector = Icons.Default.Face,
contentDescription = contentDescription,
colorFilter = MaterialTheme.colorScheme.onBackgroundColorFilter,
modifier = modifier,
alignment = alignment,
contentScale = contentScale,
)
}
}
}
@@ -20,7 +20,6 @@
*/
package com.vitorpamplona.amethyst.ui.components
import androidx.compose.animation.Crossfade
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
@@ -54,6 +53,7 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
import com.vitorpamplona.amethyst.ui.theme.ButtonPadding
@@ -102,7 +102,7 @@ fun SensitivityWarning(
var showContentWarningNote by
remember(accountState) { mutableStateOf(accountState?.account?.showSensitiveContent != true) }
Crossfade(targetState = showContentWarningNote) {
CrossfadeIfEnabled(targetState = showContentWarningNote, accountViewModel = accountViewModel) {
if (it) {
ContentWarningNote { showContentWarningNote = false }
} else {
@@ -64,6 +64,7 @@ import androidx.compose.ui.window.DialogProperties
import com.vitorpamplona.amethyst.AccountInfo
import com.vitorpamplona.amethyst.LocalPreferences
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.FeatureSetType
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji
@@ -191,12 +192,11 @@ fun DisplayAccount(
.width(55.dp)
.padding(0.dp),
) {
val automaticallyShowProfilePicture =
remember {
accountViewModel.settings.showProfilePictures.value
}
AccountPicture(it, automaticallyShowProfilePicture)
AccountPicture(
it,
accountViewModel.settings.showProfilePictures.value,
loadRobohash = accountViewModel.settings.featureSet != FeatureSetType.PERFORMANCE,
)
}
Spacer(modifier = Modifier.width(16.dp))
Column(modifier = Modifier.weight(1f)) { AccountName(acc, it) }
@@ -232,6 +232,7 @@ private fun ActiveMarker(
private fun AccountPicture(
user: User,
loadProfilePicture: Boolean,
loadRobohash: Boolean,
) {
val profilePicture by user.live().profilePictureChanges.observeAsState()
@@ -241,6 +242,7 @@ private fun AccountPicture(
contentDescription = stringResource(R.string.profile_image),
modifier = AccountPictureModifier,
loadProfilePicture = loadProfilePicture,
loadRobohash = loadRobohash,
)
}
@@ -41,7 +41,6 @@ import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ExpandMore
import androidx.compose.material3.DrawerState
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
@@ -58,7 +57,6 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -81,6 +79,7 @@ import coil.compose.AsyncImage
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.AddressableNote
import com.vitorpamplona.amethyst.model.FeatureSetType
import com.vitorpamplona.amethyst.model.GLOBAL_FOLLOWS
import com.vitorpamplona.amethyst.model.KIND3_FOLLOWS
import com.vitorpamplona.amethyst.model.LocalCache
@@ -154,13 +153,12 @@ import kotlinx.coroutines.flow.combineTransform
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.transformLatest
import kotlinx.coroutines.launch
@Composable
fun AppTopBar(
followLists: FollowListViewModel,
navEntryState: State<NavBackStackEntry?>,
drawerState: DrawerState,
openDrawer: () -> Unit,
accountViewModel: AccountViewModel,
nav: (String) -> Unit,
navPopBack: () -> Unit,
@@ -180,7 +178,7 @@ fun AppTopBar(
derivedStateOf { navEntryState.value?.arguments?.getString("id") }
}
RenderTopRouteBar(currentRoute, id, followLists, drawerState, accountViewModel, nav, navPopBack)
RenderTopRouteBar(currentRoute, id, followLists, openDrawer, accountViewModel, nav, navPopBack)
}
@Composable
@@ -188,16 +186,16 @@ private fun RenderTopRouteBar(
currentRoute: String?,
id: String?,
followLists: FollowListViewModel,
drawerState: DrawerState,
openDrawer: () -> Unit,
accountViewModel: AccountViewModel,
nav: (String) -> Unit,
navPopBack: () -> Unit,
) {
when (currentRoute) {
Route.Home.base -> HomeTopBar(followLists, drawerState, accountViewModel, nav)
Route.Video.base -> StoriesTopBar(followLists, drawerState, accountViewModel, nav)
Route.Discover.base -> DiscoveryTopBar(followLists, drawerState, accountViewModel, nav)
Route.Notification.base -> NotificationTopBar(followLists, drawerState, accountViewModel, nav)
Route.Home.base -> HomeTopBar(followLists, openDrawer, accountViewModel, nav)
Route.Video.base -> StoriesTopBar(followLists, openDrawer, accountViewModel, nav)
Route.Discover.base -> DiscoveryTopBar(followLists, openDrawer, accountViewModel, nav)
Route.Notification.base -> NotificationTopBar(followLists, openDrawer, accountViewModel, nav)
Route.Settings.base -> TopBarWithBackButton(stringResource(id = R.string.application_preferences), navPopBack)
Route.Bookmarks.base -> TopBarWithBackButton(stringResource(id = R.string.bookmarks), navPopBack)
Route.Drafts.base -> TopBarWithBackButton(stringResource(id = R.string.drafts), navPopBack)
@@ -213,10 +211,10 @@ private fun RenderTopRouteBar(
Route.Geohash.base -> GeoHashTopBar(id, accountViewModel, navPopBack)
Route.Note.base -> ThreadTopBar(id, accountViewModel, navPopBack)
Route.ContentDiscovery.base -> DvmTopBar(id, accountViewModel, nav, navPopBack)
else -> MainTopBar(drawerState, accountViewModel, nav)
else -> MainTopBar(openDrawer, accountViewModel, nav)
}
} else {
MainTopBar(drawerState, accountViewModel, nav)
MainTopBar(openDrawer, accountViewModel, nav)
}
}
}
@@ -381,7 +379,7 @@ private fun RenderRoomTopBar(
Spacer(modifier = DoubleHorzSpacer)
UsernameDisplay(baseUser, Modifier.weight(1f), fontWeight = FontWeight.Normal)
UsernameDisplay(baseUser, Modifier.weight(1f), fontWeight = FontWeight.Normal, accountViewModel = accountViewModel)
}
}
},
@@ -413,7 +411,7 @@ private fun RenderRoomTopBar(
.padding(start = 10.dp)
.weight(1f),
fontWeight = FontWeight.Normal,
accountViewModel.userProfile(),
accountViewModel,
)
},
extendableRow = {
@@ -452,11 +450,11 @@ private fun ChannelTopBar(
@Composable
fun StoriesTopBar(
followLists: FollowListViewModel,
drawerState: DrawerState,
openDrawer: () -> Unit,
accountViewModel: AccountViewModel,
nav: (String) -> Unit,
) {
GenericMainTopBar(drawerState, accountViewModel, nav) {
GenericMainTopBar(openDrawer, accountViewModel, nav) {
val list by accountViewModel.account.defaultStoriesFollowList.collectAsStateWithLifecycle()
FollowListWithRoutes(
@@ -471,11 +469,11 @@ fun StoriesTopBar(
@Composable
fun HomeTopBar(
followLists: FollowListViewModel,
drawerState: DrawerState,
openDrawer: () -> Unit,
accountViewModel: AccountViewModel,
nav: (String) -> Unit,
) {
GenericMainTopBar(drawerState, accountViewModel, nav) {
GenericMainTopBar(openDrawer, accountViewModel, nav) {
val list by accountViewModel.account.defaultHomeFollowList.collectAsStateWithLifecycle()
FollowListWithRoutes(
@@ -494,11 +492,11 @@ fun HomeTopBar(
@Composable
fun NotificationTopBar(
followLists: FollowListViewModel,
drawerState: DrawerState,
openDrawer: () -> Unit,
accountViewModel: AccountViewModel,
nav: (String) -> Unit,
) {
GenericMainTopBar(drawerState, accountViewModel, nav) {
GenericMainTopBar(openDrawer, accountViewModel, nav) {
val list by accountViewModel.account.defaultNotificationFollowList.collectAsStateWithLifecycle()
FollowListWithoutRoutes(
@@ -513,11 +511,11 @@ fun NotificationTopBar(
@Composable
fun DiscoveryTopBar(
followLists: FollowListViewModel,
drawerState: DrawerState,
openDrawer: () -> Unit,
accountViewModel: AccountViewModel,
nav: (String) -> Unit,
) {
GenericMainTopBar(drawerState, accountViewModel, nav) {
GenericMainTopBar(openDrawer, accountViewModel, nav) {
val list by accountViewModel.account.defaultDiscoveryFollowList.collectAsStateWithLifecycle()
FollowListWithoutRoutes(
@@ -531,17 +529,17 @@ fun DiscoveryTopBar(
@Composable
fun MainTopBar(
drawerState: DrawerState,
openDrawer: () -> Unit,
accountViewModel: AccountViewModel,
nav: (String) -> Unit,
) {
GenericMainTopBar(drawerState, accountViewModel, nav) { AmethystClickableIcon() }
GenericMainTopBar(openDrawer, accountViewModel, nav) { AmethystClickableIcon() }
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun GenericMainTopBar(
drawerState: DrawerState,
openDrawer: () -> Unit,
accountViewModel: AccountViewModel,
nav: (String) -> Unit,
content: @Composable () -> Unit,
@@ -558,8 +556,7 @@ fun GenericMainTopBar(
}
},
navigationIcon = {
val coroutineScope = rememberCoroutineScope()
LoggedInUserPictureDrawer(accountViewModel) { coroutineScope.launch { drawerState.open() } }
LoggedInUserPictureDrawer(accountViewModel, openDrawer)
},
actions = { SearchButton { nav(Route.Search.route) } },
)
@@ -596,6 +593,7 @@ private fun LoggedInUserPictureDrawer(
modifier = HeaderPictureModifier,
contentScale = ContentScale.Crop,
loadProfilePicture = accountViewModel.settings.showProfilePictures.value,
loadRobohash = accountViewModel.settings.featureSet != FeatureSetType.PERFORMANCE,
)
}
}
@@ -86,6 +86,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import coil.compose.AsyncImage
import com.vitorpamplona.amethyst.BuildConfig
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.FeatureSetType
import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.service.relays.RelayPool
import com.vitorpamplona.amethyst.service.relays.RelayPoolStatus
@@ -131,11 +132,6 @@ fun DrawerContent(
Unit
}
val automaticallyShowProfilePicture =
remember {
accountViewModel.settings.showProfilePictures.value
}
ModalDrawerSheet(
drawerContainerColor = MaterialTheme.colorScheme.background,
drawerTonalElevation = 0.dp,
@@ -174,7 +170,8 @@ fun DrawerContent(
BottomContent(
accountViewModel.account.userProfile(),
drawerState,
automaticallyShowProfilePicture,
loadProfilePicture = accountViewModel.settings.showProfilePictures.value,
loadRobohash = accountViewModel.settings.featureSet != FeatureSetType.PERFORMANCE,
nav,
)
}
@@ -243,6 +240,7 @@ fun ProfileContentTemplate(
.border(3.dp, MaterialTheme.colorScheme.background, CircleShape)
.clickable(onClick = onClick),
loadProfilePicture = accountViewModel.settings.showProfilePictures.value,
loadRobohash = accountViewModel.settings.featureSet != FeatureSetType.PERFORMANCE,
)
if (bestDisplayName != null) {
@@ -746,6 +744,7 @@ fun BottomContent(
user: User,
drawerState: DrawerState,
loadProfilePicture: Boolean,
loadRobohash: Boolean,
nav: (String) -> Unit,
) {
val coroutineScope = rememberCoroutineScope()
@@ -804,6 +803,7 @@ fun BottomContent(
ShowQRDialog(
user,
loadProfilePicture = loadProfilePicture,
loadRobohash = loadRobohash,
onScan = {
dialogOpen = false
coroutineScope.launch { drawerState.close() }
@@ -20,7 +20,6 @@
*/
package com.vitorpamplona.amethyst.ui.note
import androidx.compose.animation.Crossfade
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
@@ -68,6 +67,7 @@ import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.ParticipantListBuilder
import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled
import com.vitorpamplona.amethyst.ui.components.SensitivityWarning
import com.vitorpamplona.amethyst.ui.layouts.LeftPictureLayout
import com.vitorpamplona.amethyst.ui.note.elements.BannerImage
@@ -472,7 +472,7 @@ fun RenderLiveActivityThumb(
} ?: run { DisplayAuthorBanner(baseNote) }
Box(Modifier.padding(10.dp)) {
Crossfade(targetState = card.status, label = "RenderLiveActivityThumb") {
CrossfadeIfEnabled(targetState = card.status, label = "RenderLiveActivityThumb", accountViewModel = accountViewModel) {
when (it) {
STATUS_LIVE -> {
val url = card.media
@@ -20,7 +20,6 @@
*/
package com.vitorpamplona.amethyst.ui.note
import androidx.compose.animation.Crossfade
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
@@ -61,8 +60,10 @@ import androidx.lifecycle.map
import com.patrykandpatrick.vico.core.extension.forEachIndexedExtended
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.Channel
import com.vitorpamplona.amethyst.model.FeatureSetType
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled
import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji
import com.vitorpamplona.amethyst.ui.components.RobohashFallbackAsyncImage
import com.vitorpamplona.amethyst.ui.layouts.ChatHeaderLayout
@@ -136,7 +137,7 @@ private fun ChatroomPrivateMessages(
}
}
Crossfade(userRoom, label = "ChatroomPrivateMessages") { room ->
CrossfadeIfEnabled(userRoom, label = "ChatroomPrivateMessages", accountViewModel = accountViewModel) { room ->
if (room != null) {
UserRoomCompose(baseNote, room, accountViewModel, nav)
} else {
@@ -191,11 +192,6 @@ private fun ChannelRoomCompose(
val hasNewMessages = remember { mutableStateOf<Boolean>(false) }
val automaticallyShowProfilePicture =
remember {
accountViewModel.settings.showProfilePictures.value
}
WatchNotificationChanges(note, route, accountViewModel) { newHasNewMessages ->
if (hasNewMessages.value != newHasNewMessages) {
hasNewMessages.value = newHasNewMessages
@@ -209,7 +205,8 @@ private fun ChannelRoomCompose(
channelLastTime = remember(note) { note.createdAt() },
channelLastContent = remember(note, authorState) { "$authorName: $description" },
hasNewMessages = hasNewMessages,
loadProfilePicture = automaticallyShowProfilePicture,
loadProfilePicture = accountViewModel.settings.showProfilePictures.value,
loadRobohash = accountViewModel.settings.featureSet != FeatureSetType.PERFORMANCE,
onClick = { nav(route) },
)
}
@@ -305,7 +302,7 @@ fun RoomNameDisplay(
.distinctUntilChanged()
.observeAsState(accountViewModel.userProfile().privateChatrooms[room]?.subject)
Crossfade(targetState = roomSubject, modifier, label = "RoomNameDisplay") {
CrossfadeIfEnabled(targetState = roomSubject, modifier, label = "RoomNameDisplay", accountViewModel = accountViewModel) {
if (!it.isNullOrBlank()) {
if (room.users.size > 1) {
DisplayRoomSubject(it)
@@ -337,7 +334,7 @@ private fun DisplayUserAndSubject(
maxLines = 1,
)
LoadUser(baseUserHex = user, accountViewModel = accountViewModel) {
it?.let { UsernameDisplay(it, Modifier.weight(1f)) }
it?.let { UsernameDisplay(it, Modifier.weight(1f), accountViewModel = accountViewModel) }
}
}
}
@@ -354,14 +351,14 @@ fun DisplayUserSetAsSubject(
// Regular Design
Row {
LoadUser(baseUserHex = userList[0], accountViewModel) {
it?.let { UsernameDisplay(it, Modifier.weight(1f), fontWeight = fontWeight) }
it?.let { UsernameDisplay(it, Modifier.weight(1f), fontWeight = fontWeight, accountViewModel = accountViewModel) }
}
}
} else {
Row {
userList.take(4).forEachIndexedExtended { index, isFirst, isLast, value ->
LoadUser(baseUserHex = value, accountViewModel) {
it?.let { ShortUsernameDisplay(baseUser = it, fontWeight = fontWeight) }
it?.let { ShortUsernameDisplay(baseUser = it, fontWeight = fontWeight, accountViewModel = accountViewModel) }
}
if (!isLast) {
@@ -395,6 +392,7 @@ fun ShortUsernameDisplay(
baseUser: User,
weight: Modifier = Modifier,
fontWeight: FontWeight = FontWeight.Bold,
accountViewModel: AccountViewModel,
) {
val userName by
baseUser
@@ -404,7 +402,7 @@ fun ShortUsernameDisplay(
.distinctUntilChanged()
.observeAsState(baseUser.toBestShortFirstName())
Crossfade(targetState = userName, modifier = weight) {
CrossfadeIfEnabled(targetState = userName, modifier = weight, accountViewModel = accountViewModel) {
CreateTextWithEmoji(
text = it,
tags = baseUser.info?.tags,
@@ -459,6 +457,7 @@ fun ChannelName(
channelLastContent: String?,
hasNewMessages: MutableState<Boolean>,
loadProfilePicture: Boolean,
loadRobohash: Boolean,
onClick: () -> Unit,
) {
ChannelName(
@@ -469,6 +468,7 @@ fun ChannelName(
contentDescription = stringResource(R.string.channel_image),
modifier = AccountPictureModifier,
loadProfilePicture = loadProfilePicture,
loadRobohash = loadRobohash,
)
},
channelTitle,
@@ -173,7 +173,7 @@ fun NormalChatNote(
ChatBubbleLayout(
isLoggedInUser = isLoggedInUser,
innerQuote = innerQuote,
isSimplified = accountViewModel.settings.featureSet == FeatureSetType.SIMPLIFIED,
isComplete = accountViewModel.settings.featureSet == FeatureSetType.COMPLETE,
hasDetailsToShow = note.zaps.isNotEmpty() || note.zapPayments.isNotEmpty() || note.reactions.isNotEmpty(),
drawAuthorInfo = drawAuthorInfo,
parentBackgroundColor = parentBackgroundColor,
@@ -249,7 +249,7 @@ fun NormalChatNote(
fun ChatBubbleLayout(
isLoggedInUser: Boolean,
innerQuote: Boolean,
isSimplified: Boolean,
isComplete: Boolean,
hasDetailsToShow: Boolean,
drawAuthorInfo: Boolean,
parentBackgroundColor: MutableState<Color>? = null,
@@ -300,10 +300,10 @@ fun ChatBubbleLayout(
val showDetails =
remember {
mutableStateOf(
if (isSimplified) {
hasDetailsToShow
} else {
if (isComplete) {
true
} else {
hasDetailsToShow
},
)
}
@@ -313,7 +313,7 @@ fun ChatBubbleLayout(
Modifier.combinedClickable(
onClick = {
if (!onClick()) {
if (isSimplified) {
if (!isComplete) {
showDetails.value = !showDetails.value
}
}
@@ -376,7 +376,7 @@ private fun BubblePreview() {
ChatBubbleLayout(
isLoggedInUser = false,
innerQuote = false,
isSimplified = false,
isComplete = true,
hasDetailsToShow = true,
drawAuthorInfo = true,
parentBackgroundColor = backgroundBubbleColor,
@@ -406,7 +406,7 @@ private fun BubblePreview() {
ChatBubbleLayout(
isLoggedInUser = true,
innerQuote = false,
isSimplified = false,
isComplete = true,
hasDetailsToShow = true,
drawAuthorInfo = true,
parentBackgroundColor = backgroundBubbleColor,
@@ -59,6 +59,7 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.FeatureSetType
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.NoteState
import com.vitorpamplona.amethyst.model.User
@@ -493,7 +494,7 @@ private fun BoxedAuthor(
accountViewModel: AccountViewModel,
) {
Box(modifier = Size35Modifier.clickable(onClick = { nav(authorRouteFor(note)) })) {
WatchAuthorWithBlank(note, Size35Modifier) { author ->
WatchAuthorWithBlank(note, Size35Modifier, accountViewModel) { author ->
WatchUserMetadataAndFollowsAndRenderUserProfilePictureOrDefaultAuthor(
author,
accountViewModel,
@@ -510,7 +511,7 @@ fun WatchUserMetadataAndFollowsAndRenderUserProfilePictureOrDefaultAuthor(
if (author != null) {
WatchUserMetadataAndFollowsAndRenderUserProfilePicture(author, accountViewModel)
} else {
DisplayBlankAuthor(Size35dp)
DisplayBlankAuthor(Size35dp, accountViewModel = accountViewModel)
}
}
@@ -520,7 +521,6 @@ fun WatchUserMetadataAndFollowsAndRenderUserProfilePicture(
accountViewModel: AccountViewModel,
) {
WatchUserMetadata(author) { baseUserPicture ->
// Crossfade(targetState = baseUserPicture) { userPicture ->
RobohashFallbackAsyncImage(
robot = author.pubkeyHex,
model = baseUserPicture,
@@ -528,18 +528,16 @@ fun WatchUserMetadataAndFollowsAndRenderUserProfilePicture(
modifier = MaterialTheme.colorScheme.profile35dpModifier,
contentScale = ContentScale.Crop,
loadProfilePicture = accountViewModel.settings.showProfilePictures.value,
loadRobohash = accountViewModel.settings.featureSet != FeatureSetType.PERFORMANCE,
)
// }
}
WatchUserFollows(author.pubkeyHex, accountViewModel) { isFollowing ->
// Crossfade(targetState = isFollowing) {
if (isFollowing) {
Box(modifier = Size35Modifier, contentAlignment = Alignment.TopEnd) {
FollowingIcon(Size10dp)
}
}
// }
}
}
@@ -20,7 +20,6 @@
*/
package com.vitorpamplona.amethyst.ui.components
import androidx.compose.animation.Crossfade
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
@@ -54,6 +53,7 @@ import com.vitorpamplona.amethyst.commons.hashtags.Tunestr
import com.vitorpamplona.amethyst.model.AddressableNote
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled
import com.vitorpamplona.amethyst.ui.navigation.routeFor
import com.vitorpamplona.amethyst.ui.note.LoadAddressableNote
import com.vitorpamplona.amethyst.ui.note.LoadStatuses
@@ -132,10 +132,11 @@ fun ObserveDisplayNip05Status(
val nip05 by baseUser.live().nip05Changes.observeAsState(baseUser.nip05())
LoadStatuses(baseUser, accountViewModel) { statuses ->
Crossfade(
CrossfadeIfEnabled(
targetState = nip05,
modifier = columnModifier,
label = "ObserveDisplayNip05StatusCrossfade",
accountViewModel = accountViewModel,
) {
VerifyAndDisplayNIP05OrStatusLine(
it,
@@ -165,11 +166,11 @@ private fun VerifyAndDisplayNIP05OrStatusLine(
nip05VerificationAsAState(baseUser.info!!, baseUser.pubkeyHex, accountViewModel)
if (nip05Verified.value != true) {
DisplayNIP05(nip05, nip05Verified)
DisplayNIP05(nip05, nip05Verified, accountViewModel)
} else if (!statuses.isEmpty()) {
RotateStatuses(statuses, accountViewModel, nav)
} else {
DisplayNIP05(nip05, nip05Verified)
DisplayNIP05(nip05, nip05Verified, accountViewModel)
}
} else {
if (!statuses.isEmpty()) {
@@ -271,8 +272,7 @@ fun DisplayStatus(
routeFor(
note,
accountViewModel.userProfile(),
)
?.let { nav(it) }
)?.let { nav(it) }
},
) {
Icon(
@@ -294,8 +294,7 @@ fun DisplayStatus(
routeFor(
it,
accountViewModel.userProfile(),
)
?.let { nav(it) }
)?.let { nav(it) }
},
) {
Icon(
@@ -314,6 +313,7 @@ fun DisplayStatus(
private fun DisplayNIP05(
nip05: String,
nip05Verified: MutableState<Boolean?>,
accountViewModel: AccountViewModel,
) {
val uri = LocalUriHandler.current
val (user, domain) =
@@ -336,7 +336,7 @@ private fun DisplayNIP05(
)
}
NIP05VerifiedSymbol(nip05Verified, NIP05IconSize)
NIP05VerifiedSymbol(nip05Verified, NIP05IconSize, accountViewModel)
ClickableText(
text = remember(nip05) { AnnotatedString(domain) },
@@ -352,8 +352,9 @@ private fun DisplayNIP05(
private fun NIP05VerifiedSymbol(
nip05Verified: MutableState<Boolean?>,
modifier: Modifier,
accountViewModel: AccountViewModel,
) {
Crossfade(targetState = nip05Verified.value) {
CrossfadeIfEnabled(targetState = nip05Verified.value, accountViewModel = accountViewModel) {
when (it) {
null -> NIP05CheckingIcon(modifier = modifier)
true -> NIP05VerifiedIcon(modifier = modifier)
@@ -373,7 +374,7 @@ fun DisplayNip05ProfileStatus(
if (nip05.split("@").size <= 2) {
val nip05Verified = nip05VerificationAsAState(user.info!!, user.pubkeyHex, accountViewModel)
Row(verticalAlignment = Alignment.CenterVertically) {
NIP05VerifiedSymbol(nip05Verified, Size16Modifier)
NIP05VerifiedSymbol(nip05Verified, Size16Modifier, accountViewModel)
var domainPadStart = 5.dp
val (user, domain) =
@@ -477,7 +477,7 @@ fun InnerNoteWithReactions(
baseNote.event !is GenericRepostEvent &&
!isBoostedNote &&
!isQuotedNote &&
accountViewModel.settings.featureSet != FeatureSetType.SIMPLIFIED
accountViewModel.settings.featureSet == FeatureSetType.COMPLETE
NoteBody(
baseNote = baseNote,
showAuthorPicture = isQuotedNote,
@@ -975,9 +975,9 @@ fun FirstUserInfoRow(
if (showAuthorPicture) {
NoteAuthorPicture(baseNote, nav, accountViewModel, Size25dp)
Spacer(HalfPadding)
NoteUsernameDisplay(baseNote, Modifier.weight(1f), textColor = textColor)
NoteUsernameDisplay(baseNote, Modifier.weight(1f), textColor = textColor, accountViewModel = accountViewModel)
} else {
NoteUsernameDisplay(baseNote, Modifier.weight(1f), textColor = textColor)
NoteUsernameDisplay(baseNote, Modifier.weight(1f), textColor = textColor, accountViewModel = accountViewModel)
}
if (isCommunityPost) {
@@ -1064,7 +1064,7 @@ private fun BadgeBox(
accountViewModel: AccountViewModel,
nav: (String) -> Unit,
) {
if (accountViewModel.settings.featureSet != FeatureSetType.SIMPLIFIED) {
if (accountViewModel.settings.featureSet == FeatureSetType.COMPLETE) {
if (baseNote.event is RepostEvent || baseNote.event is GenericRepostEvent) {
baseNote.replyTo?.lastOrNull()?.let { RelayBadges(it, accountViewModel, nav) }
} else {
@@ -1097,6 +1097,7 @@ private fun RenderAuthorImages(
ChannelNotePicture(
channel,
loadProfilePicture = accountViewModel.settings.showProfilePictures.value,
loadRobohash = accountViewModel.settings.featureSet != FeatureSetType.PERFORMANCE,
)
}
}
@@ -1107,6 +1108,7 @@ private fun RenderAuthorImages(
private fun ChannelNotePicture(
baseChannel: Channel,
loadProfilePicture: Boolean,
loadRobohash: Boolean,
) {
val model by
baseChannel.live
@@ -1121,6 +1123,7 @@ private fun ChannelNotePicture(
contentDescription = stringResource(R.string.group_picture),
modifier = MaterialTheme.colorScheme.channelNotePictureModifier,
loadProfilePicture = loadProfilePicture,
loadRobohash = loadRobohash,
)
}
}
@@ -24,7 +24,6 @@ import android.content.Context
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.AnimatedContentTransitionScope
import androidx.compose.animation.ContentTransform
import androidx.compose.animation.Crossfade
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.tween
@@ -98,8 +97,10 @@ import coil.compose.AsyncImage
import coil.request.CachePolicy
import coil.request.ImageRequest
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.FeatureSetType
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.service.ZapPaymentHandler
import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled
import com.vitorpamplona.amethyst.ui.actions.NewPostView
import com.vitorpamplona.amethyst.ui.components.GenericLoadable
import com.vitorpamplona.amethyst.ui.components.InLineIconRenderer
@@ -177,7 +178,7 @@ private fun InnerReactionRow(
addPadding = addPadding,
one = {
WatchReactionsZapsBoostsAndDisplayIfExists(baseNote) {
RenderShowIndividualReactionsButton(wantsToSeeReactions)
RenderShowIndividualReactionsButton(wantsToSeeReactions, accountViewModel)
}
},
two = {
@@ -377,14 +378,18 @@ fun <T, K, P, R> LiveData<T>.combineWith(
}
@Composable
private fun RenderShowIndividualReactionsButton(wantsToSeeReactions: MutableState<Boolean>) {
private fun RenderShowIndividualReactionsButton(
wantsToSeeReactions: MutableState<Boolean>,
accountViewModel: AccountViewModel,
) {
IconButton(
onClick = { wantsToSeeReactions.value = !wantsToSeeReactions.value },
modifier = Size20Modifier,
) {
Crossfade(
CrossfadeIfEnabled(
targetState = wantsToSeeReactions.value,
label = "RenderShowIndividualReactionsButton",
accountViewModel = accountViewModel,
) {
if (it) {
ExpandLessIcon(modifier = Size22Modifier, R.string.close_all_reactions_to_this_post)
@@ -607,7 +612,7 @@ fun ReplyReaction(
}
if (showCounter) {
ReplyCounter(baseNote, grayTint)
ReplyCounter(baseNote, grayTint, accountViewModel)
}
}
@@ -615,23 +620,29 @@ fun ReplyReaction(
fun ReplyCounter(
baseNote: Note,
textColor: Color,
accountViewModel: AccountViewModel,
) {
val repliesState by baseNote.live().replyCount.observeAsState(baseNote.replies.size)
SlidingAnimationCount(repliesState, textColor)
SlidingAnimationCount(repliesState, textColor, accountViewModel)
}
@Composable
private fun SlidingAnimationCount(
baseCount: Int,
textColor: Color,
accountViewModel: AccountViewModel,
) {
AnimatedContent<Int>(
targetState = baseCount,
transitionSpec = AnimatedContentTransitionScope<Int>::transitionSpec,
label = "SlidingAnimationCount",
) { count ->
TextCount(count, textColor)
if (accountViewModel.settings.featureSet == FeatureSetType.PERFORMANCE) {
TextCount(baseCount, textColor)
} else {
AnimatedContent<Int>(
targetState = baseCount,
transitionSpec = AnimatedContentTransitionScope<Int>::transitionSpec,
label = "SlidingAnimationCount",
) { count ->
TextCount(count, textColor)
}
}
}
@@ -669,18 +680,28 @@ fun TextCount(
fun SlidingAnimationAmount(
amount: String,
textColor: Color,
accountViewModel: AccountViewModel,
) {
AnimatedContent(
targetState = amount,
transitionSpec = AnimatedContentTransitionScope<String>::transitionSpec,
label = "SlidingAnimationAmount",
) { count ->
if (accountViewModel.settings.featureSet == FeatureSetType.PERFORMANCE) {
Text(
text = count,
text = amount,
fontSize = Font14SP,
color = textColor,
maxLines = 1,
)
} else {
AnimatedContent(
targetState = amount,
transitionSpec = AnimatedContentTransitionScope<String>::transitionSpec,
label = "SlidingAnimationAmount",
) { count ->
Text(
text = count,
fontSize = Font14SP,
color = textColor,
maxLines = 1,
)
}
}
}
@@ -725,7 +746,7 @@ fun BoostReaction(
}
}
BoostText(baseNote, grayTint)
BoostText(baseNote, grayTint, accountViewModel)
}
@Composable
@@ -752,10 +773,11 @@ fun ObserveBoostIcon(
fun BoostText(
baseNote: Note,
grayTint: Color,
accountViewModel: AccountViewModel,
) {
val boostState by baseNote.live().boostCount.observeAsState(baseNote.boosts.size)
SlidingAnimationCount(boostState, grayTint)
SlidingAnimationCount(boostState, grayTint, accountViewModel)
}
@OptIn(ExperimentalFoundationApi::class)
@@ -792,7 +814,7 @@ fun LikeReaction(
),
) {
ObserveLikeIcon(baseNote, accountViewModel) { reactionType ->
Crossfade(targetState = reactionType, label = "LikeIcon") {
CrossfadeIfEnabled(targetState = reactionType, label = "LikeIcon", accountViewModel = accountViewModel) {
if (it != null) {
RenderReactionType(it, heartSizeModifier, iconFontSize)
} else {
@@ -823,7 +845,7 @@ fun LikeReaction(
}
}
ObserveLikeText(baseNote) { reactionCount -> SlidingAnimationCount(reactionCount, grayTint) }
ObserveLikeText(baseNote) { reactionCount -> SlidingAnimationCount(reactionCount, grayTint, accountViewModel) }
}
@Composable
@@ -1071,7 +1093,7 @@ fun ZapReaction(
baseNote,
accountViewModel,
) { wasZappedByLoggedInUser ->
Crossfade(targetState = wasZappedByLoggedInUser.value, label = "ZapIcon") {
CrossfadeIfEnabled(targetState = wasZappedByLoggedInUser.value, label = "ZapIcon", accountViewModel = accountViewModel) {
if (it) {
ZappedIcon(iconSizeModifier)
} else {
@@ -1083,7 +1105,7 @@ fun ZapReaction(
}
ObserveZapAmountText(baseNote, accountViewModel) { zapAmountTxt ->
SlidingAnimationAmount(zapAmountTxt, grayTint)
SlidingAnimationAmount(zapAmountTxt, grayTint, accountViewModel)
}
}
@@ -20,7 +20,6 @@
*/
package com.vitorpamplona.amethyst.ui.note
import androidx.compose.animation.Crossfade
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
@@ -53,6 +52,7 @@ import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.theme.ShowMoreRelaysButtonBoxModifer
import com.vitorpamplona.amethyst.ui.theme.Size17Modifier
@@ -69,7 +69,7 @@ fun RelayBadges(
) {
val expanded = remember { mutableStateOf(false) }
Crossfade(expanded.value, modifier = noteComposeRelayBox, label = "RelayBadges") {
CrossfadeIfEnabled(expanded.value, modifier = noteComposeRelayBox, label = "RelayBadges", accountViewModel = accountViewModel) {
if (it) {
RenderAllRelayList(baseNote, Modifier.fillMaxWidth(), accountViewModel = accountViewModel, nav = nav)
} else {
@@ -131,7 +131,7 @@ fun WatchAndRenderRelay(
baseNote.relays.getOrNull(relayIndex),
)
Crossfade(targetState = noteRelays, label = "RenderRelay", modifier = Size17Modifier) {
CrossfadeIfEnabled(targetState = noteRelays, label = "RenderRelay", modifier = Size17Modifier, accountViewModel = accountViewModel) {
if (it != null) {
RenderRelay(it, accountViewModel, nav)
}
@@ -40,6 +40,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.FeatureSetType
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.RelayBriefInfoCache
import com.vitorpamplona.amethyst.service.Nip11CachedRetriever
@@ -178,6 +179,7 @@ fun RenderRelay(
displayUrl = relay.displayUrl,
iconUrl = relayInfo?.icon ?: relay.favIcon,
loadProfilePicture = accountViewModel.settings.showProfilePictures.value,
loadRobohash = accountViewModel.settings.featureSet != FeatureSetType.PERFORMANCE,
)
}
}
@@ -187,6 +189,7 @@ fun RenderRelayIcon(
displayUrl: String,
iconUrl: String?,
loadProfilePicture: Boolean,
loadRobohash: Boolean,
iconModifier: Modifier = MaterialTheme.colorScheme.relayIconModifier,
) {
RobohashFallbackAsyncImage(
@@ -196,5 +199,6 @@ fun RenderRelayIcon(
colorFilter = RelayIconFilter,
modifier = iconModifier,
loadProfilePicture = loadProfilePicture,
loadRobohash = loadRobohash,
)
}
@@ -51,7 +51,7 @@ fun UserCompose(
UserPicture(baseUser, Size55dp, accountViewModel = accountViewModel, nav = nav)
Column(modifier = remember { Modifier.padding(start = 10.dp).weight(1f) }) {
Row(verticalAlignment = Alignment.CenterVertically) { UsernameDisplay(baseUser) }
Row(verticalAlignment = Alignment.CenterVertically) { UsernameDisplay(baseUser, accountViewModel = accountViewModel) }
AboutDisplay(baseUser)
}
@@ -40,6 +40,7 @@ import androidx.compose.ui.unit.Dp
import androidx.lifecycle.distinctUntilChanged
import androidx.lifecycle.map
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.FeatureSetType
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.ui.components.RobohashAsyncImage
@@ -68,9 +69,9 @@ fun NoteAuthorPicture(
modifier: Modifier = Modifier,
onClick: ((User) -> Unit)? = null,
) {
WatchAuthorWithBlank(baseNote) {
WatchAuthorWithBlank(baseNote, accountViewModel = accountViewModel) {
if (it == null) {
DisplayBlankAuthor(size, modifier)
DisplayBlankAuthor(size, modifier, accountViewModel)
} else {
ClickableUserPicture(it, size, accountViewModel, modifier, onClick)
}
@@ -81,6 +82,7 @@ fun NoteAuthorPicture(
fun DisplayBlankAuthor(
size: Dp,
modifier: Modifier = Modifier,
accountViewModel: AccountViewModel,
) {
val nullModifier =
remember {
@@ -91,6 +93,7 @@ fun DisplayBlankAuthor(
robot = "authornotfound",
contentDescription = stringResource(R.string.unknown_author),
modifier = nullModifier,
loadRobohash = accountViewModel.settings.featureSet != FeatureSetType.PERFORMANCE,
)
}
@@ -115,6 +118,7 @@ fun UserPicture(
DisplayBlankAuthor(
size,
pictureModifier,
accountViewModel,
)
}
}
@@ -350,6 +354,7 @@ fun InnerUserPicture(
modifier = myImageModifier,
contentScale = ContentScale.Crop,
loadProfilePicture = accountViewModel.settings.showProfilePictures.value,
loadRobohash = accountViewModel.settings.featureSet != FeatureSetType.PERFORMANCE,
)
}
@@ -22,7 +22,6 @@ package com.vitorpamplona.amethyst.ui.note
import android.content.Context
import android.util.Log
import androidx.compose.animation.Crossfade
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
@@ -40,7 +39,9 @@ import androidx.lifecycle.LifecycleOwner
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.service.tts.TextToSpeechHelper
import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled
import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.theme.StdButtonSizeModifier
import com.vitorpamplona.amethyst.ui.theme.placeholderText
import com.vitorpamplona.quartz.events.ImmutableListOfLists
@@ -50,9 +51,10 @@ fun NoteUsernameDisplay(
baseNote: Note,
weight: Modifier = Modifier,
textColor: Color = Color.Unspecified,
accountViewModel: AccountViewModel,
) {
WatchAuthor(baseNote) {
UsernameDisplay(it, weight, textColor = textColor)
UsernameDisplay(it, weight, textColor = textColor, accountViewModel = accountViewModel)
}
}
@@ -77,6 +79,7 @@ fun WatchAuthor(
fun WatchAuthorWithBlank(
baseNote: Note,
modifier: Modifier = Modifier,
accountViewModel: AccountViewModel,
inner: @Composable (User?) -> Unit,
) {
val noteAuthor = baseNote.author
@@ -85,7 +88,7 @@ fun WatchAuthorWithBlank(
} else {
val authorState by baseNote.live().metadata.observeAsState()
Crossfade(targetState = authorState?.note?.author, modifier = modifier, label = "WatchAuthorWithBlank") { newAuthor ->
CrossfadeIfEnabled(targetState = authorState?.note?.author, modifier = modifier, label = "WatchAuthorWithBlank", accountViewModel = accountViewModel) { newAuthor ->
inner(newAuthor)
}
}
@@ -97,10 +100,11 @@ fun UsernameDisplay(
weight: Modifier = Modifier,
fontWeight: FontWeight = FontWeight.Bold,
textColor: Color = Color.Unspecified,
accountViewModel: AccountViewModel,
) {
val userMetadata by baseUser.live().userMetadataInfo.observeAsState(baseUser.info)
Crossfade(targetState = userMetadata, modifier = weight, label = "UsernameDisplay") {
CrossfadeIfEnabled(targetState = userMetadata, modifier = weight, label = "UsernameDisplay", accountViewModel = accountViewModel) {
val name = it?.bestName()
if (name != null) {
UserDisplay(name, it.tags, weight, fontWeight, textColor)
@@ -164,7 +168,8 @@ private fun speak(
context: Context,
owner: LifecycleOwner,
) {
TextToSpeechHelper.getInstance(context)
TextToSpeechHelper
.getInstance(context)
.registerLifecycle(owner)
.speak(message)
.highlight()
@@ -20,7 +20,6 @@
*/
package com.vitorpamplona.amethyst.ui.note
import androidx.compose.animation.Crossfade
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.combinedClickable
import androidx.compose.runtime.Composable
@@ -29,6 +28,7 @@ import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
@OptIn(ExperimentalFoundationApi::class)
@@ -57,6 +57,7 @@ fun WatchNoteEvent(
)
}
},
accountViewModel = accountViewModel,
)
}
@@ -65,6 +66,7 @@ fun WatchNoteEvent(
baseNote: Note,
onNoteEventFound: @Composable () -> Unit,
onBlank: @Composable () -> Unit,
accountViewModel: AccountViewModel,
) {
if (baseNote.event != null) {
onNoteEventFound()
@@ -72,7 +74,7 @@ fun WatchNoteEvent(
// avoid observing costs if already has an event.
val hasEvent by baseNote.live().hasEvent.observeAsState(baseNote.event != null)
Crossfade(targetState = hasEvent, label = "Event presence") {
CrossfadeIfEnabled(targetState = hasEvent, label = "Event presence", accountViewModel = accountViewModel) {
if (it) {
onNoteEventFound()
} else {
@@ -102,13 +102,9 @@ class ZapOptionstViewModel : ViewModel() {
this.account = account
}
fun canSend(): Boolean {
return value() != null
}
fun canSend(): Boolean = value() != null
fun value(): Long? {
return customAmount.text.trim().toLongOrNull()
}
fun value(): Long? = customAmount.text.trim().toLongOrNull()
fun cancel() {}
}
@@ -383,14 +379,14 @@ fun PayViaIntentDialog(
if (it.user != null) {
BaseUserPicture(it.user, Size55dp, accountViewModel = accountViewModel)
} else {
DisplayBlankAuthor(size = Size55dp)
DisplayBlankAuthor(size = Size55dp, accountViewModel = accountViewModel)
}
Spacer(modifier = DoubleHorzSpacer)
Column(modifier = Modifier.weight(1f)) {
if (it.user != null) {
UsernameDisplay(it.user)
UsernameDisplay(it.user, accountViewModel = accountViewModel)
} else {
Text(
text = stringResource(id = R.string.wallet_number, index + 1),
@@ -65,7 +65,10 @@ fun ZapNoteCompose(
accountViewModel: AccountViewModel,
nav: (String) -> Unit,
) {
val baseNoteRequest by baseReqResponse.zapRequest.live().metadata.observeAsState()
val baseNoteRequest by baseReqResponse.zapRequest
.live()
.metadata
.observeAsState()
var baseAuthor by remember { mutableStateOf<User?>(null) }
@@ -115,7 +118,7 @@ private fun RenderZapNote(
Column(
modifier = remember { Modifier.padding(start = 10.dp).weight(1f) },
) {
Row(verticalAlignment = Alignment.CenterVertically) { UsernameDisplay(baseAuthor) }
Row(verticalAlignment = Alignment.CenterVertically) { UsernameDisplay(baseAuthor, accountViewModel = accountViewModel) }
Row(verticalAlignment = Alignment.CenterVertically) { AboutDisplay(baseAuthor) }
}
@@ -179,7 +182,11 @@ fun ShowFollowingOrUnfollowingButton(
accountViewModel: AccountViewModel,
) {
var isFollowing by remember { mutableStateOf(false) }
val accountFollowsState by accountViewModel.account.userProfile().live().follows.observeAsState()
val accountFollowsState by accountViewModel.account
.userProfile()
.live()
.follows
.observeAsState()
LaunchedEffect(key1 = accountFollowsState) {
launch(Dispatchers.Default) {
@@ -102,7 +102,7 @@ fun ZapUserSetCompose(
)
Column(modifier = remember { Modifier.padding(start = 10.dp).weight(1f) }) {
Row(verticalAlignment = Alignment.CenterVertically) { UsernameDisplay(zapSetCard.user) }
Row(verticalAlignment = Alignment.CenterVertically) { UsernameDisplay(zapSetCard.user, accountViewModel = accountViewModel) }
AboutDisplay(zapSetCard.user)
}
@@ -21,7 +21,6 @@
package com.vitorpamplona.amethyst.ui.note.elements
import android.content.Context
import androidx.compose.animation.Crossfade
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
@@ -68,6 +67,7 @@ import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.ThemeType
import com.vitorpamplona.amethyst.service.ZapPaymentHandler
import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled
import com.vitorpamplona.amethyst.ui.components.ClickableText
import com.vitorpamplona.amethyst.ui.components.LoadNote
import com.vitorpamplona.amethyst.ui.navigation.routeFor
@@ -443,7 +443,7 @@ fun ZapDonationButton(
}
}
Crossfade(targetState = wasZappedByLoggedInUser.value, label = "ZapIcon") {
CrossfadeIfEnabled(targetState = wasZappedByLoggedInUser.value, label = "ZapIcon", accountViewModel = accountViewModel) {
if (it) {
ZappedIcon(iconSizeModifier)
} else {
@@ -123,7 +123,7 @@ fun AudioTrackHeader(
) {
ClickableUserPicture(it.second, 25.dp, accountViewModel)
Spacer(Modifier.width(5.dp))
UsernameDisplay(it.second, Modifier.weight(1f))
UsernameDisplay(it.second, Modifier.weight(1f), accountViewModel = accountViewModel)
Spacer(Modifier.width(5.dp))
it.first.role?.let {
Text(
@@ -47,6 +47,7 @@ import androidx.lifecycle.distinctUntilChanged
import androidx.lifecycle.map
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.AddressableNote
import com.vitorpamplona.amethyst.model.FeatureSetType
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.ui.components.RobohashFallbackAsyncImage
@@ -188,7 +189,7 @@ fun LongCommunityHeader(
Spacer(DoubleHorzSpacer)
NoteAuthorPicture(baseNote, nav, accountViewModel, Size25dp)
Spacer(DoubleHorzSpacer)
NoteUsernameDisplay(baseNote, remember { Modifier.weight(1f) })
NoteUsernameDisplay(baseNote, Modifier.weight(1f), accountViewModel = accountViewModel)
}
var participantUsers by
@@ -228,7 +229,7 @@ fun LongCommunityHeader(
Spacer(DoubleHorzSpacer)
ClickableUserPicture(it.second, Size25dp, accountViewModel)
Spacer(DoubleHorzSpacer)
UsernameDisplay(it.second, remember { Modifier.weight(1f) })
UsernameDisplay(it.second, Modifier.weight(1f), accountViewModel = accountViewModel)
}
}
@@ -258,11 +259,6 @@ fun ShortCommunityHeader(
val noteEvent =
remember(noteState) { noteState?.note?.event as? CommunityDefinitionEvent } ?: return
val automaticallyShowProfilePicture =
remember {
accountViewModel.settings.showProfilePictures.value
}
Row(verticalAlignment = Alignment.CenterVertically) {
noteEvent.image()?.let {
RobohashFallbackAsyncImage(
@@ -271,7 +267,8 @@ fun ShortCommunityHeader(
contentDescription = stringResource(R.string.profile_image),
contentScale = ContentScale.Crop,
modifier = HeaderPictureModifier,
loadProfilePicture = automaticallyShowProfilePicture,
loadProfilePicture = accountViewModel.settings.showProfilePictures.value,
loadRobohash = accountViewModel.settings.featureSet != FeatureSetType.PERFORMANCE,
)
}
@@ -20,7 +20,6 @@
*/
package com.vitorpamplona.amethyst.ui.note.types
import androidx.compose.animation.Crossfade
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
@@ -49,6 +48,7 @@ import androidx.lifecycle.distinctUntilChanged
import androidx.lifecycle.map
import coil.compose.AsyncImage
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled
import com.vitorpamplona.amethyst.ui.components.ShowMoreButton
import com.vitorpamplona.amethyst.ui.note.LoadAddressableNote
import com.vitorpamplona.amethyst.ui.note.elements.AddButton
@@ -195,10 +195,9 @@ private fun EmojiListOptions(
.metadata
.map { usersEmojiList.event?.isTaggedAddressableNote(emojiPackNote.idHex) }
.distinctUntilChanged()
}
.observeAsState()
}.observeAsState()
Crossfade(targetState = hasAddedThis, label = "EmojiListOptions") {
CrossfadeIfEnabled(targetState = hasAddedThis, label = "EmojiListOptions", accountViewModel = accountViewModel) {
if (it != true) {
AddButton { accountViewModel.addEmojiPack(usersEmojiList, emojiPackNote) }
} else {
@@ -20,7 +20,6 @@
*/
package com.vitorpamplona.amethyst.ui.note.types
import androidx.compose.animation.Crossfade
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
@@ -31,6 +30,7 @@ import com.vitorpamplona.amethyst.commons.richtext.BaseMediaContent
import com.vitorpamplona.amethyst.commons.richtext.MediaLocalImage
import com.vitorpamplona.amethyst.commons.richtext.MediaLocalVideo
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled
import com.vitorpamplona.amethyst.ui.components.LoadNote
import com.vitorpamplona.amethyst.ui.components.SensitivityWarning
import com.vitorpamplona.amethyst.ui.components.ZoomableContentView
@@ -106,7 +106,7 @@ private fun ObserverAndRenderNIP95(
mutableStateOf<BaseMediaContent?>(newContent)
}
Crossfade(targetState = content) {
CrossfadeIfEnabled(targetState = content, accountViewModel = accountViewModel) {
if (it != null) {
SensitivityWarning(note = header, accountViewModel = accountViewModel) {
ZoomableContentView(
@@ -20,7 +20,6 @@
*/
package com.vitorpamplona.amethyst.ui.note.types
import androidx.compose.animation.Crossfade
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
@@ -46,6 +45,7 @@ import androidx.compose.ui.unit.dp
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled
import com.vitorpamplona.amethyst.ui.components.VideoView
import com.vitorpamplona.amethyst.ui.note.ClickableUserPicture
import com.vitorpamplona.amethyst.ui.note.UsernameDisplay
@@ -116,7 +116,7 @@ fun RenderLiveActivityEventInner(
Spacer(modifier = StdHorzSpacer)
Crossfade(targetState = status, label = "RenderLiveActivityEventInner") {
CrossfadeIfEnabled(targetState = status, label = "RenderLiveActivityEventInner", accountViewModel = accountViewModel) {
when (it) {
LiveActivitiesEvent.STATUS_LIVE -> {
media?.let { CrossfadeCheckIfUrlIsOnline(it, accountViewModel) { LiveFlag() } }
@@ -205,7 +205,7 @@ fun RenderLiveActivityEventInner(
) {
ClickableUserPicture(it.second, 25.dp, accountViewModel)
Spacer(StdHorzSpacer)
UsernameDisplay(it.second, Modifier.weight(1f))
UsernameDisplay(it.second, Modifier.weight(1f), accountViewModel = accountViewModel)
Spacer(StdHorzSpacer)
it.first.role?.let {
Text(
@@ -77,6 +77,7 @@ fun ShowQRDialogPreview() {
ShowQRDialog(
user = user,
loadProfilePicture = false,
loadRobohash = false,
onScan = {},
onClose = {},
)
@@ -86,6 +87,7 @@ fun ShowQRDialogPreview() {
fun ShowQRDialog(
user: User,
loadProfilePicture: Boolean,
loadRobohash: Boolean,
onScan: (String) -> Unit,
onClose: () -> Unit,
) {
@@ -120,11 +122,13 @@ fun ShowQRDialog(
model = user.profilePicture(),
contentDescription = stringResource(R.string.profile_image),
modifier =
Modifier.width(100.dp)
Modifier
.width(100.dp)
.height(100.dp)
.clip(shape = CircleShape)
.border(3.dp, MaterialTheme.colorScheme.background, CircleShape),
loadProfilePicture = loadProfilePicture,
loadRobohash = loadRobohash,
)
}
Row(
@@ -20,7 +20,6 @@
*/
package com.vitorpamplona.amethyst.ui.screen
import androidx.compose.animation.Crossfade
import androidx.compose.animation.core.tween
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.layout.Row
@@ -41,6 +40,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.vitorpamplona.amethyst.BuildConfig
import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled
import com.vitorpamplona.amethyst.ui.components.LoadNote
import com.vitorpamplona.amethyst.ui.note.BadgeCompose
import com.vitorpamplona.amethyst.ui.note.MessageSetCompose
@@ -111,10 +111,11 @@ fun RenderCardFeed(
) {
val feedState by viewModel.feedContent.collectAsStateWithLifecycle()
Crossfade(
CrossfadeIfEnabled(
modifier = Modifier.fillMaxSize(),
targetState = feedState,
animationSpec = tween(durationMillis = 100),
accountViewModel = accountViewModel,
) { state ->
when (state) {
is CardFeedState.Empty -> {
@@ -20,7 +20,6 @@
*/
package com.vitorpamplona.amethyst.ui.screen
import androidx.compose.animation.Crossfade
import androidx.compose.animation.core.tween
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
@@ -38,6 +37,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled
import com.vitorpamplona.amethyst.ui.note.ChatroomMessageCompose
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.theme.FeedPadding
@@ -86,7 +86,7 @@ fun RenderChatroomFeedView(
) {
val feedState by viewModel.feedContent.collectAsStateWithLifecycle()
Crossfade(targetState = feedState, animationSpec = tween(durationMillis = 100)) { state ->
CrossfadeIfEnabled(targetState = feedState, animationSpec = tween(durationMillis = 100), accountViewModel = accountViewModel) { state ->
when (state) {
is FeedState.Empty -> {
FeedEmpty { viewModel.invalidateData() }
@@ -20,7 +20,6 @@
*/
package com.vitorpamplona.amethyst.ui.screen
import androidx.compose.animation.Crossfade
import androidx.compose.animation.core.tween
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
@@ -34,6 +33,7 @@ import androidx.compose.runtime.MutableState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled
import com.vitorpamplona.amethyst.ui.note.ChatroomHeaderCompose
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
@@ -58,9 +58,10 @@ private fun CrossFadeState(
) {
val feedState by viewModel.feedContent.collectAsStateWithLifecycle()
Crossfade(
CrossfadeIfEnabled(
targetState = feedState,
animationSpec = tween(durationMillis = 100),
accountViewModel = accountViewModel,
) { state ->
when (state) {
is FeedState.Empty -> {
@@ -20,7 +20,6 @@
*/
package com.vitorpamplona.amethyst.ui.screen
import androidx.compose.animation.Crossfade
import androidx.compose.animation.core.tween
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.layout.Arrangement
@@ -55,6 +54,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled
import com.vitorpamplona.amethyst.ui.note.NoteCompose
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
@@ -105,12 +105,10 @@ fun RefresheableBox(
val pullRefreshState = rememberPullRefreshState(refreshing, onRefresh = refresh)
val modifier =
remember {
if (enablePullRefresh) {
Modifier.fillMaxSize().pullRefresh(pullRefreshState)
} else {
Modifier.fillMaxSize()
}
if (enablePullRefresh) {
Modifier.fillMaxSize().pullRefresh(pullRefreshState)
} else {
Modifier.fillMaxSize()
}
Box(modifier) {
@@ -120,7 +118,7 @@ fun RefresheableBox(
PullRefreshIndicator(
refreshing = refreshing,
state = pullRefreshState,
modifier = remember { Modifier.align(Alignment.TopCenter) },
modifier = Modifier.align(Alignment.TopCenter),
)
}
}
@@ -176,9 +174,10 @@ fun RenderFeedState(
) {
val feedState by viewModel.feedContent.collectAsStateWithLifecycle()
Crossfade(
CrossfadeIfEnabled(
targetState = feedState,
animationSpec = tween(durationMillis = 100),
accountViewModel = accountViewModel,
) { state ->
when (state) {
is FeedState.Empty -> onEmpty()
@@ -20,7 +20,6 @@
*/
package com.vitorpamplona.amethyst.ui.screen
import androidx.compose.animation.Crossfade
import androidx.compose.animation.core.tween
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
@@ -32,6 +31,7 @@ import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled
import com.vitorpamplona.amethyst.ui.note.ZapNoteCompose
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
@@ -45,7 +45,7 @@ fun LnZapFeedView(
) {
val feedState by viewModel.feedContent.collectAsStateWithLifecycle()
Crossfade(targetState = feedState, animationSpec = tween(durationMillis = 100)) { state ->
CrossfadeIfEnabled(targetState = feedState, animationSpec = tween(durationMillis = 100), accountViewModel = accountViewModel) { state ->
when (state) {
is LnZapFeedState.Empty -> {
FeedEmpty { viewModel.invalidateData() }
@@ -42,7 +42,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@Stable
class SettingsState() {
class SettingsState {
var theme by mutableStateOf(ThemeType.SYSTEM)
var language by mutableStateOf<String?>(null)
@@ -53,7 +53,7 @@ class SettingsState() {
var automaticallyShowProfilePictures by mutableStateOf(ConnectivityType.ALWAYS)
var dontShowPushNotificationSelector by mutableStateOf<Boolean>(false)
var dontAskForNotificationPermissions by mutableStateOf<Boolean>(false)
var featureSet by mutableStateOf(FeatureSetType.COMPLETE)
var featureSet by mutableStateOf(FeatureSetType.SIMPLIFIED)
var isOnMobileData: State<Boolean> = mutableStateOf(false)
@@ -20,7 +20,6 @@
*/
package com.vitorpamplona.amethyst.ui.screen
import androidx.compose.animation.Crossfade
import androidx.compose.animation.core.tween
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
@@ -38,19 +37,22 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
import com.vitorpamplona.amethyst.ui.theme.FeedPadding
@Composable
fun StringFeedView(
viewModel: StringFeedViewModel,
accountViewModel: AccountViewModel,
pre: (@Composable () -> Unit)? = null,
post: (@Composable () -> Unit)? = null,
inner: @Composable (String) -> Unit,
) {
val feedState by viewModel.feedContent.collectAsStateWithLifecycle()
Crossfade(targetState = feedState, animationSpec = tween(durationMillis = 100)) { state ->
CrossfadeIfEnabled(targetState = feedState, animationSpec = tween(durationMillis = 100), accountViewModel = accountViewModel) { state ->
when (state) {
is StringFeedState.Empty -> {
StringFeedEmpty(pre, post) { viewModel.invalidateData() }
@@ -402,7 +402,7 @@ private fun FullBleedNoteCompose(
Column(modifier = Modifier.padding(start = 10.dp)) {
Row(verticalAlignment = Alignment.CenterVertically) {
NoteUsernameDisplay(baseNote, Modifier.weight(1f))
NoteUsernameDisplay(baseNote, Modifier.weight(1f), accountViewModel = accountViewModel)
val isCommunityPost =
remember(baseNote) {
@@ -20,7 +20,6 @@
*/
package com.vitorpamplona.amethyst.ui.screen
import androidx.compose.animation.Crossfade
import androidx.compose.animation.core.tween
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
@@ -29,6 +28,7 @@ import androidx.compose.material3.HorizontalDivider
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled
import com.vitorpamplona.amethyst.ui.note.UserCompose
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
@@ -52,7 +52,7 @@ fun UserFeedView(
) {
val feedState by viewModel.feedContent.collectAsStateWithLifecycle()
Crossfade(targetState = feedState, animationSpec = tween(durationMillis = 100)) { state ->
CrossfadeIfEnabled(targetState = feedState, animationSpec = tween(durationMillis = 100), accountViewModel = accountViewModel) { state ->
when (state) {
is UserFeedState.Empty -> {
FeedEmpty { viewModel.invalidateData() }
@@ -104,6 +104,7 @@ import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.commons.richtext.MediaUrlVideo
import com.vitorpamplona.amethyst.model.AddressableNote
import com.vitorpamplona.amethyst.model.Channel
import com.vitorpamplona.amethyst.model.FeatureSetType
import com.vitorpamplona.amethyst.model.LiveActivitiesChannel
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note
@@ -763,11 +764,6 @@ fun ShortChannelHeader(
val channelState by baseChannel.live.observeAsState()
val channel = channelState?.channel ?: return
val automaticallyShowProfilePicture =
remember {
accountViewModel.settings.showProfilePictures.value
}
Row(verticalAlignment = Alignment.CenterVertically) {
if (channel is LiveActivitiesChannel) {
channel.creator?.let {
@@ -786,7 +782,8 @@ fun ShortChannelHeader(
contentDescription = stringResource(R.string.profile_image),
contentScale = ContentScale.Crop,
modifier = HeaderPictureModifier,
loadProfilePicture = automaticallyShowProfilePicture,
loadProfilePicture = accountViewModel.settings.showProfilePictures.value,
loadRobohash = accountViewModel.settings.featureSet != FeatureSetType.PERFORMANCE,
)
}
}
@@ -902,7 +899,7 @@ fun LongChannelHeader(
Spacer(DoubleHorzSpacer)
NoteAuthorPicture(note, nav, accountViewModel, Size25dp)
Spacer(DoubleHorzSpacer)
NoteUsernameDisplay(note, remember { Modifier.weight(1f) })
NoteUsernameDisplay(note, Modifier.weight(1f), accountViewModel = accountViewModel)
}
Row(
@@ -963,7 +960,7 @@ fun LongChannelHeader(
Spacer(DoubleHorzSpacer)
ClickableUserPicture(it.second, Size25dp, accountViewModel)
Spacer(DoubleHorzSpacer)
UsernameDisplay(it.second, remember { Modifier.weight(1f) })
UsernameDisplay(it.second, Modifier.weight(1f), accountViewModel = accountViewModel)
}
}
}
@@ -21,7 +21,6 @@
package com.vitorpamplona.amethyst.ui.screen.loggedIn
import android.widget.Toast
import androidx.compose.animation.Crossfade
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
@@ -89,6 +88,7 @@ import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.service.NostrChatroomDataSource
import com.vitorpamplona.amethyst.ui.actions.CloseButton
import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled
import com.vitorpamplona.amethyst.ui.actions.NewPostViewModel
import com.vitorpamplona.amethyst.ui.actions.PostButton
import com.vitorpamplona.amethyst.ui.actions.ServerOption
@@ -646,7 +646,7 @@ fun ChatroomHeader(
)
Column(modifier = Modifier.padding(start = 10.dp)) {
UsernameDisplay(baseUser)
UsernameDisplay(baseUser, accountViewModel = accountViewModel)
}
}
}
@@ -678,7 +678,7 @@ fun GroupChatroomHeader(
)
Column(modifier = Modifier.padding(start = 10.dp)) {
RoomNameOnlyDisplay(room, Modifier, FontWeight.Bold, accountViewModel.userProfile())
RoomNameOnlyDisplay(room, Modifier, FontWeight.Bold, accountViewModel)
DisplayUserSetAsSubject(room, accountViewModel, FontWeight.Normal)
}
}
@@ -892,17 +892,18 @@ fun RoomNameOnlyDisplay(
room: ChatroomKey,
modifier: Modifier,
fontWeight: FontWeight = FontWeight.Bold,
loggedInUser: User,
accountViewModel: AccountViewModel,
) {
val roomSubject by
loggedInUser
accountViewModel
.userProfile()
.live()
.messages
.map { it.user.privateChatrooms[room]?.subject }
.distinctUntilChanged()
.observeAsState(loggedInUser.privateChatrooms[room]?.subject)
.observeAsState(accountViewModel.userProfile().privateChatrooms[room]?.subject)
Crossfade(targetState = roomSubject, modifier) {
CrossfadeIfEnabled(targetState = roomSubject, modifier, accountViewModel = accountViewModel) {
if (it != null && it.isNotBlank()) {
DisplayRoomSubject(it, fontWeight)
}
@@ -20,7 +20,6 @@
*/
package com.vitorpamplona.amethyst.ui.screen.loggedIn
import androidx.compose.animation.Crossfade
import androidx.compose.animation.core.tween
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.layout.Column
@@ -59,6 +58,7 @@ import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.service.NostrDiscoveryDataSource
import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled
import com.vitorpamplona.amethyst.ui.navigation.Route
import com.vitorpamplona.amethyst.ui.note.ChannelCardCompose
import com.vitorpamplona.amethyst.ui.screen.FeedEmpty
@@ -146,8 +146,7 @@ fun DiscoverScreen(
ScrollStateKeys.DISCOVER_CHATS,
ChannelCreateEvent.KIND,
),
)
.toImmutableList(),
).toImmutableList(),
)
}
@@ -213,8 +212,7 @@ private fun DiscoverPages(
HorizontalPager(state = pagerState) { page ->
RefresheableBox(tabs[page].viewModel, true) {
if (tabs[page].viewModel is NostrDiscoverMarketplaceFeedViewModel) {
SaveableGridFeedState(tabs[page].viewModel, scrollStateKey = tabs[page].scrollStateKey) {
listState ->
SaveableGridFeedState(tabs[page].viewModel, scrollStateKey = tabs[page].scrollStateKey) { listState ->
RenderDiscoverFeed(
viewModel = tabs[page].viewModel,
routeForLastRead = tabs[page].routeForLastRead,
@@ -225,8 +223,7 @@ private fun DiscoverPages(
)
}
} else {
SaveableFeedState(tabs[page].viewModel, scrollStateKey = tabs[page].scrollStateKey) {
listState ->
SaveableFeedState(tabs[page].viewModel, scrollStateKey = tabs[page].scrollStateKey) { listState ->
RenderDiscoverFeed(
viewModel = tabs[page].viewModel,
routeForLastRead = tabs[page].routeForLastRead,
@@ -252,10 +249,11 @@ private fun RenderDiscoverFeed(
) {
val feedState by viewModel.feedContent.collectAsStateWithLifecycle()
Crossfade(
CrossfadeIfEnabled(
targetState = feedState,
animationSpec = tween(durationMillis = 100),
label = "RenderDiscoverFeed",
accountViewModel = accountViewModel,
) { state ->
when (state) {
is FeedState.Empty -> {
@@ -292,10 +290,11 @@ private fun RenderDiscoverFeed(
) {
val feedState by viewModel.feedContent.collectAsStateWithLifecycle()
Crossfade(
CrossfadeIfEnabled(
targetState = feedState,
animationSpec = tween(durationMillis = 100),
label = "RenderDiscoverFeed",
accountViewModel = accountViewModel,
) { state ->
when (state) {
is FeedState.Empty -> {
@@ -219,6 +219,7 @@ private fun HiddenWordsFeed(
RefresheableBox(hiddenWordsViewModel, false) {
StringFeedView(
hiddenWordsViewModel,
accountViewModel,
post = { AddMuteWordTextField(accountViewModel) },
) {
MutedWordHeader(tag = it, account = accountViewModel)
@@ -20,7 +20,6 @@
*/
package com.vitorpamplona.amethyst.ui.screen.loggedIn
import androidx.compose.animation.Crossfade
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
@@ -55,6 +54,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.service.NostrHomeDataSource
import com.vitorpamplona.amethyst.service.OnlineChecker
import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled
import com.vitorpamplona.amethyst.ui.navigation.Route
import com.vitorpamplona.amethyst.ui.note.UpdateZapAmountDialog
import com.vitorpamplona.amethyst.ui.screen.FeedViewModel
@@ -289,9 +289,10 @@ fun CrossfadeCheckIfUrlIsOnline(
}
}
Crossfade(
CrossfadeIfEnabled(
targetState = online,
label = "CheckIfUrlIsOnline",
accountViewModel = accountViewModel,
) {
if (it) {
whenOnline()
@@ -74,6 +74,7 @@ import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavBackStackEntry
import androidx.navigation.NavHostController
import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController
import com.vitorpamplona.amethyst.R
@@ -141,7 +142,6 @@ fun MainScreen(
}
val navController = rememberNavController()
val navState = navController.currentBackStackEntryAsState()
val orientation = LocalConfiguration.current.orientation
val currentDrawerState = drawerState.currentValue
@@ -165,9 +165,6 @@ fun MainScreen(
}
}
DisplayErrorMessages(accountViewModel)
DisplayNotifyMessages(accountViewModel, nav)
val navPopBack =
remember(navController) {
{
@@ -176,7 +173,10 @@ fun MainScreen(
}
}
val followLists: FollowListViewModel =
DisplayErrorMessages(accountViewModel)
DisplayNotifyMessages(accountViewModel, nav)
val followListsViewModel: FollowListViewModel =
viewModel(
key = "FollowListViewModel",
factory = FollowListViewModel.Factory(accountViewModel.account),
@@ -297,52 +297,6 @@ fun MainScreen(
}
}
val bottomBarHeightPx = with(LocalDensity.current) { 50.dp.roundToPx().toFloat() }
val bottomBarOffsetHeightPx = remember { mutableFloatStateOf(0f) }
val shouldShow = remember { mutableStateOf(true) }
val nestedScrollConnection =
remember {
object : NestedScrollConnection {
override fun onPreScroll(
available: Offset,
source: NestedScrollSource,
): Offset {
val newOffset = bottomBarOffsetHeightPx.floatValue + available.y
if (accountViewModel.settings.automaticallyHideNavigationBars == BooleanType.ALWAYS) {
val newBottomBarOffset =
if (navState.value?.destination?.route !in InvertedLayouts) {
newOffset.coerceIn(-bottomBarHeightPx, 0f)
} else {
newOffset.coerceIn(0f, bottomBarHeightPx)
}
if (newBottomBarOffset != bottomBarOffsetHeightPx.floatValue) {
bottomBarOffsetHeightPx.floatValue = newBottomBarOffset
}
} else {
if (abs(bottomBarOffsetHeightPx.floatValue) > 0.1) {
bottomBarOffsetHeightPx.floatValue = 0f
}
}
val newShouldShow = abs(bottomBarOffsetHeightPx.floatValue) < bottomBarHeightPx / 2.0f
if (shouldShow.value != newShouldShow) {
shouldShow.value = newShouldShow
}
return Offset.Zero
}
}
}
WatchNavStateToUpdateBarVisibility(navState) {
bottomBarOffsetHeightPx.floatValue = 0f
shouldShow.value = true
}
ModalNavigationDrawer(
drawerState = drawerState,
drawerContent = {
@@ -350,87 +304,29 @@ fun MainScreen(
BackHandler(enabled = drawerState.isOpen) { scope.launch { drawerState.close() } }
},
content = {
Scaffold(
// containerColor = Color.Unspecified,
modifier =
Modifier
.statusBarsPadding()
.nestedScroll(nestedScrollConnection),
bottomBar = {
AnimatedContent(
targetState = shouldShow.value,
transitionSpec = AnimatedContentTransitionScope<Boolean>::bottomBarTransitionSpec,
label = "BottomBarAnimatedContent",
) { isVisible ->
if (isVisible) {
AppBottomBar(accountViewModel, navState, navBottomRow)
}
}
},
topBar = {
AnimatedContent(
targetState = shouldShow.value,
transitionSpec = AnimatedContentTransitionScope<Boolean>::topBarTransitionSpec,
label = "TopBarAnimatedContent",
) { isVisible ->
if (isVisible) {
AppTopBar(
followLists,
navState,
drawerState,
accountViewModel,
nav = nav,
navPopBack,
)
}
}
},
floatingActionButton = {
AnimatedVisibility(
visible = shouldShow.value,
enter = remember { scaleIn() },
exit = remember { scaleOut() },
) {
Box(
modifier = Modifier.defaultMinSize(minWidth = 55.dp, minHeight = 55.dp),
) {
FloatingButtons(
navState,
accountViewModel,
accountStateViewModel,
nav,
navBottomRow,
)
}
}
},
) {
Column(
modifier =
Modifier.padding(
top = it.calculateTopPadding(),
bottom = it.calculateBottomPadding(),
),
) {
AppNavigation(
homeFeedViewModel = homeFeedViewModel,
repliesFeedViewModel = repliesFeedViewModel,
knownFeedViewModel = knownFeedViewModel,
newFeedViewModel = newFeedViewModel,
videoFeedViewModel = videoFeedViewModel,
discoverNip89FeedViewModel = discoverNIP89FeedViewModel,
discoverMarketplaceFeedViewModel = discoverMarketplaceFeedViewModel,
discoveryLiveFeedViewModel = discoveryLiveFeedViewModel,
discoveryCommunityFeedViewModel = discoveryCommunityFeedViewModel,
discoveryChatFeedViewModel = discoveryChatFeedViewModel,
notifFeedViewModel = notifFeedViewModel,
userReactionsStatsModel = userReactionsStatsModel,
navController = navController,
accountViewModel = accountViewModel,
sharedPreferencesViewModel = sharedPreferencesViewModel,
)
}
}
MainScaffold(
navController = navController,
navBottomRow = navBottomRow,
navPopBack = navPopBack,
openDrawer = { scope.launch { drawerState.open() } },
accountStateViewModel = accountStateViewModel,
userReactionsStatsModel = userReactionsStatsModel,
followListsViewModel = followListsViewModel,
homeFeedViewModel = homeFeedViewModel,
repliesFeedViewModel = repliesFeedViewModel,
knownFeedViewModel = knownFeedViewModel,
newFeedViewModel = newFeedViewModel,
videoFeedViewModel = videoFeedViewModel,
discoverNIP89FeedViewModel = discoverNIP89FeedViewModel,
discoverMarketplaceFeedViewModel = discoverMarketplaceFeedViewModel,
discoveryLiveFeedViewModel = discoveryLiveFeedViewModel,
discoveryCommunityFeedViewModel = discoveryCommunityFeedViewModel,
discoveryChatFeedViewModel = discoveryChatFeedViewModel,
notifFeedViewModel = notifFeedViewModel,
sharedPreferencesViewModel = sharedPreferencesViewModel,
accountViewModel = accountViewModel,
nav = nav,
)
},
)
@@ -456,6 +352,168 @@ fun MainScreen(
}
}
@Composable
private fun MainScaffold(
navController: NavHostController,
navBottomRow: (Route, Boolean) -> Unit,
navPopBack: () -> Unit,
openDrawer: () -> Unit,
accountStateViewModel: AccountStateViewModel,
userReactionsStatsModel: UserReactionsViewModel,
followListsViewModel: FollowListViewModel,
homeFeedViewModel: NostrHomeFeedViewModel,
repliesFeedViewModel: NostrHomeRepliesFeedViewModel,
knownFeedViewModel: NostrChatroomListKnownFeedViewModel,
newFeedViewModel: NostrChatroomListNewFeedViewModel,
videoFeedViewModel: NostrVideoFeedViewModel,
discoverNIP89FeedViewModel: NostrDiscoverNIP89FeedViewModel,
discoverMarketplaceFeedViewModel: NostrDiscoverMarketplaceFeedViewModel,
discoveryLiveFeedViewModel: NostrDiscoverLiveFeedViewModel,
discoveryCommunityFeedViewModel: NostrDiscoverCommunityFeedViewModel,
discoveryChatFeedViewModel: NostrDiscoverChatFeedViewModel,
notifFeedViewModel: NotificationViewModel,
sharedPreferencesViewModel: SharedPreferencesViewModel,
accountViewModel: AccountViewModel,
nav: (String) -> Unit,
) {
val navState = navController.currentBackStackEntryAsState()
val shouldShow = remember { mutableStateOf(true) }
val modifier =
if (accountViewModel.settings.automaticallyHideNavigationBars == BooleanType.ALWAYS) {
val bottomBarHeightPx = with(LocalDensity.current) { 50.dp.roundToPx().toFloat() }
val bottomBarOffsetHeightPx = remember { mutableFloatStateOf(0f) }
val nestedScrollConnection =
remember {
object : NestedScrollConnection {
override fun onPreScroll(
available: Offset,
source: NestedScrollSource,
): Offset {
val newOffset = bottomBarOffsetHeightPx.floatValue + available.y
if (accountViewModel.settings.automaticallyHideNavigationBars == BooleanType.ALWAYS) {
val newBottomBarOffset =
if (navState.value?.destination?.route !in InvertedLayouts) {
newOffset.coerceIn(-bottomBarHeightPx, 0f)
} else {
newOffset.coerceIn(0f, bottomBarHeightPx)
}
if (newBottomBarOffset != bottomBarOffsetHeightPx.floatValue) {
bottomBarOffsetHeightPx.floatValue = newBottomBarOffset
}
} else {
if (abs(bottomBarOffsetHeightPx.floatValue) > 0.1) {
bottomBarOffsetHeightPx.floatValue = 0f
}
}
val newShouldShow = abs(bottomBarOffsetHeightPx.floatValue) < bottomBarHeightPx / 2.0f
if (shouldShow.value != newShouldShow) {
shouldShow.value = newShouldShow
}
return Offset.Zero
}
}
}
WatchNavStateToUpdateBarVisibility(navState) {
bottomBarOffsetHeightPx.floatValue = 0f
shouldShow.value = true
}
Modifier
.statusBarsPadding()
.nestedScroll(nestedScrollConnection)
} else {
Modifier
.statusBarsPadding()
}
Scaffold(
modifier = modifier,
bottomBar = {
AnimatedContent(
targetState = shouldShow.value,
transitionSpec = AnimatedContentTransitionScope<Boolean>::bottomBarTransitionSpec,
label = "BottomBarAnimatedContent",
) { isVisible ->
if (isVisible) {
AppBottomBar(accountViewModel, navState, navBottomRow)
}
}
},
topBar = {
AnimatedContent(
targetState = shouldShow.value,
transitionSpec = AnimatedContentTransitionScope<Boolean>::topBarTransitionSpec,
label = "TopBarAnimatedContent",
) { isVisible ->
if (isVisible) {
AppTopBar(
followListsViewModel,
navState,
openDrawer,
accountViewModel,
nav = nav,
navPopBack,
)
}
}
},
floatingActionButton = {
AnimatedVisibility(
visible = shouldShow.value,
enter = remember { scaleIn() },
exit = remember { scaleOut() },
) {
Box(
modifier = Modifier.defaultMinSize(minWidth = 55.dp, minHeight = 55.dp),
) {
FloatingButtons(
navState,
accountViewModel,
accountStateViewModel,
nav,
navBottomRow,
)
}
}
},
) {
Column(
modifier =
Modifier.padding(
top = it.calculateTopPadding(),
bottom = it.calculateBottomPadding(),
),
) {
AppNavigation(
homeFeedViewModel = homeFeedViewModel,
repliesFeedViewModel = repliesFeedViewModel,
knownFeedViewModel = knownFeedViewModel,
newFeedViewModel = newFeedViewModel,
videoFeedViewModel = videoFeedViewModel,
discoverNip89FeedViewModel = discoverNIP89FeedViewModel,
discoverMarketplaceFeedViewModel = discoverMarketplaceFeedViewModel,
discoveryLiveFeedViewModel = discoveryLiveFeedViewModel,
discoveryCommunityFeedViewModel = discoveryCommunityFeedViewModel,
discoveryChatFeedViewModel = discoveryChatFeedViewModel,
notifFeedViewModel = notifFeedViewModel,
userReactionsStatsModel = userReactionsStatsModel,
navController = navController,
accountViewModel = accountViewModel,
sharedPreferencesViewModel = sharedPreferencesViewModel,
)
}
}
}
@OptIn(ExperimentalAnimationApi::class)
private fun <S> AnimatedContentTransitionScope<S>.topBarTransitionSpec(): ContentTransform = topBarAnimation
@@ -20,7 +20,6 @@
*/
package com.vitorpamplona.amethyst.ui.screen.loggedIn
import androidx.compose.animation.Crossfade
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
@@ -66,6 +65,7 @@ import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.service.ZapPaymentHandler
import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled
import com.vitorpamplona.amethyst.ui.components.LoadNote
import com.vitorpamplona.amethyst.ui.navigation.routeToMessage
import com.vitorpamplona.amethyst.ui.note.DVMCard
@@ -117,6 +117,7 @@ fun NIP90ContentDiscoveryScreen(
onBlank = {
FeedEmptyWithStatus(baseNote, stringResource(R.string.dvm_looking_for_app), accountViewModel, nav)
},
accountViewModel,
)
}
}
@@ -530,8 +531,7 @@ fun ZapDVMButton(
targetValue = zappingProgress,
animationSpec = ProgressIndicatorDefaults.ProgressAnimationSpec,
label = "ZapIconIndicator",
)
.value,
).value,
modifier = remember { Modifier.size(animationSize) },
strokeWidth = 2.dp,
color = grayTint,
@@ -549,7 +549,7 @@ fun ZapDVMButton(
}
}
Crossfade(targetState = wasZappedByLoggedInUser.value, label = "ZapIcon") {
CrossfadeIfEnabled(targetState = wasZappedByLoggedInUser.value, label = "ZapIcon", accountViewModel = accountViewModel) {
if (it) {
ZappedIcon(iconSizeModifier)
} else {
@@ -632,8 +632,7 @@ fun observeAppDefinition(appDefinitionNote: Note): DVMCard {
description = noteEvent?.appMetaData()?.about ?: "",
cover = noteEvent?.appMetaData()?.image?.ifBlank { null },
)
}
.distinctUntilChanged()
}.distinctUntilChanged()
.observeAsState(
DVMCard(
name = noteEvent.appMetaData()?.name ?: "",
@@ -20,7 +20,6 @@
*/
package com.vitorpamplona.amethyst.ui.screen.loggedIn
import androidx.compose.animation.Crossfade
import androidx.compose.animation.core.tween
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.Image
@@ -117,10 +116,12 @@ import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.commons.richtext.RichTextParser
import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.AddressableNote
import com.vitorpamplona.amethyst.model.FeatureSetType
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.service.NostrUserProfileDataSource
import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled
import com.vitorpamplona.amethyst.ui.actions.InformationDialog
import com.vitorpamplona.amethyst.ui.actions.NewUserMetadataView
import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji
@@ -957,11 +958,6 @@ private fun DrawAdditionalInfo(
val uri = LocalUriHandler.current
val clipboardManager = LocalClipboardManager.current
val automaticallyShowProfilePicture =
remember {
accountViewModel.settings.showProfilePictures.value
}
user.toBestDisplayName().let {
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(top = 7.dp)) {
CreateTextWithEmoji(
@@ -1002,7 +998,8 @@ private fun DrawAdditionalInfo(
if (dialogOpen) {
ShowQRDialog(
user,
automaticallyShowProfilePicture,
accountViewModel.settings.showProfilePictures.value,
loadRobohash = accountViewModel.settings.featureSet != FeatureSetType.PERFORMANCE,
onScan = {
dialogOpen = false
nav(it)
@@ -1105,7 +1102,7 @@ private fun DrawAdditionalInfo(
}
}
DisplayAppRecommendations(appRecommendations, nav)
DisplayAppRecommendations(appRecommendations, accountViewModel, nav)
}
@Composable
@@ -1199,15 +1196,17 @@ fun DisplayLNAddress(
@OptIn(ExperimentalLayoutApi::class)
private fun DisplayAppRecommendations(
appRecommendations: NostrUserAppRecommendationsFeedViewModel,
accountViewModel: AccountViewModel,
nav: (String) -> Unit,
) {
val feedState by appRecommendations.feedContent.collectAsStateWithLifecycle()
LaunchedEffect(key1 = Unit) { appRecommendations.invalidateData() }
Crossfade(
CrossfadeIfEnabled(
targetState = feedState,
animationSpec = tween(durationMillis = 100),
accountViewModel = accountViewModel,
) { state ->
when (state) {
is FeedState.Loaded -> {
@@ -1274,17 +1273,17 @@ private fun DisplayBadges(
accountViewModel: AccountViewModel,
nav: (String) -> Unit,
) {
val automaticallyShowProfilePicture =
remember {
accountViewModel.settings.showProfilePictures.value
}
LoadAddressableNote(
aTag = BadgeProfilesEvent.createAddressTag(baseUser.pubkeyHex),
accountViewModel = accountViewModel,
) { note ->
if (note != null) {
WatchAndRenderBadgeList(note, automaticallyShowProfilePicture, nav)
WatchAndRenderBadgeList(
note = note,
loadProfilePicture = accountViewModel.settings.showProfilePictures.value,
loadRobohash = accountViewModel.settings.featureSet != FeatureSetType.PERFORMANCE,
nav = nav,
)
}
}
}
@@ -1293,6 +1292,7 @@ private fun DisplayBadges(
private fun WatchAndRenderBadgeList(
note: AddressableNote,
loadProfilePicture: Boolean,
loadRobohash: Boolean,
nav: (String) -> Unit,
) {
val badgeList by
@@ -1303,7 +1303,7 @@ private fun WatchAndRenderBadgeList(
.distinctUntilChanged()
.observeAsState()
badgeList?.let { list -> RenderBadgeList(list, loadProfilePicture, nav) }
badgeList?.let { list -> RenderBadgeList(list, loadProfilePicture, loadRobohash, nav) }
}
@Composable
@@ -1311,13 +1311,14 @@ private fun WatchAndRenderBadgeList(
private fun RenderBadgeList(
list: ImmutableList<String>,
loadProfilePicture: Boolean,
loadRobohash: Boolean,
nav: (String) -> Unit,
) {
FlowRow(
verticalArrangement = Arrangement.Center,
modifier = Modifier.padding(vertical = 5.dp),
) {
list.forEach { badgeAwardEvent -> LoadAndRenderBadge(badgeAwardEvent, loadProfilePicture, nav) }
list.forEach { badgeAwardEvent -> LoadAndRenderBadge(badgeAwardEvent, loadProfilePicture, loadRobohash, nav) }
}
}
@@ -1325,6 +1326,7 @@ private fun RenderBadgeList(
private fun LoadAndRenderBadge(
badgeAwardEventHex: String,
loadProfilePicture: Boolean,
loadRobohash: Boolean,
nav: (String) -> Unit,
) {
var baseNote by remember(badgeAwardEventHex) { mutableStateOf(LocalCache.getNoteIfExists(badgeAwardEventHex)) }
@@ -1337,37 +1339,40 @@ private fun LoadAndRenderBadge(
}
}
baseNote?.let { ObserveAndRenderBadge(it, loadProfilePicture, nav) }
baseNote?.let { ObserveAndRenderBadge(it, loadProfilePicture, loadRobohash, nav) }
}
@Composable
private fun ObserveAndRenderBadge(
it: Note,
loadProfilePicture: Boolean,
loadRobohash: Boolean,
nav: (String) -> Unit,
) {
val badgeAwardState by it.live().metadata.observeAsState()
val baseBadgeDefinition by
remember(badgeAwardState) { derivedStateOf { badgeAwardState?.note?.replyTo?.firstOrNull() } }
baseBadgeDefinition?.let { BadgeThumb(it, loadProfilePicture, nav, Size35dp) }
baseBadgeDefinition?.let { BadgeThumb(it, loadProfilePicture, loadRobohash, nav, Size35dp) }
}
@Composable
fun BadgeThumb(
note: Note,
loadProfilePicture: Boolean,
loadRobohash: Boolean,
nav: (String) -> Unit,
size: Dp,
pictureModifier: Modifier = Modifier,
) {
BadgeThumb(note, loadProfilePicture, size, pictureModifier) { nav("Note/${note.idHex}") }
BadgeThumb(note, loadProfilePicture, loadRobohash, size, pictureModifier) { nav("Note/${note.idHex}") }
}
@Composable
fun BadgeThumb(
baseNote: Note,
loadProfilePicture: Boolean,
loadRobohash: Boolean,
size: Dp,
pictureModifier: Modifier = Modifier,
onClick: ((String) -> Unit)? = null,
@@ -1379,7 +1384,7 @@ fun BadgeThumb(
.height(size)
},
) {
WatchAndRenderBadgeImage(baseNote, loadProfilePicture, size, pictureModifier, onClick)
WatchAndRenderBadgeImage(baseNote, loadProfilePicture, loadRobohash, size, pictureModifier, onClick)
}
}
@@ -1387,6 +1392,7 @@ fun BadgeThumb(
private fun WatchAndRenderBadgeImage(
baseNote: Note,
loadProfilePicture: Boolean,
loadRobohash: Boolean,
size: Dp,
pictureModifier: Modifier,
onClick: ((String) -> Unit)?,
@@ -1411,6 +1417,7 @@ private fun WatchAndRenderBadgeImage(
.width(size)
.height(size)
},
loadRobohash = loadRobohash,
)
} else {
RobohashFallbackAsyncImage(
@@ -1432,6 +1439,7 @@ private fun WatchAndRenderBadgeImage(
}
},
loadProfilePicture = loadProfilePicture,
loadRobohash = loadRobohash,
)
}
}
@@ -71,6 +71,7 @@ import androidx.lifecycle.viewmodel.compose.viewModel
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.Channel
import com.vitorpamplona.amethyst.model.FeatureSetType
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.User
@@ -168,7 +169,9 @@ fun WatchAccountForSearchScreen(accountViewModel: AccountViewModel) {
}
@Stable
class SearchBarViewModel(val account: Account) : ViewModel() {
class SearchBarViewModel(
val account: Account,
) : ViewModel() {
var searchValue by mutableStateOf("")
private var _searchResultsUsers = MutableStateFlow<List<User>>(emptyList())
@@ -198,12 +201,14 @@ class SearchBarViewModel(val account: Account) : ViewModel() {
_hashtagResults.emit(findHashtags(searchValue))
_searchResultsUsers.emit(
LocalCache.findUsersStartingWith(searchValue)
LocalCache
.findUsersStartingWith(searchValue)
.sortedWith(compareBy({ account.isFollowing(it) }, { it.toBestDisplayName() }))
.reversed(),
)
_searchResultsNotes.emit(
LocalCache.findNotesStartingWith(searchValue)
LocalCache
.findNotesStartingWith(searchValue)
.sortedWith(compareBy({ it.createdAt() }, { it.idHex }))
.reversed(),
)
@@ -236,10 +241,10 @@ class SearchBarViewModel(val account: Account) : ViewModel() {
fun isSearchingFun() = searchValue.isNotBlank()
class Factory(val account: Account) : ViewModelProvider.Factory {
override fun <SearchBarViewModel : ViewModel> create(modelClass: Class<SearchBarViewModel>): SearchBarViewModel {
return SearchBarViewModel(account) as SearchBarViewModel
}
class Factory(
val account: Account,
) : ViewModelProvider.Factory {
override fun <SearchBarViewModel : ViewModel> create(modelClass: Class<SearchBarViewModel>): SearchBarViewModel = SearchBarViewModel(account) as SearchBarViewModel
}
}
@@ -377,11 +382,6 @@ private fun DisplaySearchResults(
val hasNewMessages = remember { mutableStateOf(false) }
val automaticallyShowProfilePicture =
remember {
accountViewModel.settings.showProfilePictures.value
}
LazyColumn(
modifier = Modifier.fillMaxHeight(),
contentPadding = FeedPadding,
@@ -426,7 +426,8 @@ private fun DisplaySearchResults(
channelLastTime = null,
channelLastContent = item.summary(),
hasNewMessages = hasNewMessages,
loadProfilePicture = automaticallyShowProfilePicture,
loadProfilePicture = accountViewModel.settings.showProfilePictures.value,
loadRobohash = accountViewModel.settings.featureSet != FeatureSetType.PERFORMANCE,
onClick = { nav("Channel/${item.idHex}") },
)
@@ -500,7 +501,7 @@ fun UserLine(
Column(
modifier = Modifier.padding(start = 10.dp).weight(1f),
) {
Row(verticalAlignment = Alignment.CenterVertically) { UsernameDisplay(baseUser) }
Row(verticalAlignment = Alignment.CenterVertically) { UsernameDisplay(baseUser, accountViewModel = accountViewModel) }
AboutDisplay(baseUser)
}
@@ -147,6 +147,7 @@ fun SettingsScreen(sharedPreferencesViewModel: SharedPreferencesViewModel) {
persistentListOf(
TitleExplainer(stringResource(FeatureSetType.COMPLETE.resourceId)),
TitleExplainer(stringResource(FeatureSetType.SIMPLIFIED.resourceId)),
TitleExplainer(stringResource(FeatureSetType.PERFORMANCE.resourceId)),
)
val showImagesIndex = sharedPreferencesViewModel.sharedPrefs.automaticallyShowImages.screenCode
@@ -168,7 +169,8 @@ fun SettingsScreen(sharedPreferencesViewModel: SharedPreferencesViewModel) {
sharedPreferencesViewModel.sharedPrefs.featureSet.screenCode
Column(
Modifier.fillMaxSize()
Modifier
.fillMaxSize()
.padding(top = Size10dp, start = Size20dp, end = Size20dp)
.verticalScroll(rememberScrollState()),
horizontalAlignment = Alignment.CenterHorizontally,
@@ -20,7 +20,6 @@
*/
package com.vitorpamplona.amethyst.ui.screen.loggedIn
import androidx.compose.animation.Crossfade
import androidx.compose.animation.core.tween
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.layout.Arrangement
@@ -63,6 +62,7 @@ import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.FeatureSetType
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.service.NostrVideoDataSource
import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled
import com.vitorpamplona.amethyst.ui.actions.NewPostView
import com.vitorpamplona.amethyst.ui.components.ObserveDisplayNip05Status
import com.vitorpamplona.amethyst.ui.navigation.routeFor
@@ -176,10 +176,11 @@ fun RenderPage(
) {
val feedState by videoFeedView.feedContent.collectAsStateWithLifecycle()
Crossfade(
CrossfadeIfEnabled(
targetState = feedState,
animationSpec = tween(durationMillis = 100),
label = "RenderPage",
accountViewModel = accountViewModel,
) { state ->
when (state) {
is FeedState.Empty -> {
@@ -315,10 +316,10 @@ private fun RenderAuthorInformation(
verticalArrangement = Arrangement.Center,
) {
Row(verticalAlignment = Alignment.CenterVertically) {
NoteUsernameDisplay(note, remember { Modifier.weight(1f) })
NoteUsernameDisplay(note, Modifier.weight(1f), accountViewModel = accountViewModel)
VideoUserOptionAction(note, accountViewModel, nav)
}
if (accountViewModel.settings.featureSet != FeatureSetType.SIMPLIFIED) {
if (accountViewModel.settings.featureSet == FeatureSetType.COMPLETE) {
Row(verticalAlignment = Alignment.CenterVertically) {
ObserveDisplayNip05Status(
note.author!!,
+1
View File
@@ -515,6 +515,7 @@
<string name="ui_feature_set_type_complete">Complete</string>
<string name="ui_feature_set_type_simplified">Simplified</string>
<string name="ui_feature_set_type_performance">Performance</string>
<string name="system">System</string>
<string name="light">Light</string>
@@ -21,7 +21,6 @@
package com.vitorpamplona.amethyst.ui.components
import android.content.res.Resources
import androidx.compose.animation.Crossfade
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
@@ -59,6 +58,7 @@ import androidx.compose.ui.unit.dp
import androidx.core.os.ConfigurationCompat
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.service.lang.LanguageTranslatorService
import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
import com.vitorpamplona.amethyst.ui.theme.lessImportantLink
@@ -94,7 +94,7 @@ fun TranslatableRichTextViewer(
}
}
Crossfade(targetState = translatedTextState) {
CrossfadeIfEnabled(targetState = translatedTextState, accountViewModel = accountViewModel) {
RenderText(
translatedTextState = it,
content = content,
@@ -215,8 +215,7 @@ private fun TranslationMessage(
overflow = TextOverflow.Visible,
maxLines = 3,
) { spanOffset ->
annotatedTranslationString.getStringAnnotations(spanOffset, spanOffset).firstOrNull()?.also {
span ->
annotatedTranslationString.getStringAnnotations(spanOffset, spanOffset).firstOrNull()?.also { span ->
if (span.tag == "showOriginal") {
onChangeWhatToShow(span.item.toBoolean())
} else {
@@ -373,12 +372,12 @@ fun TranslateAndWatchLanguageChanges(
// This takes some time. Launches as a Composition scope to make sure this gets cancel if this
// item gets out of view.
withContext(Dispatchers.IO) {
LanguageTranslatorService.autoTranslate(
content,
accountViewModel.account.dontTranslateFrom,
accountViewModel.account.translateTo,
)
.addOnCompleteListener { task ->
LanguageTranslatorService
.autoTranslate(
content,
accountViewModel.account.dontTranslateFrom,
accountViewModel.account.translateTo,
).addOnCompleteListener { task ->
if (task.isSuccessful && !content.equals(task.result.result, true)) {
if (task.result.sourceLang != null && task.result.targetLang != null) {
val preference =