diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/elements/AddRemoveButtons.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/elements/AddRemoveButtons.kt new file mode 100644 index 000000000..a06317b31 --- /dev/null +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/elements/AddRemoveButtons.kt @@ -0,0 +1,93 @@ +package com.vitorpamplona.amethyst.ui.elements + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Button +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.vitorpamplona.amethyst.R +import com.vitorpamplona.amethyst.ui.theme.ButtonBorder +import com.vitorpamplona.amethyst.ui.theme.ThemeComparison + +@Composable +@Preview +fun AddButtonPreview() { + ThemeComparison( + onDark = { + Row() { + Column { + AddButton(isActive = true) {} + AddButton(isActive = false) {} + } + + Column { + RemoveButton(isActive = true) {} + RemoveButton(isActive = false) {} + } + } + }, + onLight = { + Row() { + Column { + AddButton(isActive = true) {} + AddButton(isActive = false) {} + } + + Column { + RemoveButton(isActive = true) {} + RemoveButton(isActive = false) {} + } + } + } + ) +} + +@Composable +fun AddButton( + text: Int = R.string.add, + isActive: Boolean = true, + modifier: Modifier = Modifier.padding(start = 3.dp), + onClick: () -> Unit +) { + Button( + modifier = modifier, + onClick = { + if (isActive) { + onClick() + } + }, + shape = ButtonBorder, + enabled = isActive, + contentPadding = PaddingValues(vertical = 0.dp, horizontal = 16.dp) + ) { + Text(text = stringResource(text), color = Color.White, textAlign = TextAlign.Center) + } +} + +@Composable +fun RemoveButton( + isActive: Boolean = true, + onClick: () -> Unit +) { + Button( + modifier = Modifier.padding(start = 3.dp), + onClick = { + if (isActive) { + onClick() + } + }, + shape = ButtonBorder, + enabled = isActive, + contentPadding = PaddingValues(vertical = 0.dp, horizontal = 16.dp) + ) { + Text(text = stringResource(R.string.remove), color = Color.White) + } +} diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/elements/DisplayCommunity.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/elements/DisplayCommunity.kt new file mode 100644 index 000000000..0ed7371b1 --- /dev/null +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/elements/DisplayCommunity.kt @@ -0,0 +1,60 @@ +package com.vitorpamplona.amethyst.ui.elements + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.text.ClickableText +import androidx.compose.material3.LocalTextStyle +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.text.AnnotatedString +import com.vitorpamplona.amethyst.model.Note +import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.theme.HalfStartPadding +import com.vitorpamplona.quartz.encoders.ATag +import com.vitorpamplona.quartz.events.CommunityDefinitionEvent + +@Composable +fun DisplayFollowingCommunityInPost( + baseNote: Note, + accountViewModel: AccountViewModel, + nav: (String) -> Unit +) { + Column(HalfStartPadding) { + Row(verticalAlignment = Alignment.CenterVertically) { + DisplayCommunity(baseNote, nav) + } + } +} + +@Composable +private fun DisplayCommunity(note: Note, nav: (String) -> Unit) { + val communityTag = remember(note) { + note.event?.getTagOfAddressableKind(CommunityDefinitionEvent.kind) + } ?: return + + val displayTag = remember(note) { AnnotatedString(getCommunityShortName(communityTag)) } + val route = remember(note) { "Community/${communityTag.toTag()}" } + + ClickableText( + text = displayTag, + onClick = { nav(route) }, + style = LocalTextStyle.current.copy( + color = MaterialTheme.colorScheme.primary.copy( + alpha = 0.52f + ) + ), + maxLines = 1 + ) +} + +private fun getCommunityShortName(communityTag: ATag): String { + val name = if (communityTag.dTag.length > 10) { + communityTag.dTag.take(10) + "..." + } else { + communityTag.dTag.take(10) + } + + return "/n/$name" +} diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/elements/DisplayHashtags.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/elements/DisplayHashtags.kt new file mode 100644 index 000000000..5e6f104f3 --- /dev/null +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/elements/DisplayHashtags.kt @@ -0,0 +1,71 @@ +package com.vitorpamplona.amethyst.ui.elements + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.text.ClickableText +import androidx.compose.material3.LocalTextStyle +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +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.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.text.AnnotatedString +import com.vitorpamplona.amethyst.model.Note +import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch + +@Composable +fun DisplayFollowingHashtagsInPost( + baseNote: Note, + accountViewModel: AccountViewModel, + nav: (String) -> Unit +) { + val noteEvent = remember { baseNote.event } ?: return + + val userFollowState by accountViewModel.userFollows.observeAsState() + var firstTag by remember { mutableStateOf(null) } + + LaunchedEffect(key1 = userFollowState) { + launch(Dispatchers.Default) { + val followingTags = userFollowState?.user?.cachedFollowingTagSet() ?: emptySet() + val newFirstTag = noteEvent.firstIsTaggedHashes(followingTags) + + if (firstTag != newFirstTag) { + launch(Dispatchers.Main) { + firstTag = newFirstTag + } + } + } + } + + firstTag?.let { + Column(verticalArrangement = Arrangement.Center) { + Row(verticalAlignment = Alignment.CenterVertically) { + DisplayTagList(it, nav) + } + } + } +} + +@Composable +private fun DisplayTagList(firstTag: String, nav: (String) -> Unit) { + val displayTag = remember(firstTag) { AnnotatedString(" #$firstTag") } + val route = remember(firstTag) { "Hashtag/$firstTag" } + + ClickableText( + text = displayTag, + onClick = { nav(route) }, + style = LocalTextStyle.current.copy( + color = MaterialTheme.colorScheme.primary.copy( + alpha = 0.52f + ) + ), + maxLines = 1 + ) +} diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/elements/DisplayPoW.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/elements/DisplayPoW.kt new file mode 100644 index 000000000..bef4cf915 --- /dev/null +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/elements/DisplayPoW.kt @@ -0,0 +1,41 @@ +package com.vitorpamplona.amethyst.ui.elements + +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.tooling.preview.Preview +import com.vitorpamplona.amethyst.ui.theme.Font14SP +import com.vitorpamplona.amethyst.ui.theme.ThemeComparison +import com.vitorpamplona.amethyst.ui.theme.lessImportantLink + +@Composable +@Preview +fun DisplayPoWPreview() { + ThemeComparison( + onDark = { + DisplayPoW(pow = 24) + }, + onLight = { + DisplayPoW(pow = 24) + } + ) +} + +@Composable +fun DisplayPoW( + pow: Int +) { + val powStr = remember(pow) { + "PoW-$pow" + } + + Text( + powStr, + color = MaterialTheme.colorScheme.lessImportantLink, + fontSize = Font14SP, + fontWeight = FontWeight.Bold, + maxLines = 1 + ) +} diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/AddBountyAmountDialog.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/elements/DisplayReward.kt similarity index 64% rename from app/src/main/java/com/vitorpamplona/amethyst/ui/note/AddBountyAmountDialog.kt rename to app/src/main/java/com/vitorpamplona/amethyst/ui/elements/DisplayReward.kt index 4b28ff745..5c8a686cf 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/AddBountyAmountDialog.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/elements/DisplayReward.kt @@ -1,5 +1,6 @@ -package com.vitorpamplona.amethyst.ui.note +package com.vitorpamplona.amethyst.ui.elements +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.IntrinsicSize @@ -9,19 +10,26 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width +import androidx.compose.foundation.text.ClickableText import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material3.LocalTextStyle import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.Stable 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 import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.input.KeyboardCapitalization import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.input.TextFieldValue @@ -35,10 +43,102 @@ import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.ui.actions.CloseButton import com.vitorpamplona.amethyst.ui.actions.PostButton +import com.vitorpamplona.amethyst.ui.note.ZapIcon +import com.vitorpamplona.amethyst.ui.note.ZappedIcon +import com.vitorpamplona.amethyst.ui.note.showAmount import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.theme.Size20Modifier import com.vitorpamplona.amethyst.ui.theme.placeholderText import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +import java.math.BigDecimal + +@Stable +data class Reward(val amount: BigDecimal) + +@Composable +fun DisplayReward( + baseReward: Reward, + baseNote: Note, + accountViewModel: AccountViewModel, + nav: (String) -> Unit +) { + var popupExpanded by remember { mutableStateOf(false) } + + Column() { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.clickable { popupExpanded = true } + ) { + ClickableText( + text = AnnotatedString("#bounty"), + onClick = { nav("Hashtag/bounty") }, + style = LocalTextStyle.current.copy( + color = MaterialTheme.colorScheme.primary.copy( + alpha = 0.52f + ) + ) + ) + + RenderPledgeAmount(baseNote, baseReward, accountViewModel) + } + + if (popupExpanded) { + AddBountyAmountDialog(baseNote, accountViewModel) { + popupExpanded = false + } + } + } +} + +@Composable +private fun RenderPledgeAmount( + baseNote: Note, + baseReward: Reward, + accountViewModel: AccountViewModel +) { + val repliesState by baseNote.live().replies.observeAsState() + var reward by remember { + mutableStateOf( + showAmount(baseReward.amount) + ) + } + + var hasPledge by remember { + mutableStateOf( + false + ) + } + + LaunchedEffect(key1 = repliesState) { + launch(Dispatchers.IO) { + repliesState?.note?.pledgedAmountByOthers()?.let { + val newRewardAmount = showAmount(baseReward.amount.add(it)) + if (newRewardAmount != reward) { + reward = newRewardAmount + } + } + val newHasPledge = repliesState?.note?.hasPledgeBy(accountViewModel.userProfile()) == true + if (hasPledge != newHasPledge) { + launch(Dispatchers.Main) { + hasPledge = newHasPledge + } + } + } + } + + if (hasPledge) { + ZappedIcon(modifier = Size20Modifier) + } else { + ZapIcon(modifier = Size20Modifier, MaterialTheme.colorScheme.placeholderText) + } + + Text( + text = reward, + color = MaterialTheme.colorScheme.placeholderText, + maxLines = 1 + ) +} class AddBountyAmountViewModel : ViewModel() { private var account: Account? = null diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/elements/DisplayUncitedHashtags.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/elements/DisplayUncitedHashtags.kt new file mode 100644 index 000000000..5417510b4 --- /dev/null +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/elements/DisplayUncitedHashtags.kt @@ -0,0 +1,41 @@ +package com.vitorpamplona.amethyst.ui.elements + +import androidx.compose.foundation.layout.ExperimentalLayoutApi +import androidx.compose.foundation.layout.FlowRow +import androidx.compose.foundation.text.ClickableText +import androidx.compose.material3.LocalTextStyle +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.text.AnnotatedString +import com.vitorpamplona.amethyst.ui.theme.HalfTopPadding +import com.vitorpamplona.amethyst.ui.theme.lessImportantLink +import kotlinx.collections.immutable.ImmutableList + +@OptIn(ExperimentalLayoutApi::class) +@Composable +fun DisplayUncitedHashtags( + hashtags: ImmutableList, + eventContent: String, + nav: (String) -> Unit +) { + val unusedHashtags = remember(eventContent) { + hashtags.filter { !eventContent.contains(it, true) } + } + + if (unusedHashtags.isNotEmpty()) { + FlowRow( + modifier = HalfTopPadding + ) { + unusedHashtags.forEach { hashtag -> + ClickableText( + text = remember { AnnotatedString("#$hashtag ") }, + onClick = { nav("Hashtag/$hashtag") }, + style = LocalTextStyle.current.copy( + color = MaterialTheme.colorScheme.lessImportantLink + ) + ) + } + } + } +} diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/elements/DisplayZapSplits.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/elements/DisplayZapSplits.kt new file mode 100644 index 000000000..74afd98e0 --- /dev/null +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/elements/DisplayZapSplits.kt @@ -0,0 +1,84 @@ +package com.vitorpamplona.amethyst.ui.elements + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.ExperimentalLayoutApi +import androidx.compose.foundation.layout.FlowRow +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.text.ClickableText +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.ArrowForwardIos +import androidx.compose.material.icons.outlined.Bolt +import androidx.compose.material3.Icon +import androidx.compose.material3.LocalTextStyle +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.unit.dp +import com.vitorpamplona.amethyst.R +import com.vitorpamplona.amethyst.ui.note.UserPicture +import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange +import com.vitorpamplona.amethyst.ui.theme.Size25dp +import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer +import com.vitorpamplona.quartz.events.EventInterface + +@OptIn(ExperimentalLayoutApi::class) +@Composable +fun DisplayZapSplits(noteEvent: EventInterface, accountViewModel: AccountViewModel, nav: (String) -> Unit) { + val list = remember(noteEvent) { noteEvent.zapSplitSetup() } + if (list.isEmpty()) return + + Row(verticalAlignment = Alignment.CenterVertically) { + Box( + Modifier + .height(20.dp) + .width(25.dp) + ) { + Icon( + imageVector = Icons.Outlined.Bolt, + contentDescription = stringResource(id = R.string.zaps), + modifier = Modifier + .size(20.dp) + .align(Alignment.CenterStart), + tint = BitcoinOrange + ) + Icon( + imageVector = Icons.Outlined.ArrowForwardIos, + contentDescription = stringResource(id = R.string.zaps), + modifier = Modifier + .size(13.dp) + .align(Alignment.CenterEnd), + tint = BitcoinOrange + ) + } + + Spacer(modifier = StdHorzSpacer) + + FlowRow { + list.forEach { + if (it.isLnAddress) { + ClickableText( + text = AnnotatedString(it.lnAddressOrPubKeyHex), + onClick = { }, + style = LocalTextStyle.current.copy(color = MaterialTheme.colorScheme.primary) + ) + } else { + UserPicture( + userHex = it.lnAddressOrPubKeyHex, + size = Size25dp, + accountViewModel = accountViewModel, + nav = nav + ) + } + } + } + } +} diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/layouts/RepostLayout.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/layouts/RepostLayout.kt new file mode 100644 index 000000000..b6f524e71 --- /dev/null +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/layouts/RepostLayout.kt @@ -0,0 +1,58 @@ +package com.vitorpamplona.amethyst.ui.layouts + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.vitorpamplona.amethyst.ui.note.RepostedIcon +import com.vitorpamplona.amethyst.ui.theme.Size18Modifier +import com.vitorpamplona.amethyst.ui.theme.Size35Modifier +import com.vitorpamplona.amethyst.ui.theme.Size55Modifier +import com.vitorpamplona.amethyst.ui.theme.placeholderText + +@Composable +@Preview +private fun GenericRepostSectionPreview() { + GenericRepostLayout( + baseAuthorPicture = { + Text("ab") + }, + repostAuthorPicture = { + Text("cd") + } + ) +} + +@Composable +fun GenericRepostLayout( + baseAuthorPicture: @Composable () -> Unit, + repostAuthorPicture: @Composable () -> Unit +) { + Box(modifier = Size55Modifier) { + Box(remember { Size35Modifier.align(Alignment.TopStart) }) { + baseAuthorPicture() + } + + Box( + remember { + Size18Modifier + .align(Alignment.BottomStart) + .padding(1.dp) + } + ) { + RepostedIcon(modifier = Size18Modifier, MaterialTheme.colorScheme.placeholderText) + } + + Box( + remember { Size35Modifier.align(Alignment.BottomEnd) }, + contentAlignment = Alignment.BottomEnd + ) { + repostAuthorPicture() + } + } +} diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/RouteMaker.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/RouteMaker.kt new file mode 100644 index 000000000..bdbc76490 --- /dev/null +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/RouteMaker.kt @@ -0,0 +1,67 @@ +package com.vitorpamplona.amethyst.ui.navigation + +import com.vitorpamplona.amethyst.model.Channel +import com.vitorpamplona.amethyst.model.Note +import com.vitorpamplona.amethyst.model.User +import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.quartz.encoders.HexKey +import com.vitorpamplona.quartz.events.ChannelCreateEvent +import com.vitorpamplona.quartz.events.ChannelMessageEvent +import com.vitorpamplona.quartz.events.ChannelMetadataEvent +import com.vitorpamplona.quartz.events.ChatroomKey +import com.vitorpamplona.quartz.events.ChatroomKeyable +import com.vitorpamplona.quartz.events.CommunityDefinitionEvent +import com.vitorpamplona.quartz.events.LiveActivitiesChatMessageEvent +import com.vitorpamplona.quartz.events.LiveActivitiesEvent +import kotlinx.collections.immutable.persistentSetOf +import java.net.URLEncoder + +fun routeFor(note: Note, loggedIn: User): String? { + val noteEvent = note.event + + if (noteEvent is ChannelMessageEvent || noteEvent is ChannelCreateEvent || noteEvent is ChannelMetadataEvent) { + note.channelHex()?.let { + return "Channel/$it" + } + } else if (noteEvent is LiveActivitiesEvent || noteEvent is LiveActivitiesChatMessageEvent) { + note.channelHex()?.let { + return "Channel/${URLEncoder.encode(it, "utf-8")}" + } + } else if (noteEvent is ChatroomKeyable) { + val room = noteEvent.chatroomKey(loggedIn.pubkeyHex) + loggedIn.createChatroom(room) + return "Room/${room.hashCode()}" + } else if (noteEvent is CommunityDefinitionEvent) { + return "Community/${URLEncoder.encode(note.idHex, "utf-8")}" + } else { + return "Note/${URLEncoder.encode(note.idHex, "utf-8")}" + } + + return null +} + +fun routeToMessage(user: HexKey, draftMessage: String?, accountViewModel: AccountViewModel): String { + val withKey = ChatroomKey(persistentSetOf(user)) + accountViewModel.account.userProfile().createChatroom(withKey) + return if (draftMessage != null) { + "Room/${withKey.hashCode()}?message=$draftMessage" + } else { + "Room/${withKey.hashCode()}" + } +} + +fun routeToMessage(user: User, draftMessage: String?, accountViewModel: AccountViewModel): String { + return routeToMessage(user.pubkeyHex, draftMessage, accountViewModel) +} + +fun routeFor(note: Channel): String { + return "Channel/${note.idHex}" +} + +fun routeFor(user: User): String { + return "User/${user.pubkeyHex}" +} + +fun authorRouteFor(note: Note): String { + return "User/${note.author?.pubkeyHex}" +} diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/BadgeCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/BadgeCompose.kt index c847d3882..142b6933c 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/BadgeCompose.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/BadgeCompose.kt @@ -33,6 +33,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import com.vitorpamplona.amethyst.R +import com.vitorpamplona.amethyst.ui.navigation.routeFor import com.vitorpamplona.amethyst.ui.screen.BadgeCard import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.theme.DividerThickness diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/MessageSetCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/MessageSetCompose.kt index 96d2db36d..8d7181723 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/MessageSetCompose.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/MessageSetCompose.kt @@ -22,6 +22,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.compositeOver import androidx.compose.ui.unit.dp +import com.vitorpamplona.amethyst.ui.navigation.routeFor import com.vitorpamplona.amethyst.ui.screen.MessageSetCard import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.theme.DividerThickness 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 8ac6928ab..cad8f59a1 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 @@ -49,6 +49,8 @@ import com.vitorpamplona.amethyst.ui.components.ImageUrlType import com.vitorpamplona.amethyst.ui.components.InLineIconRenderer import com.vitorpamplona.amethyst.ui.components.RobohashFallbackAsyncImage import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer +import com.vitorpamplona.amethyst.ui.navigation.authorRouteFor +import com.vitorpamplona.amethyst.ui.navigation.routeFor import com.vitorpamplona.amethyst.ui.screen.CombinedZap import com.vitorpamplona.amethyst.ui.screen.MultiSetCard import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel 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 783b93ff5..e1cbe9dac 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 @@ -34,12 +34,12 @@ import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.model.AddressableNote import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.User +import com.vitorpamplona.amethyst.ui.navigation.routeFor import com.vitorpamplona.amethyst.ui.note.LoadAddressableNote import com.vitorpamplona.amethyst.ui.note.LoadStatuses import com.vitorpamplona.amethyst.ui.note.NIP05CheckingIcon import com.vitorpamplona.amethyst.ui.note.NIP05FailedVerification import com.vitorpamplona.amethyst.ui.note.NIP05VerifiedIcon -import com.vitorpamplona.amethyst.ui.note.routeFor import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.theme.Font14SP import com.vitorpamplona.amethyst.ui.theme.NIP05IconSize 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 803f5cfd8..a977c0fd1 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 @@ -14,7 +14,6 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ExperimentalLayoutApi import androidx.compose.foundation.layout.FlowRow -import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize @@ -28,13 +27,7 @@ import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.CutCornerShape import androidx.compose.foundation.text.ClickableText import androidx.compose.foundation.verticalScroll -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.ArrowForwardIos -import androidx.compose.material.icons.outlined.Bolt -import androidx.compose.material3.Button -import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.Divider -import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.LocalTextStyle import androidx.compose.material3.MaterialTheme @@ -44,7 +37,6 @@ import androidx.compose.material3.lightColorScheme import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.MutableState -import androidx.compose.runtime.Stable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState @@ -53,7 +45,6 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment -import androidx.compose.ui.Alignment.Companion.BottomEnd import androidx.compose.ui.Alignment.Companion.CenterVertically import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -71,7 +62,6 @@ import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.core.graphics.drawable.toBitmap @@ -114,6 +104,17 @@ import com.vitorpamplona.amethyst.ui.components.figureOutMimeType import com.vitorpamplona.amethyst.ui.components.imageExtensions import com.vitorpamplona.amethyst.ui.components.measureSpaceWidth import com.vitorpamplona.amethyst.ui.components.removeQueryParamsForExtensionComparison +import com.vitorpamplona.amethyst.ui.elements.AddButton +import com.vitorpamplona.amethyst.ui.elements.DisplayFollowingCommunityInPost +import com.vitorpamplona.amethyst.ui.elements.DisplayFollowingHashtagsInPost +import com.vitorpamplona.amethyst.ui.elements.DisplayPoW +import com.vitorpamplona.amethyst.ui.elements.DisplayReward +import com.vitorpamplona.amethyst.ui.elements.DisplayUncitedHashtags +import com.vitorpamplona.amethyst.ui.elements.DisplayZapSplits +import com.vitorpamplona.amethyst.ui.elements.RemoveButton +import com.vitorpamplona.amethyst.ui.elements.Reward +import com.vitorpamplona.amethyst.ui.layouts.GenericRepostLayout +import com.vitorpamplona.amethyst.ui.navigation.routeFor import com.vitorpamplona.amethyst.ui.screen.equalImmutableLists import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.ChannelHeader @@ -124,8 +125,6 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.LeaveCommunityButton import com.vitorpamplona.amethyst.ui.screen.loggedIn.LiveFlag import com.vitorpamplona.amethyst.ui.screen.loggedIn.NormalTimeAgo import com.vitorpamplona.amethyst.ui.screen.loggedIn.ScheduledFlag -import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange -import com.vitorpamplona.amethyst.ui.theme.ButtonBorder import com.vitorpamplona.amethyst.ui.theme.DividerThickness import com.vitorpamplona.amethyst.ui.theme.DoubleHorzSpacer import com.vitorpamplona.amethyst.ui.theme.DoubleVertSpacer @@ -133,14 +132,11 @@ import com.vitorpamplona.amethyst.ui.theme.Font14SP import com.vitorpamplona.amethyst.ui.theme.HalfDoubleVertSpacer import com.vitorpamplona.amethyst.ui.theme.HalfPadding import com.vitorpamplona.amethyst.ui.theme.HalfStartPadding -import com.vitorpamplona.amethyst.ui.theme.HalfTopPadding import com.vitorpamplona.amethyst.ui.theme.HeaderPictureModifier import com.vitorpamplona.amethyst.ui.theme.QuoteBorder import com.vitorpamplona.amethyst.ui.theme.Size10dp import com.vitorpamplona.amethyst.ui.theme.Size15Modifier import com.vitorpamplona.amethyst.ui.theme.Size16Modifier -import com.vitorpamplona.amethyst.ui.theme.Size18Modifier -import com.vitorpamplona.amethyst.ui.theme.Size20Modifier import com.vitorpamplona.amethyst.ui.theme.Size24Modifier import com.vitorpamplona.amethyst.ui.theme.Size25dp import com.vitorpamplona.amethyst.ui.theme.Size30Modifier @@ -159,7 +155,6 @@ import com.vitorpamplona.amethyst.ui.theme.UserNameRowHeight import com.vitorpamplona.amethyst.ui.theme.WidthAuthorPictureModifier import com.vitorpamplona.amethyst.ui.theme.channelNotePictureModifier import com.vitorpamplona.amethyst.ui.theme.grayText -import com.vitorpamplona.amethyst.ui.theme.lessImportantLink import com.vitorpamplona.amethyst.ui.theme.mediumImportanceLink import com.vitorpamplona.amethyst.ui.theme.newItemBackgroundColor import com.vitorpamplona.amethyst.ui.theme.placeholderText @@ -167,7 +162,6 @@ import com.vitorpamplona.amethyst.ui.theme.replyBackground import com.vitorpamplona.amethyst.ui.theme.replyModifier import com.vitorpamplona.amethyst.ui.theme.subtleBorder import com.vitorpamplona.quartz.encoders.ATag -import com.vitorpamplona.quartz.encoders.HexKey import com.vitorpamplona.quartz.encoders.toNpub import com.vitorpamplona.quartz.events.AppDefinitionEvent import com.vitorpamplona.quartz.events.AudioHeaderEvent @@ -178,8 +172,6 @@ import com.vitorpamplona.quartz.events.BaseTextNoteEvent import com.vitorpamplona.quartz.events.ChannelCreateEvent import com.vitorpamplona.quartz.events.ChannelMessageEvent import com.vitorpamplona.quartz.events.ChannelMetadataEvent -import com.vitorpamplona.quartz.events.ChatroomKey -import com.vitorpamplona.quartz.events.ChatroomKeyable import com.vitorpamplona.quartz.events.ClassifiedsEvent import com.vitorpamplona.quartz.events.CommunityDefinitionEvent import com.vitorpamplona.quartz.events.CommunityPostApprovalEvent @@ -187,7 +179,6 @@ import com.vitorpamplona.quartz.events.EmojiPackEvent import com.vitorpamplona.quartz.events.EmojiPackSelectionEvent import com.vitorpamplona.quartz.events.EmojiUrl import com.vitorpamplona.quartz.events.EmptyTagList -import com.vitorpamplona.quartz.events.EventInterface import com.vitorpamplona.quartz.events.FileHeaderEvent import com.vitorpamplona.quartz.events.FileStorageHeaderEvent import com.vitorpamplona.quartz.events.GenericRepostEvent @@ -212,14 +203,11 @@ import com.vitorpamplona.quartz.events.UserMetadata import com.vitorpamplona.quartz.events.toImmutableListOfLists import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf -import kotlinx.collections.immutable.persistentSetOf import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import java.io.File -import java.math.BigDecimal import java.net.URL -import java.net.URLEncoder import java.util.Locale @OptIn(ExperimentalFoundationApi::class) @@ -1070,60 +1058,6 @@ private fun NoteBody( } } -@OptIn(ExperimentalLayoutApi::class) -@Composable -fun DisplayZapSplits(noteEvent: EventInterface, accountViewModel: AccountViewModel, nav: (String) -> Unit) { - val list = remember(noteEvent) { noteEvent.zapSplitSetup() } - - if (list.isEmpty()) return - - Row(verticalAlignment = CenterVertically) { - Box( - Modifier - .height(20.dp) - .width(25.dp) - ) { - Icon( - imageVector = Icons.Outlined.Bolt, - contentDescription = stringResource(id = R.string.zaps), - modifier = Modifier - .size(20.dp) - .align(Alignment.CenterStart), - tint = BitcoinOrange - ) - Icon( - imageVector = Icons.Outlined.ArrowForwardIos, - contentDescription = stringResource(id = R.string.zaps), - modifier = Modifier - .size(13.dp) - .align(Alignment.CenterEnd), - tint = BitcoinOrange - ) - } - - Spacer(modifier = StdHorzSpacer) - - FlowRow { - list.forEach { - if (it.isLnAddress) { - ClickableText( - text = AnnotatedString(it.lnAddressOrPubKeyHex), - onClick = { }, - style = LocalTextStyle.current.copy(color = MaterialTheme.colorScheme.primary) - ) - } else { - UserPicture( - userHex = it.lnAddressOrPubKeyHex, - size = Size25dp, - accountViewModel = accountViewModel, - nav = nav - ) - } - } - } - } -} - @Composable private fun RenderNoteRow( baseNote: Note, @@ -1265,56 +1199,6 @@ private fun RenderNoteRow( } } -fun routeFor(note: Note, loggedIn: User): String? { - val noteEvent = note.event - - if (noteEvent is ChannelMessageEvent || noteEvent is ChannelCreateEvent || noteEvent is ChannelMetadataEvent) { - note.channelHex()?.let { - return "Channel/$it" - } - } else if (noteEvent is LiveActivitiesEvent || noteEvent is LiveActivitiesChatMessageEvent) { - note.channelHex()?.let { - return "Channel/${URLEncoder.encode(it, "utf-8")}" - } - } else if (noteEvent is ChatroomKeyable) { - val room = noteEvent.chatroomKey(loggedIn.pubkeyHex) - loggedIn.createChatroom(room) - return "Room/${room.hashCode()}" - } else if (noteEvent is CommunityDefinitionEvent) { - return "Community/${URLEncoder.encode(note.idHex, "utf-8")}" - } else { - return "Note/${URLEncoder.encode(note.idHex, "utf-8")}" - } - - return null -} - -fun routeToMessage(user: HexKey, draftMessage: String?, accountViewModel: AccountViewModel): String { - val withKey = ChatroomKey(persistentSetOf(user)) - accountViewModel.account.userProfile().createChatroom(withKey) - return if (draftMessage != null) { - "Room/${withKey.hashCode()}?message=$draftMessage" - } else { - "Room/${withKey.hashCode()}" - } -} - -fun routeToMessage(user: User, draftMessage: String?, accountViewModel: AccountViewModel): String { - return routeToMessage(user.pubkeyHex, draftMessage, accountViewModel) -} - -fun routeFor(note: Channel): String { - return "Channel/${note.idHex}" -} - -fun routeFor(user: User): String { - return "User/${user.pubkeyHex}" -} - -fun authorRouteFor(note: Note): String { - return "User/${note.author?.pubkeyHex}" -} - @Composable fun LoadDecryptedContent( note: Note, @@ -2289,45 +2173,6 @@ private fun EmojiListOptions( } } -@Composable -fun RemoveButton(onClick: () -> Unit) { - Button( - modifier = Modifier.padding(start = 3.dp), - onClick = onClick, - shape = ButtonBorder, - colors = ButtonDefaults.buttonColors( - containerColor = MaterialTheme.colorScheme.primary - ), - contentPadding = PaddingValues(vertical = 0.dp, horizontal = 16.dp) - ) { - Text(text = stringResource(R.string.remove), color = Color.White) - } -} - -@Composable -fun AddButton( - text: Int = R.string.add, - isActive: Boolean = true, - modifier: Modifier = Modifier.padding(start = 3.dp), - onClick: () -> Unit -) { - Button( - modifier = modifier, - onClick = { - if (isActive) { - onClick() - } - }, - shape = ButtonBorder, - colors = ButtonDefaults.buttonColors( - containerColor = if (isActive) MaterialTheme.colorScheme.primary else Color.Gray - ), - contentPadding = PaddingValues(vertical = 0.dp, horizontal = 16.dp) - ) { - Text(text = stringResource(text), color = Color.White, textAlign = TextAlign.Center) - } -} - @OptIn(ExperimentalLayoutApi::class) @Composable fun RenderPinListEvent( @@ -2901,7 +2746,7 @@ private fun RepostNoteAuthorPicture( accountViewModel: AccountViewModel, nav: (String) -> Unit ) { - GenericRepostSection( + GenericRepostLayout( baseAuthorPicture = { NoteAuthorPicture( baseNote = baseNote, @@ -2921,45 +2766,6 @@ private fun RepostNoteAuthorPicture( ) } -@Composable -@Preview -private fun GenericRepostSectionPreview() { - GenericRepostSection( - baseAuthorPicture = { - Text("ab") - }, - repostAuthorPicture = { - Text("cd") - } - ) -} - -@Composable -private fun GenericRepostSection( - baseAuthorPicture: @Composable () -> Unit, - repostAuthorPicture: @Composable () -> Unit -) { - Box(modifier = Size55Modifier) { - Box(remember { Size35Modifier.align(Alignment.TopStart) }) { - baseAuthorPicture() - } - - Box( - remember { - Size18Modifier - .align(Alignment.BottomStart) - .padding(1.dp) - } - ) { - RepostedIcon(modifier = Size18Modifier, MaterialTheme.colorScheme.placeholderText) - } - - Box(remember { Size35Modifier.align(Alignment.BottomEnd) }, contentAlignment = BottomEnd) { - repostAuthorPicture() - } - } -} - @Composable fun DisplayHighlight( highlight: String, @@ -3095,232 +2901,6 @@ private fun LoadAndDisplayUser( } } -@Composable -fun DisplayFollowingCommunityInPost( - baseNote: Note, - accountViewModel: AccountViewModel, - nav: (String) -> Unit -) { - Column(HalfStartPadding) { - Row(verticalAlignment = CenterVertically) { - DisplayCommunity(baseNote, nav) - } - } -} - -@Composable -fun DisplayFollowingHashtagsInPost( - baseNote: Note, - accountViewModel: AccountViewModel, - nav: (String) -> Unit -) { - val noteEvent = remember { baseNote.event } ?: return - - val userFollowState by accountViewModel.userFollows.observeAsState() - var firstTag by remember { mutableStateOf(null) } - - LaunchedEffect(key1 = userFollowState) { - launch(Dispatchers.Default) { - val followingTags = userFollowState?.user?.cachedFollowingTagSet() ?: emptySet() - val newFirstTag = noteEvent.firstIsTaggedHashes(followingTags) - - if (firstTag != newFirstTag) { - launch(Dispatchers.Main) { - firstTag = newFirstTag - } - } - } - } - - firstTag?.let { - Column(verticalArrangement = Arrangement.Center) { - Row(verticalAlignment = CenterVertically) { - DisplayTagList(it, nav) - } - } - } -} - -@Composable -private fun DisplayTagList(firstTag: String, nav: (String) -> Unit) { - val displayTag = remember(firstTag) { AnnotatedString(" #$firstTag") } - val route = remember(firstTag) { "Hashtag/$firstTag" } - - ClickableText( - text = displayTag, - onClick = { nav(route) }, - style = LocalTextStyle.current.copy( - color = MaterialTheme.colorScheme.primary.copy( - alpha = 0.52f - ) - ), - maxLines = 1 - ) -} - -@Composable -private fun DisplayCommunity(note: Note, nav: (String) -> Unit) { - val communityTag = remember(note) { - note.event?.getTagOfAddressableKind(CommunityDefinitionEvent.kind) - } ?: return - - val displayTag = remember(note) { AnnotatedString(getCommunityShortName(communityTag)) } - val route = remember(note) { "Community/${communityTag.toTag()}" } - - ClickableText( - text = displayTag, - onClick = { nav(route) }, - style = LocalTextStyle.current.copy( - color = MaterialTheme.colorScheme.primary.copy( - alpha = 0.52f - ) - ), - maxLines = 1 - ) -} - -private fun getCommunityShortName(communityTag: ATag): String { - val name = if (communityTag.dTag.length > 10) { - communityTag.dTag.take(10) + "..." - } else { - communityTag.dTag.take(10) - } - - return "/n/$name" -} - -@OptIn(ExperimentalLayoutApi::class) -@Composable -fun DisplayUncitedHashtags( - hashtags: ImmutableList, - eventContent: String, - nav: (String) -> Unit -) { - val unusedHashtags = remember(eventContent) { - hashtags.filter { !eventContent.contains(it, true) } - } - - if (unusedHashtags.isNotEmpty()) { - FlowRow( - modifier = HalfTopPadding - ) { - unusedHashtags.forEach { hashtag -> - ClickableText( - text = remember { AnnotatedString("#$hashtag ") }, - onClick = { nav("Hashtag/$hashtag") }, - style = LocalTextStyle.current.copy( - color = MaterialTheme.colorScheme.lessImportantLink - ) - ) - } - } - } -} - -@Composable -fun DisplayPoW( - pow: Int -) { - val powStr = remember(pow) { - "PoW-$pow" - } - - Text( - powStr, - color = MaterialTheme.colorScheme.lessImportantLink, - fontSize = Font14SP, - fontWeight = FontWeight.Bold, - maxLines = 1 - ) -} - -@Stable -data class Reward(val amount: BigDecimal) - -@Composable -fun DisplayReward( - baseReward: Reward, - baseNote: Note, - accountViewModel: AccountViewModel, - nav: (String) -> Unit -) { - var popupExpanded by remember { mutableStateOf(false) } - - Column() { - Row( - verticalAlignment = CenterVertically, - modifier = Modifier.clickable { popupExpanded = true } - ) { - ClickableText( - text = AnnotatedString("#bounty"), - onClick = { nav("Hashtag/bounty") }, - style = LocalTextStyle.current.copy( - color = MaterialTheme.colorScheme.primary.copy( - alpha = 0.52f - ) - ) - ) - - RenderPledgeAmount(baseNote, baseReward, accountViewModel) - } - - if (popupExpanded) { - AddBountyAmountDialog(baseNote, accountViewModel) { - popupExpanded = false - } - } - } -} - -@Composable -private fun RenderPledgeAmount( - baseNote: Note, - baseReward: Reward, - accountViewModel: AccountViewModel -) { - val repliesState by baseNote.live().replies.observeAsState() - var reward by remember { - mutableStateOf( - showAmount(baseReward.amount) - ) - } - - var hasPledge by remember { - mutableStateOf( - false - ) - } - - LaunchedEffect(key1 = repliesState) { - launch(Dispatchers.IO) { - repliesState?.note?.pledgedAmountByOthers()?.let { - val newRewardAmount = showAmount(baseReward.amount.add(it)) - if (newRewardAmount != reward) { - reward = newRewardAmount - } - } - val newHasPledge = repliesState?.note?.hasPledgeBy(accountViewModel.userProfile()) == true - if (hasPledge != newHasPledge) { - launch(Dispatchers.Main) { - hasPledge = newHasPledge - } - } - } - } - - if (hasPledge) { - ZappedIcon(modifier = Size20Modifier) - } else { - ZapIcon(modifier = Size20Modifier, MaterialTheme.colorScheme.placeholderText) - } - - Text( - text = reward, - color = MaterialTheme.colorScheme.placeholderText, - maxLines = 1 - ) -} - @Composable fun BadgeDisplay(baseNote: Note) { val background = MaterialTheme.colorScheme.background diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/PollNote.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/PollNote.kt index 59a967924..829b3d7c4 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/PollNote.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/PollNote.kt @@ -54,6 +54,7 @@ import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.service.ZapPaymentHandler import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer +import com.vitorpamplona.amethyst.ui.navigation.routeToMessage import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.StringToastMsg import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange @@ -70,7 +71,6 @@ import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch -import java.util.* import kotlin.math.roundToInt @Composable 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 19bf2c423..6ceab65c6 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 @@ -13,7 +13,6 @@ import androidx.compose.animation.fadeOut import androidx.compose.animation.slideInVertically import androidx.compose.animation.slideOutVertically import androidx.compose.animation.togetherWith -import androidx.compose.animation.with import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.interaction.MutableInteractionSource @@ -83,6 +82,7 @@ import com.vitorpamplona.amethyst.ui.actions.NewPostView import com.vitorpamplona.amethyst.ui.components.ImageUrlType import com.vitorpamplona.amethyst.ui.components.InLineIconRenderer import com.vitorpamplona.amethyst.ui.components.TextType +import com.vitorpamplona.amethyst.ui.navigation.routeToMessage import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.theme.ButtonBorder import com.vitorpamplona.amethyst.ui.theme.DarkerGreen @@ -714,17 +714,12 @@ fun LikeReaction( indication = rememberRipple(bounded = false, radius = Size24dp), onClick = { likeClick( - baseNote, accountViewModel, onMultipleChoices = { wantsToReact = true }, onWantsToSignReaction = { - if (accountViewModel.account.reactionChoices.size == 1) { - accountViewModel.reactToOrDelete(baseNote) - } else if (accountViewModel.account.reactionChoices.size > 1) { - wantsToReact = true - } + accountViewModel.reactToOrDelete(baseNote) } ) }, @@ -835,7 +830,6 @@ fun ObserveLikeText(baseNote: Note, inner: @Composable (Int) -> Unit) { } private fun likeClick( - baseNote: Note, accountViewModel: AccountViewModel, onMultipleChoices: () -> Unit, onWantsToSignReaction: () -> Unit @@ -851,7 +845,7 @@ private fun likeClick( R.string.login_with_a_private_key_to_like_posts ) } else if (accountViewModel.account.reactionChoices.size == 1) { - accountViewModel.reactToOrDelete(baseNote) + onWantsToSignReaction() } else if (accountViewModel.account.reactionChoices.size > 1) { onMultipleChoices() } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UpdateReactionTypeDialog.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UpdateReactionTypeDialog.kt index 5caae47dc..5f81079f1 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UpdateReactionTypeDialog.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UpdateReactionTypeDialog.kt @@ -63,6 +63,7 @@ import com.vitorpamplona.amethyst.ui.actions.SaveButton import com.vitorpamplona.amethyst.ui.components.ImageUrlType import com.vitorpamplona.amethyst.ui.components.InLineIconRenderer import com.vitorpamplona.amethyst.ui.components.TextType +import com.vitorpamplona.amethyst.ui.navigation.routeFor import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.theme.ButtonBorder import com.vitorpamplona.amethyst.ui.theme.placeholderText 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 20d1125a0..9b703746a 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 @@ -3,7 +3,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.background import androidx.compose.foundation.clickable import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.layout.Arrangement @@ -65,20 +64,22 @@ import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.ui.components.InlineCarrousel import com.vitorpamplona.amethyst.ui.components.ObserveDisplayNip05Status +import com.vitorpamplona.amethyst.ui.elements.DisplayFollowingCommunityInPost +import com.vitorpamplona.amethyst.ui.elements.DisplayFollowingHashtagsInPost +import com.vitorpamplona.amethyst.ui.elements.DisplayPoW +import com.vitorpamplona.amethyst.ui.elements.DisplayReward +import com.vitorpamplona.amethyst.ui.elements.DisplayZapSplits +import com.vitorpamplona.amethyst.ui.elements.Reward +import com.vitorpamplona.amethyst.ui.navigation.routeToMessage import com.vitorpamplona.amethyst.ui.note.AudioHeader import com.vitorpamplona.amethyst.ui.note.AudioTrackHeader import com.vitorpamplona.amethyst.ui.note.BadgeDisplay import com.vitorpamplona.amethyst.ui.note.BlankNote import com.vitorpamplona.amethyst.ui.note.CreateImageHeader -import com.vitorpamplona.amethyst.ui.note.DisplayFollowingCommunityInPost -import com.vitorpamplona.amethyst.ui.note.DisplayFollowingHashtagsInPost import com.vitorpamplona.amethyst.ui.note.DisplayHighlight import com.vitorpamplona.amethyst.ui.note.DisplayLocation import com.vitorpamplona.amethyst.ui.note.DisplayPeopleList -import com.vitorpamplona.amethyst.ui.note.DisplayPoW import com.vitorpamplona.amethyst.ui.note.DisplayRelaySet -import com.vitorpamplona.amethyst.ui.note.DisplayReward -import com.vitorpamplona.amethyst.ui.note.DisplayZapSplits import com.vitorpamplona.amethyst.ui.note.FileHeaderDisplay import com.vitorpamplona.amethyst.ui.note.FileStorageHeaderDisplay import com.vitorpamplona.amethyst.ui.note.HiddenNote @@ -95,8 +96,6 @@ import com.vitorpamplona.amethyst.ui.note.RenderPoll import com.vitorpamplona.amethyst.ui.note.RenderPostApproval import com.vitorpamplona.amethyst.ui.note.RenderRepost import com.vitorpamplona.amethyst.ui.note.RenderTextEvent -import com.vitorpamplona.amethyst.ui.note.Reward -import com.vitorpamplona.amethyst.ui.note.routeToMessage import com.vitorpamplona.amethyst.ui.note.showAmount import com.vitorpamplona.amethyst.ui.note.timeAgo import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel 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 aa824d9d2..b9b2fcba2 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 @@ -98,9 +98,10 @@ import com.vitorpamplona.amethyst.ui.components.SensitivityWarning import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer import com.vitorpamplona.amethyst.ui.components.ZoomableContentView import com.vitorpamplona.amethyst.ui.components.ZoomableUrlVideo +import com.vitorpamplona.amethyst.ui.elements.DisplayUncitedHashtags +import com.vitorpamplona.amethyst.ui.navigation.routeFor import com.vitorpamplona.amethyst.ui.note.ChatroomMessageCompose import com.vitorpamplona.amethyst.ui.note.ClickableUserPicture -import com.vitorpamplona.amethyst.ui.note.DisplayUncitedHashtags import com.vitorpamplona.amethyst.ui.note.LikeReaction import com.vitorpamplona.amethyst.ui.note.LoadChannel import com.vitorpamplona.amethyst.ui.note.MoreOptionsButton @@ -109,7 +110,6 @@ import com.vitorpamplona.amethyst.ui.note.NoteUsernameDisplay import com.vitorpamplona.amethyst.ui.note.UserPicture import com.vitorpamplona.amethyst.ui.note.UsernameDisplay import com.vitorpamplona.amethyst.ui.note.ZapReaction -import com.vitorpamplona.amethyst.ui.note.routeFor import com.vitorpamplona.amethyst.ui.note.timeAgo import com.vitorpamplona.amethyst.ui.note.timeAgoShort import com.vitorpamplona.amethyst.ui.screen.NostrChannelFeedViewModel 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 c40e26c88..40627ae22 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 @@ -47,7 +47,7 @@ import androidx.lifecycle.distinctUntilChanged import androidx.lifecycle.map import androidx.lifecycle.viewmodel.compose.viewModel import com.vitorpamplona.amethyst.R -import com.vitorpamplona.amethyst.ui.note.AddButton +import com.vitorpamplona.amethyst.ui.elements.AddButton import com.vitorpamplona.amethyst.ui.screen.NostrHiddenAccountsFeedViewModel import com.vitorpamplona.amethyst.ui.screen.NostrHiddenWordsFeedViewModel import com.vitorpamplona.amethyst.ui.screen.NostrSpammerAccountsFeedViewModel 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 fdcf769a8..069d1d117 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 @@ -109,12 +109,12 @@ import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer import com.vitorpamplona.amethyst.ui.components.ZoomableImageDialog import com.vitorpamplona.amethyst.ui.components.figureOutMimeType import com.vitorpamplona.amethyst.ui.dal.UserProfileReportsFeedFilter +import com.vitorpamplona.amethyst.ui.navigation.routeToMessage import com.vitorpamplona.amethyst.ui.note.ClickableUserPicture import com.vitorpamplona.amethyst.ui.note.ErrorMessageDialog import com.vitorpamplona.amethyst.ui.note.LightningAddressIcon import com.vitorpamplona.amethyst.ui.note.LoadAddressableNote import com.vitorpamplona.amethyst.ui.note.payViaIntent -import com.vitorpamplona.amethyst.ui.note.routeToMessage import com.vitorpamplona.amethyst.ui.qrcode.ShowQRDialog import com.vitorpamplona.amethyst.ui.screen.FeedState import com.vitorpamplona.amethyst.ui.screen.LnZapFeedView 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 31388915a..02b754809 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 @@ -46,6 +46,7 @@ import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.service.NostrVideoDataSource import com.vitorpamplona.amethyst.ui.actions.NewPostView import com.vitorpamplona.amethyst.ui.components.ObserveDisplayNip05Status +import com.vitorpamplona.amethyst.ui.navigation.routeFor import com.vitorpamplona.amethyst.ui.note.BoostReaction import com.vitorpamplona.amethyst.ui.note.FileHeaderDisplay import com.vitorpamplona.amethyst.ui.note.FileStorageHeaderDisplay @@ -59,7 +60,6 @@ import com.vitorpamplona.amethyst.ui.note.ReplyReaction import com.vitorpamplona.amethyst.ui.note.ViewCountReaction import com.vitorpamplona.amethyst.ui.note.WatchForReports import com.vitorpamplona.amethyst.ui.note.ZapReaction -import com.vitorpamplona.amethyst.ui.note.routeFor import com.vitorpamplona.amethyst.ui.screen.FeedEmpty import com.vitorpamplona.amethyst.ui.screen.FeedError import com.vitorpamplona.amethyst.ui.screen.FeedState diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/theme/Theme.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/theme/Theme.kt index 76692e395..3cfb34d58 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/theme/Theme.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/theme/Theme.kt @@ -4,12 +4,14 @@ import android.app.Activity import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.CircleShape import androidx.compose.material3.ColorScheme import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface import androidx.compose.material3.darkColorScheme import androidx.compose.material3.lightColorScheme import androidx.compose.runtime.Composable @@ -27,6 +29,7 @@ import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.core.view.WindowCompat +import androidx.lifecycle.viewmodel.compose.viewModel import com.halilibo.richtext.ui.RichTextStyle import com.halilibo.richtext.ui.resolveDefaults import com.patrykandpatrick.vico.compose.style.ChartStyle @@ -367,3 +370,27 @@ fun AmethystTheme(sharedPrefsViewModel: SharedPreferencesViewModel, content: @Co } } } + +@Composable +fun ThemeComparison( + onDark: @Composable () -> Unit, + onLight: @Composable () -> Unit +) { + Column() { + val darkTheme: SharedPreferencesViewModel = viewModel() + darkTheme.updateTheme(ThemeType.DARK) + AmethystTheme(darkTheme) { + Surface(color = MaterialTheme.colorScheme.background) { + onDark() + } + } + + val lightTheme: SharedPreferencesViewModel = viewModel() + lightTheme.updateTheme(ThemeType.LIGHT) + AmethystTheme(lightTheme) { + Surface(color = MaterialTheme.colorScheme.background) { + onLight() + } + } + } +}