diff --git a/app/src/main/java/com/vitorpamplona/amethyst/model/Settings.kt b/app/src/main/java/com/vitorpamplona/amethyst/model/Settings.kt index 3d5993718..f1c433264 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/Settings.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/Settings.kt @@ -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 } } -} diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/CrossfadeIfEnabled.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/CrossfadeIfEnabled.kt new file mode 100644 index 000000000..145dcd44f --- /dev/null +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/CrossfadeIfEnabled.kt @@ -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 CrossfadeIfEnabled( + targetState: T, + modifier: Modifier = Modifier, + animationSpec: FiniteAnimationSpec = 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) + } +} diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/JoinUserOrChannelView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/JoinUserOrChannelView.kt index 4cda055f4..373b28c43 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/JoinUserOrChannelView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/JoinUserOrChannelView.kt @@ -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) } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostView.kt index 0cfe1e6e3..225f63b40 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostView.kt @@ -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, diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/BasicRelaySetupInfoClickableRow.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/BasicRelaySetupInfoClickableRow.kt index 045421fd6..89f40a14c 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/BasicRelaySetupInfoClickableRow.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/BasicRelaySetupInfoClickableRow.kt @@ -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, ) } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/BasicRelaySetupInfoDialog.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/BasicRelaySetupInfoDialog.kt index 5a4c8069e..3bd774fb7 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/BasicRelaySetupInfoDialog.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/BasicRelaySetupInfoDialog.kt @@ -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 = { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/Kind3RelayListView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/Kind3RelayListView.kt index 6eeae1716..f6199fc5b 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/Kind3RelayListView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/Kind3RelayListView.kt @@ -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() } }, ), diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/RelayInformationDialog.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/RelayInformationDialog.kt index 7ccc0cc44..695e47747 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/RelayInformationDialog.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/RelayInformationDialog.kt @@ -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, diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/CashuRedeem.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/CashuRedeem.kt index 8a8daec0d..2ef53e03a 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/CashuRedeem.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/CashuRedeem.kt @@ -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 -> CashuPreview(it.loaded, accountViewModel) is GenericLoadable.Error -> diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ClickableWithdrawal.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ClickableWithdrawal.kt index a46bddec4..67258d71f 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ClickableWithdrawal.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ClickableWithdrawal.kt @@ -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(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 { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ForwardingPainter.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ForwardingPainter.kt new file mode 100644 index 000000000..ab6cabd43 --- /dev/null +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ForwardingPainter.kt @@ -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) + } +} diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/InvoicePreview.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/InvoicePreview.kt index f7cc281c5..623c66991 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/InvoicePreview.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/InvoicePreview.kt @@ -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 { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/LoadUrlPreview.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/LoadUrlPreview.kt index 2d91cab5c..70a6f97e1 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/LoadUrlPreview.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/LoadUrlPreview.kt @@ -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 -> { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/RichTextViewer.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/RichTextViewer.kt index 7bcb04992..bfad6b30b 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/RichTextViewer.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/RichTextViewer.kt @@ -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() }, diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/RobohashAsyncImage.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/RobohashAsyncImage.kt index b0cd25766..35aa692d3 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/RobohashAsyncImage.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/RobohashAsyncImage.kt @@ -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, + ) + } } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/SensitivityWarning.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/SensitivityWarning.kt index fd19d837d..5d8dedc8d 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/SensitivityWarning.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/SensitivityWarning.kt @@ -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 { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AccountSwitchBottomSheet.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AccountSwitchBottomSheet.kt index 4114a8209..b8d251b46 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AccountSwitchBottomSheet.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AccountSwitchBottomSheet.kt @@ -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, ) } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppTopBar.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppTopBar.kt index 9fca1d6ff..7406e60e9 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppTopBar.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppTopBar.kt @@ -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, - 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, ) } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/DrawerContent.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/DrawerContent.kt index 8eb7f42ef..17a6c102b 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/DrawerContent.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/DrawerContent.kt @@ -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() } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ChannelCardCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ChannelCardCompose.kt index bf2a68778..986f2fd6c 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ChannelCardCompose.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ChannelCardCompose.kt @@ -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 diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ChatroomHeaderCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ChatroomHeaderCompose.kt index 61501fbd6..5c0b685d5 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ChatroomHeaderCompose.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ChatroomHeaderCompose.kt @@ -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(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, 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, diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ChatroomMessageCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ChatroomMessageCompose.kt index 4144b4b00..d508327e8 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ChatroomMessageCompose.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ChatroomMessageCompose.kt @@ -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? = 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, diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/MultiSetCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/MultiSetCompose.kt index 0ade7e95a..958c5e525 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/MultiSetCompose.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/MultiSetCompose.kt @@ -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) } } - // } } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NIP05VerificationDisplay.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NIP05VerificationDisplay.kt index 3f34d409b..5435d2f7d 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NIP05VerificationDisplay.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NIP05VerificationDisplay.kt @@ -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, + 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, 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) = diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt index 51107e486..6a483e2e7 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt @@ -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, ) } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ReactionsRow.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ReactionsRow.kt index 0d48b980a..46f899148 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ReactionsRow.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ReactionsRow.kt @@ -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 LiveData.combineWith( } @Composable -private fun RenderShowIndividualReactionsButton(wantsToSeeReactions: MutableState) { +private fun RenderShowIndividualReactionsButton( + wantsToSeeReactions: MutableState, + 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( - targetState = baseCount, - transitionSpec = AnimatedContentTransitionScope::transitionSpec, - label = "SlidingAnimationCount", - ) { count -> - TextCount(count, textColor) + if (accountViewModel.settings.featureSet == FeatureSetType.PERFORMANCE) { + TextCount(baseCount, textColor) + } else { + AnimatedContent( + targetState = baseCount, + transitionSpec = AnimatedContentTransitionScope::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::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::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) } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/RelayListBox.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/RelayListBox.kt index 893c142c6..7f182de16 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/RelayListBox.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/RelayListBox.kt @@ -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) } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/RelayListRow.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/RelayListRow.kt index 7486e908e..4dd5231a9 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/RelayListRow.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/RelayListRow.kt @@ -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, ) } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UserCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UserCompose.kt index 89ef9551f..b6914a028 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UserCompose.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UserCompose.kt @@ -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) } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UserProfilePicture.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UserProfilePicture.kt index 75c763c31..5b20bf188 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UserProfilePicture.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UserProfilePicture.kt @@ -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, ) } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UsernameDisplay.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UsernameDisplay.kt index 7f3db2f47..b2c2084ce 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UsernameDisplay.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UsernameDisplay.kt @@ -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() diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/WatchNoteEvent.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/WatchNoteEvent.kt index 06dc89eb7..33db44e8b 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/WatchNoteEvent.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/WatchNoteEvent.kt @@ -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 { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapCustomDialog.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapCustomDialog.kt index 84e592d7a..cca7e1b87 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapCustomDialog.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapCustomDialog.kt @@ -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), diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapNoteCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapNoteCompose.kt index 1ede76f82..f085925ed 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapNoteCompose.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapNoteCompose.kt @@ -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(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) { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapUserSetCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapUserSetCompose.kt index 3a440f4d1..edb150193 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapUserSetCompose.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapUserSetCompose.kt @@ -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) } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/ZapTheDevsCard.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/ZapTheDevsCard.kt index 41ff8fe33..59e1b8803 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/ZapTheDevsCard.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/ZapTheDevsCard.kt @@ -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 { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/AudioTrack.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/AudioTrack.kt index 7b3215b96..d7875628e 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/AudioTrack.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/AudioTrack.kt @@ -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( diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/CommunityHeader.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/CommunityHeader.kt index 3477c8af2..5b1cf183c 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/CommunityHeader.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/CommunityHeader.kt @@ -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, ) } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Emoji.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Emoji.kt index 6c5a29d04..1f0c00606 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Emoji.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Emoji.kt @@ -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 { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/FileStorage.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/FileStorage.kt index 26eab2f87..70321e2ee 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/FileStorage.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/FileStorage.kt @@ -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(newContent) } - Crossfade(targetState = content) { + CrossfadeIfEnabled(targetState = content, accountViewModel = accountViewModel) { if (it != null) { SensitivityWarning(note = header, accountViewModel = accountViewModel) { ZoomableContentView( diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/LiveActivity.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/LiveActivity.kt index cd5ed6250..eb379995c 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/LiveActivity.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/LiveActivity.kt @@ -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( diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/qrcode/ShowQRDialog.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/qrcode/ShowQRDialog.kt index 0cb474347..3b02eadde 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/qrcode/ShowQRDialog.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/qrcode/ShowQRDialog.kt @@ -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( diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedView.kt index 8d0a140b8..ead1f9cd9 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedView.kt @@ -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 -> { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/ChatroomFeedView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/ChatroomFeedView.kt index a4afc4aad..b0dd65c34 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/ChatroomFeedView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/ChatroomFeedView.kt @@ -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() } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/ChatroomListFeedView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/ChatroomListFeedView.kt index 1d88fe089..56d30493f 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/ChatroomListFeedView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/ChatroomListFeedView.kt @@ -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 -> { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedView.kt index 8dd64290f..200ba6a4f 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedView.kt @@ -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() diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/LnZapFeedView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/LnZapFeedView.kt index 4d531ac3b..0a2c8d2b2 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/LnZapFeedView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/LnZapFeedView.kt @@ -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() } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/SharedPreferencesViewModel.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/SharedPreferencesViewModel.kt index 83f506cfd..129ad0ebb 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/SharedPreferencesViewModel.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/SharedPreferencesViewModel.kt @@ -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(null) @@ -53,7 +53,7 @@ class SettingsState() { var automaticallyShowProfilePictures by mutableStateOf(ConnectivityType.ALWAYS) var dontShowPushNotificationSelector by mutableStateOf(false) var dontAskForNotificationPermissions by mutableStateOf(false) - var featureSet by mutableStateOf(FeatureSetType.COMPLETE) + var featureSet by mutableStateOf(FeatureSetType.SIMPLIFIED) var isOnMobileData: State = mutableStateOf(false) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/StringFeedView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/StringFeedView.kt index c3561e544..931352ac6 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/StringFeedView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/StringFeedView.kt @@ -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() } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/ThreadFeedView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/ThreadFeedView.kt index 6980f89ed..c0e5fe1cf 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/ThreadFeedView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/ThreadFeedView.kt @@ -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) { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/UserFeedView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/UserFeedView.kt index bc1ee84fe..bc9812d17 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/UserFeedView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/UserFeedView.kt @@ -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() } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChannelScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChannelScreen.kt index 1f886feb8..4fc7151aa 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChannelScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChannelScreen.kt @@ -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) } } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChatroomScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChatroomScreen.kt index aedacc4ed..c8df0f652 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChatroomScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChatroomScreen.kt @@ -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) } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/DiscoverScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/DiscoverScreen.kt index 3120345fe..cbe5c710d 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/DiscoverScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/DiscoverScreen.kt @@ -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 -> { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/HiddenUsersScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/HiddenUsersScreen.kt index 1c30be259..929ddbbd5 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/HiddenUsersScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/HiddenUsersScreen.kt @@ -219,6 +219,7 @@ private fun HiddenWordsFeed( RefresheableBox(hiddenWordsViewModel, false) { StringFeedView( hiddenWordsViewModel, + accountViewModel, post = { AddMuteWordTextField(accountViewModel) }, ) { MutedWordHeader(tag = it, account = accountViewModel) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/HomeScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/HomeScreen.kt index d422a0f97..4578a27fd 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/HomeScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/HomeScreen.kt @@ -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() diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/MainScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/MainScreen.kt index 83b3a2c92..3b4b2ae6a 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/MainScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/MainScreen.kt @@ -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::bottomBarTransitionSpec, - label = "BottomBarAnimatedContent", - ) { isVisible -> - if (isVisible) { - AppBottomBar(accountViewModel, navState, navBottomRow) - } - } - }, - topBar = { - AnimatedContent( - targetState = shouldShow.value, - transitionSpec = AnimatedContentTransitionScope::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::bottomBarTransitionSpec, + label = "BottomBarAnimatedContent", + ) { isVisible -> + if (isVisible) { + AppBottomBar(accountViewModel, navState, navBottomRow) + } + } + }, + topBar = { + AnimatedContent( + targetState = shouldShow.value, + transitionSpec = AnimatedContentTransitionScope::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 AnimatedContentTransitionScope.topBarTransitionSpec(): ContentTransform = topBarAnimation diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/NIP90ContentDiscoveryScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/NIP90ContentDiscoveryScreen.kt index 3ec2a88da..e4927c220 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/NIP90ContentDiscoveryScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/NIP90ContentDiscoveryScreen.kt @@ -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 ?: "", diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileScreen.kt index 5168253b5..9e42c3332 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileScreen.kt @@ -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, 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, ) } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/SearchScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/SearchScreen.kt index 03a81f620..27e6bb35f 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/SearchScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/SearchScreen.kt @@ -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>(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 create(modelClass: Class): SearchBarViewModel { - return SearchBarViewModel(account) as SearchBarViewModel - } + class Factory( + val account: Account, + ) : ViewModelProvider.Factory { + override fun create(modelClass: Class): 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) } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/SettingsScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/SettingsScreen.kt index aeb9f87f8..98e798ff9 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/SettingsScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/SettingsScreen.kt @@ -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, diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/VideoScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/VideoScreen.kt index 0c230a69e..4cb678251 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/VideoScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/VideoScreen.kt @@ -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!!, diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 296d2e8cf..b3e0e6f41 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -515,6 +515,7 @@ Complete Simplified + Performance System Light diff --git a/app/src/play/java/com/vitorpamplona/amethyst/ui/components/TranslatableRichTextViewer.kt b/app/src/play/java/com/vitorpamplona/amethyst/ui/components/TranslatableRichTextViewer.kt index 2034c3174..79a4b7ec1 100644 --- a/app/src/play/java/com/vitorpamplona/amethyst/ui/components/TranslatableRichTextViewer.kt +++ b/app/src/play/java/com/vitorpamplona/amethyst/ui/components/TranslatableRichTextViewer.kt @@ -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 =