- Adds Immutable Lists to avoid recompositions

- Moves NavController to a lambda to avoid recompositions
- Moves AccountViewModel builder to a ViewModel Factory to avoid recompositions
- Reduces notifications invalidation requests when starting the screen.
This commit is contained in:
Vitor Pamplona
2023-05-24 12:34:09 -04:00
parent 4841d4004e
commit f1ecaf1ee5
66 changed files with 847 additions and 768 deletions
+3 -1
View File
@@ -82,7 +82,6 @@ android {
lint { lint {
disable 'MissingTranslation' disable 'MissingTranslation'
} }
} }
dependencies { dependencies {
@@ -183,6 +182,9 @@ dependencies {
implementation "com.patrykandpatrick.vico:views:${vico_version}" implementation "com.patrykandpatrick.vico:views:${vico_version}"
implementation "com.patrykandpatrick.vico:compose-m2:${vico_version}" implementation "com.patrykandpatrick.vico:compose-m2:${vico_version}"
// immutable collections to avoid recomposition
implementation("org.jetbrains.kotlinx:kotlinx-collections-immutable:0.3.5")
// Automatic memory leak detection // Automatic memory leak detection
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.11' debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.11'
@@ -3,7 +3,6 @@ package com.vitorpamplona.amethyst.ui.components
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.navigation.NavController
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
@Composable @Composable
@@ -14,7 +13,7 @@ fun TranslatableRichTextViewer(
tags: List<List<String>>?, tags: List<List<String>>?,
backgroundColor: Color, backgroundColor: Color,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
navController: NavController nav: (String) -> Unit
) = ExpandableRichTextViewer( ) = ExpandableRichTextViewer(
content, content,
canPreview, canPreview,
@@ -22,5 +21,5 @@ fun TranslatableRichTextViewer(
tags, tags,
backgroundColor, backgroundColor,
accountViewModel, accountViewModel,
navController nav
) )
@@ -8,6 +8,7 @@ import kotlinx.coroutines.Job
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
object NotificationCache { object NotificationCache {
// TODO: This must be account-based
val lastReadByRoute = mutableMapOf<String, Long>() val lastReadByRoute = mutableMapOf<String, Long>()
fun markAsRead(route: String, timestampInSecs: Long) { fun markAsRead(route: String, timestampInSecs: Long) {
@@ -2,6 +2,8 @@ package com.vitorpamplona.amethyst.model
import android.content.res.Resources import android.content.res.Resources
import android.util.Log import android.util.Log
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.Stable
import androidx.core.os.ConfigurationCompat import androidx.core.os.ConfigurationCompat
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import com.vitorpamplona.amethyst.service.FileHeader import com.vitorpamplona.amethyst.service.FileHeader
@@ -43,6 +45,7 @@ val GLOBAL_FOLLOWS = " Global "
val KIND3_FOLLOWS = " All Follows " val KIND3_FOLLOWS = " All Follows "
@OptIn(DelicateCoroutinesApi::class) @OptIn(DelicateCoroutinesApi::class)
@Stable
class Account( class Account(
val loggedIn: Persona, val loggedIn: Persona,
var followingChannels: Set<String> = DefaultChannels, var followingChannels: Set<String> = DefaultChannels,
@@ -1166,4 +1169,5 @@ class AccountLiveData(private val account: Account) : LiveData<AccountState>(Acc
} }
} }
@Immutable
class AccountState(val account: Account) class AccountState(val account: Account)
@@ -1,5 +1,6 @@
package com.vitorpamplona.amethyst.model package com.vitorpamplona.amethyst.model
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.Stable import androidx.compose.runtime.Stable
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import com.vitorpamplona.amethyst.service.NostrSingleEventDataSource import com.vitorpamplona.amethyst.service.NostrSingleEventDataSource
@@ -442,4 +443,5 @@ class NoteLiveData(val note: Note) : LiveData<NoteState>(NoteState(note)) {
} }
} }
@Immutable
class NoteState(val note: Note) class NoteState(val note: Note)
@@ -1,5 +1,6 @@
package com.vitorpamplona.amethyst.model package com.vitorpamplona.amethyst.model
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.Stable import androidx.compose.runtime.Stable
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import com.vitorpamplona.amethyst.service.NostrSingleUserDataSource import com.vitorpamplona.amethyst.service.NostrSingleUserDataSource
@@ -420,4 +421,5 @@ class UserLiveData(val user: User) : LiveData<UserState>(UserState(user)) {
} }
} }
@Immutable
class UserState(val user: User) class UserState(val user: User)
@@ -30,6 +30,7 @@ class Relay(
) { ) {
val seconds = if (proxy != null) 20L else 10L val seconds = if (proxy != null) 20L else 10L
val duration = Duration.ofSeconds(seconds) val duration = Duration.ofSeconds(seconds)
private val httpClient = OkHttpClient.Builder() private val httpClient = OkHttpClient.Builder()
.proxy(proxy) .proxy(proxy)
.readTimeout(duration) .readTimeout(duration)
@@ -51,7 +51,6 @@ import androidx.compose.ui.window.DialogProperties
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.LocalCache
@@ -73,7 +72,7 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@Composable @Composable
fun JoinUserOrChannelView(onClose: () -> Unit, account: Account, navController: NavController) { fun JoinUserOrChannelView(onClose: () -> Unit, account: Account, nav: (String) -> Unit) {
val searchBarViewModel: SearchBarViewModel = viewModel() val searchBarViewModel: SearchBarViewModel = viewModel()
searchBarViewModel.account = account searchBarViewModel.account = account
@@ -116,7 +115,7 @@ fun JoinUserOrChannelView(onClose: () -> Unit, account: Account, navController:
Spacer(modifier = Modifier.height(15.dp)) Spacer(modifier = Modifier.height(15.dp))
RenderSeach(searchBarViewModel, account, navController) RenderSeach(searchBarViewModel, account, nav)
} }
} }
} }
@@ -127,7 +126,7 @@ fun JoinUserOrChannelView(onClose: () -> Unit, account: Account, navController:
private fun RenderSeach( private fun RenderSeach(
searchBarViewModel: SearchBarViewModel, searchBarViewModel: SearchBarViewModel,
account: Account, account: Account,
navController: NavController nav: (String) -> Unit
) { ) {
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val listState = rememberLazyListState() val listState = rememberLazyListState()
@@ -275,7 +274,7 @@ private fun RenderSeach(
UserComposeForChat( UserComposeForChat(
item, item,
account = account, account = account,
navController = navController nav = nav
) )
} }
@@ -295,7 +294,7 @@ private fun RenderSeach(
channelLastTime = null, channelLastTime = null,
channelLastContent = item.info.about, channelLastContent = item.info.about,
false, false,
onClick = { navController.navigate("Channel/${item.idHex}") } onClick = { nav("Channel/${item.idHex}") }
) )
} }
} }
@@ -307,12 +306,12 @@ private fun RenderSeach(
fun UserComposeForChat( fun UserComposeForChat(
baseUser: User, baseUser: User,
account: Account, account: Account,
navController: NavController nav: (String) -> Unit
) { ) {
Column( Column(
modifier = modifier =
Modifier.clickable( Modifier.clickable(
onClick = { navController.navigate("Room/${baseUser.pubkeyHex}") } onClick = { nav("Room/${baseUser.pubkeyHex}") }
) )
) { ) {
Row( Row(
@@ -324,7 +323,7 @@ fun UserComposeForChat(
), ),
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
UserPicture(baseUser, navController, account.userProfile(), 55.dp) UserPicture(baseUser, nav, account.userProfile(), 55.dp)
Column(modifier = Modifier.padding(start = 10.dp).weight(1f)) { Column(modifier = Modifier.padding(start = 10.dp).weight(1f)) {
Row(verticalAlignment = Alignment.CenterVertically) { Row(verticalAlignment = Alignment.CenterVertically) {
@@ -28,7 +28,6 @@ import androidx.compose.ui.text.input.KeyboardCapitalization
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties import androidx.compose.ui.window.DialogProperties
import androidx.navigation.NavController
import coil.compose.AsyncImage import coil.compose.AsyncImage
import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.Account
@@ -39,7 +38,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@Composable @Composable
fun NewMediaView(uri: Uri, onClose: () -> Unit, postViewModel: NewMediaModel, accountViewModel: AccountViewModel, navController: NavController) { fun NewMediaView(uri: Uri, onClose: () -> Unit, postViewModel: NewMediaModel, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
val account = accountViewModel.accountLiveData.value?.account ?: return val account = accountViewModel.accountLiveData.value?.account ?: return
val resolver = LocalContext.current.contentResolver val resolver = LocalContext.current.contentResolver
val context = LocalContext.current val context = LocalContext.current
@@ -57,7 +57,6 @@ import androidx.compose.ui.unit.sp
import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties import androidx.compose.ui.window.DialogProperties
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import coil.compose.AsyncImage import coil.compose.AsyncImage
import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.Account
@@ -75,7 +74,7 @@ import kotlinx.coroutines.launch
@OptIn(ExperimentalComposeUiApi::class) @OptIn(ExperimentalComposeUiApi::class)
@Composable @Composable
fun NewPostView(onClose: () -> Unit, baseReplyTo: Note? = null, quote: Note? = null, account: Account, accountViewModel: AccountViewModel, navController: NavController) { fun NewPostView(onClose: () -> Unit, baseReplyTo: Note? = null, quote: Note? = null, account: Account, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
val postViewModel: NewPostViewModel = viewModel() val postViewModel: NewPostViewModel = viewModel()
val context = LocalContext.current val context = LocalContext.current
@@ -292,7 +291,7 @@ fun NewPostView(onClose: () -> Unit, baseReplyTo: Note? = null, quote: Note? = n
true, true,
MaterialTheme.colors.background, MaterialTheme.colors.background,
accountViewModel, accountViewModel,
navController nav
) )
} else if (noProtocolUrlValidator.matcher(myUrlPreview).matches()) { } else if (noProtocolUrlValidator.matcher(myUrlPreview).matches()) {
UrlPreview("https://$myUrlPreview", myUrlPreview) UrlPreview("https://$myUrlPreview", myUrlPreview)
@@ -15,14 +15,13 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.ui.actions.JoinUserOrChannelView import com.vitorpamplona.amethyst.ui.actions.JoinUserOrChannelView
import com.vitorpamplona.amethyst.ui.actions.NewChannelView import com.vitorpamplona.amethyst.ui.actions.NewChannelView
@Composable @Composable
fun ChannelFabColumn(account: Account, navController: NavController) { fun ChannelFabColumn(account: Account, nav: (String) -> Unit) {
var isOpen by remember { var isOpen by remember {
mutableStateOf(false) mutableStateOf(false)
} }
@@ -40,7 +39,7 @@ fun ChannelFabColumn(account: Account, navController: NavController) {
} }
if (wantsToJoinChannelOrUser) { if (wantsToJoinChannelOrUser) {
JoinUserOrChannelView({ wantsToJoinChannelOrUser = false }, account = account, navController = navController) JoinUserOrChannelView({ wantsToJoinChannelOrUser = false }, account = account, nav = nav)
} }
Column() { Column() {
@@ -16,20 +16,19 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.ui.actions.NewPostView import com.vitorpamplona.amethyst.ui.actions.NewPostView
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
@Composable @Composable
fun NewNoteButton(account: Account, accountViewModel: AccountViewModel, navController: NavController) { fun NewNoteButton(account: Account, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
var wantsToPost by remember { var wantsToPost by remember {
mutableStateOf(false) mutableStateOf(false)
} }
if (wantsToPost) { if (wantsToPost) {
NewPostView({ wantsToPost = false }, account = account, accountViewModel = accountViewModel, navController = navController) NewPostView({ wantsToPost = false }, account = account, accountViewModel = accountViewModel, nav = nav)
} }
OutlinedButton( OutlinedButton(
@@ -22,9 +22,11 @@ class BundledUpdate(
private var onlyOneInBlock = AtomicBoolean() private var onlyOneInBlock = AtomicBoolean()
private var invalidatesAgain = false private var invalidatesAgain = false
fun invalidate() { fun invalidate(ignoreIfDoing: Boolean = false) {
if (onlyOneInBlock.getAndSet(true)) { if (onlyOneInBlock.getAndSet(true)) {
if (!ignoreIfDoing) {
invalidatesAgain = true invalidatesAgain = true
}
return return
} }
@@ -5,18 +5,17 @@ import androidx.compose.material.LocalTextStyle
import androidx.compose.material.MaterialTheme import androidx.compose.material.MaterialTheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.AnnotatedString
import androidx.navigation.NavController
import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.ui.note.toShortenHex import com.vitorpamplona.amethyst.ui.note.toShortenHex
@Composable @Composable
fun ClickableNoteTag( fun ClickableNoteTag(
baseNote: Note, baseNote: Note,
navController: NavController nav: (String) -> Unit
) { ) {
ClickableText( ClickableText(
text = AnnotatedString("@${baseNote.idNote().toShortenHex()}"), text = AnnotatedString("@${baseNote.idNote().toShortenHex()}"),
onClick = { navController.navigate("Note/${baseNote.idHex}") }, onClick = { nav("Note/${baseNote.idHex}") },
style = LocalTextStyle.current.copy(color = MaterialTheme.colors.primary) style = LocalTextStyle.current.copy(color = MaterialTheme.colors.primary)
) )
} }
@@ -37,7 +37,6 @@ import androidx.compose.ui.text.withStyle
import androidx.compose.ui.unit.TextUnit import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.navigation.NavController
import coil.compose.AsyncImage import coil.compose.AsyncImage
import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.Note
@@ -52,16 +51,16 @@ import kotlinx.coroutines.withContext
@Composable @Composable
fun ClickableRoute( fun ClickableRoute(
nip19: Nip19.Return, nip19: Nip19.Return,
navController: NavController nav: (String) -> Unit
) { ) {
if (nip19.type == Nip19.Type.USER) { if (nip19.type == Nip19.Type.USER) {
DisplayUser(nip19, navController) DisplayUser(nip19, nav)
} else if (nip19.type == Nip19.Type.ADDRESS) { } else if (nip19.type == Nip19.Type.ADDRESS) {
DisplayAddress(nip19, navController) DisplayAddress(nip19, nav)
} else if (nip19.type == Nip19.Type.NOTE) { } else if (nip19.type == Nip19.Type.NOTE) {
DisplayNote(nip19, navController) DisplayNote(nip19, nav)
} else if (nip19.type == Nip19.Type.EVENT) { } else if (nip19.type == Nip19.Type.EVENT) {
DisplayEvent(nip19, navController) DisplayEvent(nip19, nav)
} else { } else {
Text( Text(
"@${nip19.hex}${nip19.additionalChars} " "@${nip19.hex}${nip19.additionalChars} "
@@ -72,7 +71,7 @@ fun ClickableRoute(
@Composable @Composable
private fun DisplayEvent( private fun DisplayEvent(
nip19: Nip19.Return, nip19: Nip19.Return,
navController: NavController nav: (String) -> Unit
) { ) {
var noteBase by remember { mutableStateOf<Note?>(null) } var noteBase by remember { mutableStateOf<Note?>(null) }
@@ -92,28 +91,28 @@ private fun DisplayEvent(
clickablePart = "@${note.idDisplayNote()}", clickablePart = "@${note.idDisplayNote()}",
suffix = "${nip19.additionalChars} ", suffix = "${nip19.additionalChars} ",
route = "Channel/${nip19.hex}", route = "Channel/${nip19.hex}",
navController = navController nav = nav
) )
} else if (note.event is PrivateDmEvent) { } else if (note.event is PrivateDmEvent) {
CreateClickableText( CreateClickableText(
clickablePart = "@${note.idDisplayNote()}", clickablePart = "@${note.idDisplayNote()}",
suffix = "${nip19.additionalChars} ", suffix = "${nip19.additionalChars} ",
route = "Room/${note.author?.pubkeyHex}", route = "Room/${note.author?.pubkeyHex}",
navController = navController nav = nav
) )
} else if (channel != null) { } else if (channel != null) {
CreateClickableText( CreateClickableText(
clickablePart = channel.toBestDisplayName(), clickablePart = channel.toBestDisplayName(),
suffix = "${nip19.additionalChars} ", suffix = "${nip19.additionalChars} ",
route = "Channel/${channel.idHex}", route = "Channel/${channel.idHex}",
navController = navController nav = nav
) )
} else { } else {
CreateClickableText( CreateClickableText(
clickablePart = "@${note.idDisplayNote()}", clickablePart = "@${note.idDisplayNote()}",
suffix = "${nip19.additionalChars} ", suffix = "${nip19.additionalChars} ",
route = "Event/${nip19.hex}", route = "Event/${nip19.hex}",
navController = navController nav = nav
) )
} }
} }
@@ -128,7 +127,7 @@ private fun DisplayEvent(
@Composable @Composable
private fun DisplayNote( private fun DisplayNote(
nip19: Nip19.Return, nip19: Nip19.Return,
navController: NavController nav: (String) -> Unit
) { ) {
var noteBase by remember { mutableStateOf<Note?>(null) } var noteBase by remember { mutableStateOf<Note?>(null) }
@@ -148,28 +147,28 @@ private fun DisplayNote(
clickablePart = "@${note.idDisplayNote()}", clickablePart = "@${note.idDisplayNote()}",
suffix = "${nip19.additionalChars} ", suffix = "${nip19.additionalChars} ",
route = "Channel/${nip19.hex}", route = "Channel/${nip19.hex}",
navController = navController nav = nav
) )
} else if (note.event is PrivateDmEvent) { } else if (note.event is PrivateDmEvent) {
CreateClickableText( CreateClickableText(
clickablePart = "@${note.idDisplayNote()}", clickablePart = "@${note.idDisplayNote()}",
suffix = "${nip19.additionalChars} ", suffix = "${nip19.additionalChars} ",
route = "Room/${note.author?.pubkeyHex}", route = "Room/${note.author?.pubkeyHex}",
navController = navController nav = nav
) )
} else if (channel != null) { } else if (channel != null) {
CreateClickableText( CreateClickableText(
clickablePart = channel.toBestDisplayName(), clickablePart = channel.toBestDisplayName(),
suffix = "${nip19.additionalChars} ", suffix = "${nip19.additionalChars} ",
route = "Channel/${note.channel()?.idHex}", route = "Channel/${note.channel()?.idHex}",
navController = navController nav = nav
) )
} else { } else {
CreateClickableText( CreateClickableText(
clickablePart = "@${note.idDisplayNote()}", clickablePart = "@${note.idDisplayNote()}",
suffix = "${nip19.additionalChars} ", suffix = "${nip19.additionalChars} ",
route = "Note/${nip19.hex}", route = "Note/${nip19.hex}",
navController = navController nav = nav
) )
} }
} }
@@ -184,7 +183,7 @@ private fun DisplayNote(
@Composable @Composable
private fun DisplayAddress( private fun DisplayAddress(
nip19: Nip19.Return, nip19: Nip19.Return,
navController: NavController nav: (String) -> Unit
) { ) {
var noteBase by remember { mutableStateOf<Note?>(null) } var noteBase by remember { mutableStateOf<Note?>(null) }
@@ -202,7 +201,7 @@ private fun DisplayAddress(
clickablePart = "@${note.idDisplayNote()}", clickablePart = "@${note.idDisplayNote()}",
suffix = "${nip19.additionalChars} ", suffix = "${nip19.additionalChars} ",
route = "Note/${nip19.hex}", route = "Note/${nip19.hex}",
navController = navController nav = nav
) )
} }
@@ -216,7 +215,7 @@ private fun DisplayAddress(
@Composable @Composable
private fun DisplayUser( private fun DisplayUser(
nip19: Nip19.Return, nip19: Nip19.Return,
navController: NavController nav: (String) -> Unit
) { ) {
var userBase by remember { mutableStateOf<User?>(null) } var userBase by remember { mutableStateOf<User?>(null) }
@@ -238,7 +237,7 @@ private fun DisplayUser(
suffix = "${nip19.additionalChars} ", suffix = "${nip19.additionalChars} ",
tags = userTags, tags = userTags,
route = route, route = route,
navController = navController nav = nav
) )
} }
} }
@@ -257,7 +256,7 @@ fun CreateClickableText(
overrideColor: Color? = null, overrideColor: Color? = null,
fontWeight: FontWeight = FontWeight.Normal, fontWeight: FontWeight = FontWeight.Normal,
route: String, route: String,
navController: NavController nav: (String) -> Unit
) { ) {
ClickableText( ClickableText(
text = buildAnnotatedString { text = buildAnnotatedString {
@@ -272,7 +271,7 @@ fun CreateClickableText(
append(suffix) append(suffix)
} }
}, },
onClick = { navController.navigate(route) } onClick = { nav(route) }
) )
} }
@@ -378,21 +377,21 @@ fun CreateClickableTextWithEmoji(
overrideColor: Color? = null, overrideColor: Color? = null,
fontWeight: FontWeight = FontWeight.Normal, fontWeight: FontWeight = FontWeight.Normal,
route: String, route: String,
navController: NavController nav: (String) -> Unit
) { ) {
val emojis = remember(tags) { val emojis = remember(tags) {
tags?.filter { it.size > 2 && it[0] == "emoji" }?.associate { ":${it[1]}:" to it[2] } ?: emptyMap() tags?.filter { it.size > 2 && it[0] == "emoji" }?.associate { ":${it[1]}:" to it[2] } ?: emptyMap()
} }
if (emojis.isEmpty()) { if (emojis.isEmpty()) {
CreateClickableText(clickablePart, suffix, overrideColor, fontWeight, route, navController) CreateClickableText(clickablePart, suffix, overrideColor, fontWeight, route, nav)
} else { } else {
val myList = remember { val myList = remember {
assembleAnnotatedList(clickablePart, emojis) assembleAnnotatedList(clickablePart, emojis)
} }
ClickableInLineIconRenderer(myList, LocalTextStyle.current.copy(color = overrideColor ?: MaterialTheme.colors.primary, fontWeight = fontWeight).toSpanStyle()) { ClickableInLineIconRenderer(myList, LocalTextStyle.current.copy(color = overrideColor ?: MaterialTheme.colors.primary, fontWeight = fontWeight).toSpanStyle()) {
navController.navigate(route) nav(route)
} }
val myList2 = remember { val myList2 = remember {
@@ -7,18 +7,17 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.AnnotatedString
import androidx.navigation.NavController
import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.model.User
@Composable @Composable
fun ClickableUserTag( fun ClickableUserTag(
user: User, user: User,
navController: NavController nav: (String) -> Unit
) { ) {
val innerUserState by user.live().metadata.observeAsState() val innerUserState by user.live().metadata.observeAsState()
ClickableText( ClickableText(
text = AnnotatedString("@${innerUserState?.user?.toBestDisplayName()}"), text = AnnotatedString("@${innerUserState?.user?.toBestDisplayName()}"),
onClick = { navController.navigate("User/${innerUserState?.user?.pubkeyHex}") }, onClick = { nav("User/${innerUserState?.user?.pubkeyHex}") },
style = LocalTextStyle.current.copy(color = MaterialTheme.colors.primary) style = LocalTextStyle.current.copy(color = MaterialTheme.colors.primary)
) )
} }
@@ -25,7 +25,6 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.compositeOver import androidx.compose.ui.graphics.compositeOver
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
@@ -39,7 +38,7 @@ fun ExpandableRichTextViewer(
tags: List<List<String>>?, tags: List<List<String>>?,
backgroundColor: Color, backgroundColor: Color,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
navController: NavController nav: (String) -> Unit
) { ) {
var showFullText by remember { mutableStateOf(false) } var showFullText by remember { mutableStateOf(false) }
@@ -69,7 +68,7 @@ fun ExpandableRichTextViewer(
tags, tags,
backgroundColor, backgroundColor,
accountViewModel, accountViewModel,
navController nav
) )
if (content.length > whereToCut && !showFullText) { if (content.length > whereToCut && !showFullText) {
@@ -26,7 +26,6 @@ import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.style.TextDirection import androidx.compose.ui.text.style.TextDirection
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.navigation.NavController
import com.google.accompanist.flowlayout.FlowRow import com.google.accompanist.flowlayout.FlowRow
import com.halilibo.richtext.markdown.Markdown import com.halilibo.richtext.markdown.Markdown
import com.halilibo.richtext.markdown.MarkdownParseOptions import com.halilibo.richtext.markdown.MarkdownParseOptions
@@ -94,19 +93,20 @@ fun RichTextViewer(
tags: List<List<String>>?, tags: List<List<String>>?,
backgroundColor: Color, backgroundColor: Color,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
navController: NavController nav: (String) -> Unit
) { ) {
val isMarkdown = remember { isMarkdown(content) } val isMarkdown = remember { isMarkdown(content) }
Column(modifier = modifier) { Column(modifier = modifier) {
if (isMarkdown) { if (isMarkdown) {
RenderContentAsMarkdown(content, backgroundColor, tags, navController) RenderContentAsMarkdown(content, backgroundColor, tags, nav)
} else { } else {
RenderRegular(content, tags, canPreview, backgroundColor, accountViewModel, navController) RenderRegular(content, tags, canPreview, backgroundColor, accountViewModel, nav)
} }
} }
} }
@Stable
class RichTextViewerState( class RichTextViewerState(
val content: String, val content: String,
val urlSet: Set<String>, val urlSet: Set<String>,
@@ -122,10 +122,10 @@ private fun RenderRegular(
canPreview: Boolean, canPreview: Boolean,
backgroundColor: Color, backgroundColor: Color,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
navController: NavController nav: (String) -> Unit
) { ) {
var processedState by remember { var state by remember {
mutableStateOf<RichTextViewerState?>(RichTextViewerState(content, emptySet(), emptyMap(), emptyList(), emptyMap())) mutableStateOf(RichTextViewerState(content, emptySet(), emptyMap(), emptyList(), emptyMap()))
} }
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
@@ -149,13 +149,12 @@ private fun RenderRegular(
val emojiMap = tags?.filter { it.size > 2 && it[0] == "emoji" }?.associate { ":${it[1]}:" to it[2] } ?: emptyMap() val emojiMap = tags?.filter { it.size > 2 && it[0] == "emoji" }?.associate { ":${it[1]}:" to it[2] } ?: emptyMap()
if (urlSet.isNotEmpty() || emojiMap.isNotEmpty()) { if (urlSet.isNotEmpty() || emojiMap.isNotEmpty()) {
processedState = RichTextViewerState(content, urlSet, imagesForPager, imageList, emojiMap) state = RichTextViewerState(content, urlSet, imagesForPager, imageList, emojiMap)
} }
} }
} }
// FlowRow doesn't work well with paragraphs. So we need to split them // FlowRow doesn't work well with paragraphs. So we need to split them
processedState?.let { state ->
content.split('\n').forEach { paragraph -> content.split('\n').forEach { paragraph ->
FlowRow() { FlowRow() {
val s = if (isArabic(paragraph)) { val s = if (isArabic(paragraph)) {
@@ -188,7 +187,7 @@ private fun RenderRegular(
canPreview, canPreview,
backgroundColor, backgroundColor,
accountViewModel, accountViewModel,
navController nav
) )
} else if (word.startsWith("#")) { } else if (word.startsWith("#")) {
if (tagIndex.matcher(word).matches() && tags != null) { if (tagIndex.matcher(word).matches() && tags != null) {
@@ -198,10 +197,10 @@ private fun RenderRegular(
canPreview, canPreview,
backgroundColor, backgroundColor,
accountViewModel, accountViewModel,
navController nav
) )
} else if (hashTagsPattern.matcher(word).matches()) { } else if (hashTagsPattern.matcher(word).matches()) {
HashTag(word, navController) HashTag(word, nav)
} else { } else {
Text( Text(
text = "$word ", text = "$word ",
@@ -254,7 +253,7 @@ private fun RenderRegular(
canPreview, canPreview,
backgroundColor, backgroundColor,
accountViewModel, accountViewModel,
navController nav
) )
} else if (word.startsWith("#")) { } else if (word.startsWith("#")) {
if (tagIndex.matcher(word).matches() && tags != null) { if (tagIndex.matcher(word).matches() && tags != null) {
@@ -264,10 +263,10 @@ private fun RenderRegular(
canPreview, canPreview,
backgroundColor, backgroundColor,
accountViewModel, accountViewModel,
navController nav
) )
} else if (hashTagsPattern.matcher(word).matches()) { } else if (hashTagsPattern.matcher(word).matches()) {
HashTag(word, navController) HashTag(word, nav)
} else { } else {
Text( Text(
text = "$word ", text = "$word ",
@@ -300,7 +299,6 @@ private fun RenderRegular(
} }
} }
} }
}
@Composable @Composable
fun RenderCustomEmoji(word: String, customEmoji: Map<String, String>) { fun RenderCustomEmoji(word: String, customEmoji: Map<String, String>) {
@@ -311,7 +309,7 @@ fun RenderCustomEmoji(word: String, customEmoji: Map<String, String>) {
} }
@Composable @Composable
private fun RenderContentAsMarkdown(content: String, backgroundColor: Color, tags: List<List<String>>?, navController: NavController) { private fun RenderContentAsMarkdown(content: String, backgroundColor: Color, tags: List<List<String>>?, nav: (String) -> Unit) {
val myMarkDownStyle = richTextDefaults.copy( val myMarkDownStyle = richTextDefaults.copy(
codeBlockStyle = richTextDefaults.codeBlockStyle?.copy( codeBlockStyle = richTextDefaults.codeBlockStyle?.copy(
textStyle = TextStyle( textStyle = TextStyle(
@@ -416,7 +414,7 @@ private fun RenderContentAsMarkdown(content: String, backgroundColor: Color, tag
) { link -> ) { link ->
val route = uriToRoute(link) val route = uriToRoute(link)
if (route != null) { if (route != null) {
navController.navigate(route) nav(route)
} else { } else {
runCatching { uri.openUri(link) } runCatching { uri.openUri(link) }
} }
@@ -572,12 +570,14 @@ fun isBechLink(word: String): Boolean {
} }
@Composable @Composable
fun BechLink(word: String, canPreview: Boolean, backgroundColor: Color, accountViewModel: AccountViewModel, navController: NavController) { fun BechLink(word: String, canPreview: Boolean, backgroundColor: Color, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
var nip19Route by remember { mutableStateOf<Nip19.Return?>(null) } var nip19Route by remember { mutableStateOf<Nip19.Return?>(null) }
var baseNotePair by remember { mutableStateOf<Pair<Note, String?>?>(null) } var baseNotePair by remember { mutableStateOf<Pair<Note, String?>?>(null) }
val scope = rememberCoroutineScope()
LaunchedEffect(key1 = word) { LaunchedEffect(key1 = word) {
withContext(Dispatchers.IO) { scope.launch(Dispatchers.IO) {
Nip19.uriToRoute(word)?.let { Nip19.uriToRoute(word)?.let {
if (it.type == Nip19.Type.NOTE || it.type == Nip19.Type.EVENT || it.type == Nip19.Type.ADDRESS) { if (it.type == Nip19.Type.NOTE || it.type == Nip19.Type.EVENT || it.type == Nip19.Type.ADDRESS) {
LocalCache.checkGetOrCreateNote(it.hex)?.let { note -> LocalCache.checkGetOrCreateNote(it.hex)?.let { note ->
@@ -606,7 +606,7 @@ fun BechLink(word: String, canPreview: Boolean, backgroundColor: Color, accountV
), ),
parentBackgroundColor = backgroundColor, parentBackgroundColor = backgroundColor,
isQuotedNote = true, isQuotedNote = true,
navController = navController nav = nav
) )
if (!it.second.isNullOrEmpty()) { if (!it.second.isNullOrEmpty()) {
Text( Text(
@@ -614,21 +614,23 @@ fun BechLink(word: String, canPreview: Boolean, backgroundColor: Color, accountV
) )
} }
} ?: nip19Route?.let { } ?: nip19Route?.let {
ClickableRoute(it, navController) ClickableRoute(it, nav)
} ?: Text(text = "$word ") } ?: Text(text = "$word ")
} else { } else {
nip19Route?.let { nip19Route?.let {
ClickableRoute(it, navController) ClickableRoute(it, nav)
} ?: Text(text = "$word ") } ?: Text(text = "$word ")
} }
} }
@Composable @Composable
fun HashTag(word: String, navController: NavController) { fun HashTag(word: String, nav: (String) -> Unit) {
var tagSuffixPair by remember { mutableStateOf<Pair<String, String?>?>(null) } var tagSuffixPair by remember { mutableStateOf<Pair<String, String?>?>(null) }
val scope = rememberCoroutineScope()
LaunchedEffect(key1 = word) { LaunchedEffect(key1 = word) {
withContext(Dispatchers.IO) { scope.launch(Dispatchers.IO) {
val hashtagMatcher = hashTagsPattern.matcher(word) val hashtagMatcher = hashTagsPattern.matcher(word)
val (myTag, mySuffix) = try { val (myTag, mySuffix) = try {
@@ -646,7 +648,7 @@ fun HashTag(word: String, navController: NavController) {
} }
tagSuffixPair?.let { tagPair -> tagSuffixPair?.let { tagPair ->
val hashtagIcon = checkForHashtagWithIcon(tagPair.first) val hashtagIcon = remember(tagPair.first) { checkForHashtagWithIcon(tagPair.first) }
ClickableText( ClickableText(
text = buildAnnotatedString { text = buildAnnotatedString {
withStyle( withStyle(
@@ -655,7 +657,7 @@ fun HashTag(word: String, navController: NavController) {
append("#${tagPair.first}") append("#${tagPair.first}")
} }
}, },
onClick = { navController.navigate("Hashtag/${tagPair.first}") } onClick = { nav("Hashtag/${tagPair.first}") }
) )
if (hashtagIcon != null) { if (hashtagIcon != null) {
@@ -701,7 +703,7 @@ fun HashTag(word: String, navController: NavController) {
} }
@Composable @Composable
fun TagLink(word: String, tags: List<List<String>>, canPreview: Boolean, backgroundColor: Color, accountViewModel: AccountViewModel, navController: NavController) { fun TagLink(word: String, tags: List<List<String>>, canPreview: Boolean, backgroundColor: Color, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
var baseUserPair by remember { mutableStateOf<Pair<User, String?>?>(null) } var baseUserPair by remember { mutableStateOf<Pair<User, String?>?>(null) }
var baseNotePair by remember { mutableStateOf<Pair<Note, String?>?>(null) } var baseNotePair by remember { mutableStateOf<Pair<Note, String?>?>(null) }
@@ -753,7 +755,7 @@ fun TagLink(word: String, tags: List<List<String>>, canPreview: Boolean, backgro
suffix = "${it.second} ", suffix = "${it.second} ",
tags = userTags, tags = userTags,
route = route, route = route,
navController = navController nav = nav
) )
} }
@@ -773,13 +775,13 @@ fun TagLink(word: String, tags: List<List<String>>, canPreview: Boolean, backgro
), ),
parentBackgroundColor = backgroundColor, parentBackgroundColor = backgroundColor,
isQuotedNote = true, isQuotedNote = true,
navController = navController nav = nav
) )
it.second?.ifBlank { null }?.let { it.second?.ifBlank { null }?.let {
Text(text = "$it ") Text(text = "$it ")
} }
} else { } else {
ClickableNoteTag(it.first, navController) ClickableNoteTag(it.first, nav)
Text(text = "${it.second} ") Text(text = "${it.second} ")
} }
} }
@@ -132,6 +132,7 @@ fun RobohashAsyncImageProxy(
filterQuality: FilterQuality = DrawScope.DefaultFilterQuality filterQuality: FilterQuality = DrawScope.DefaultFilterQuality
) { ) {
val proxy = remember(model) { model.proxyUrl() } val proxy = remember(model) { model.proxyUrl() }
if (proxy == null) { if (proxy == null) {
RobohashAsyncImage( RobohashAsyncImage(
robot = robot, robot = robot,
@@ -37,7 +37,6 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.navigation.NavController
import androidx.navigation.NavHostController import androidx.navigation.NavHostController
import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.currentBackStackEntryAsState
import com.vitorpamplona.amethyst.NotificationCache import com.vitorpamplona.amethyst.NotificationCache
@@ -116,24 +115,26 @@ private fun RowScope.HasNewItemsIcon(
val accountState by accountViewModel.accountLiveData.observeAsState() val accountState by accountViewModel.accountLiveData.observeAsState()
val account = remember(accountState) { accountState?.account } ?: return val account = remember(accountState) { accountState?.account } ?: return
val notifState = NotificationCache.live.observeAsState() val notifState by NotificationCache.live.observeAsState()
val notif = remember(notifState) { notifState.value } ?: return val notif = remember(notifState) { notifState?.cache } ?: return
var hasNewItems by remember { mutableStateOf<Boolean>(false) } var hasNewItems by remember { mutableStateOf<Boolean>(false) }
LaunchedEffect(key1 = notif) { LaunchedEffect(key1 = notifState, key2 = accountState) {
scope.launch(Dispatchers.IO) { scope.launch(Dispatchers.IO) {
val newHasNewItems = route.hasNewItems(account, notif.cache, emptySet()) val newHasNewItems = route.hasNewItems(account, notif, emptySet())
println("Notification Change ${route.route} $hasNewItems -> $newHasNewItems")
if (newHasNewItems != hasNewItems) { if (newHasNewItems != hasNewItems) {
hasNewItems = newHasNewItems hasNewItems = newHasNewItems
} }
} }
} }
LaunchedEffect(Unit) { LaunchedEffect(accountState) {
scope.launch(Dispatchers.IO) { scope.launch(Dispatchers.IO) {
LocalCache.live.newEventBundles.collect { LocalCache.live.newEventBundles.collect {
val newHasNewItems = route.hasNewItems(account, notif.cache, it) val newHasNewItems = route.hasNewItems(account, notif, it)
println("Notification From Base ${route.route} $hasNewItems -> $newHasNewItems")
if (newHasNewItems != hasNewItems) { if (newHasNewItems != hasNewItems) {
hasNewItems = newHasNewItems hasNewItems = newHasNewItems
} }
@@ -182,7 +183,7 @@ private fun RowScope.BottomIcon(
iconSize: Dp, iconSize: Dp,
base: String, base: String,
hasNewItems: Boolean, hasNewItems: Boolean,
navController: NavController, navController: NavHostController,
onClick: (Boolean) -> Unit onClick: (Boolean) -> Unit
) { ) {
val navBackStackEntry by navController.currentBackStackEntryAsState() val navBackStackEntry by navController.currentBackStackEntryAsState()
@@ -82,6 +82,14 @@ fun AppNavigation(
} }
} }
val nav = remember {
{ route: String ->
if (getRouteWithArguments(navController) != route) {
navController.navigate(route)
}
}
}
NavHost(navController, startDestination = Route.Home.route) { NavHost(navController, startDestination = Route.Home.route) {
Route.Video.let { route -> Route.Video.let { route ->
composable(route.route, route.arguments, content = { composable(route.route, route.arguments, content = {
@@ -90,7 +98,7 @@ fun AppNavigation(
VideoScreen( VideoScreen(
videoFeedView = videoFeedViewModel, videoFeedView = videoFeedViewModel,
accountViewModel = accountViewModel, accountViewModel = accountViewModel,
navController = navController, nav = nav,
scrollToTop = scrollToTop scrollToTop = scrollToTop
) )
@@ -108,7 +116,7 @@ fun AppNavigation(
SearchScreen( SearchScreen(
searchFeedViewModel = searchFeedViewModel, searchFeedViewModel = searchFeedViewModel,
accountViewModel = accountViewModel, accountViewModel = accountViewModel,
navController = navController, nav = nav,
scrollToTop = scrollToTop scrollToTop = scrollToTop
) )
@@ -128,7 +136,7 @@ fun AppNavigation(
homeFeedViewModel = homeFeedViewModel, homeFeedViewModel = homeFeedViewModel,
repliesFeedViewModel = repliesFeedViewModel, repliesFeedViewModel = repliesFeedViewModel,
accountViewModel = accountViewModel, accountViewModel = accountViewModel,
navController = navController, nav = nav,
pagerState = homePagerState, pagerState = homePagerState,
scrollToTop = scrollToTop, scrollToTop = scrollToTop,
nip47 = nip47 nip47 = nip47
@@ -152,7 +160,7 @@ fun AppNavigation(
notifFeedViewModel = notifFeedViewModel, notifFeedViewModel = notifFeedViewModel,
userReactionsStatsModel = userReactionsStatsModel, userReactionsStatsModel = userReactionsStatsModel,
accountViewModel = accountViewModel, accountViewModel = accountViewModel,
navController = navController, nav = nav,
scrollToTop = scrollToTop scrollToTop = scrollToTop
) )
@@ -163,16 +171,16 @@ fun AppNavigation(
}) })
} }
composable(Route.Message.route, content = { ChatroomListScreen(accountViewModel, navController) }) composable(Route.Message.route, content = { ChatroomListScreen(accountViewModel, nav) })
composable(Route.BlockedUsers.route, content = { HiddenUsersScreen(accountViewModel, navController) }) composable(Route.BlockedUsers.route, content = { HiddenUsersScreen(accountViewModel, nav) })
composable(Route.Bookmarks.route, content = { BookmarkListScreen(accountViewModel, navController) }) composable(Route.Bookmarks.route, content = { BookmarkListScreen(accountViewModel, nav) })
Route.Profile.let { route -> Route.Profile.let { route ->
composable(route.route, route.arguments, content = { composable(route.route, route.arguments, content = {
ProfileScreen( ProfileScreen(
userId = it.arguments?.getString("id"), userId = it.arguments?.getString("id"),
accountViewModel = accountViewModel, accountViewModel = accountViewModel,
navController = navController nav = nav
) )
}) })
} }
@@ -182,7 +190,7 @@ fun AppNavigation(
ThreadScreen( ThreadScreen(
noteId = it.arguments?.getString("id"), noteId = it.arguments?.getString("id"),
accountViewModel = accountViewModel, accountViewModel = accountViewModel,
navController = navController nav = nav
) )
}) })
} }
@@ -192,7 +200,7 @@ fun AppNavigation(
HashtagScreen( HashtagScreen(
tag = it.arguments?.getString("id"), tag = it.arguments?.getString("id"),
accountViewModel = accountViewModel, accountViewModel = accountViewModel,
navController = navController nav = nav
) )
}) })
} }
@@ -202,7 +210,7 @@ fun AppNavigation(
ChatroomScreen( ChatroomScreen(
userId = it.arguments?.getString("id"), userId = it.arguments?.getString("id"),
accountViewModel = accountViewModel, accountViewModel = accountViewModel,
navController = navController nav = nav
) )
}) })
} }
@@ -212,7 +220,7 @@ fun AppNavigation(
ChannelScreen( ChannelScreen(
channelId = it.arguments?.getString("id"), channelId = it.arguments?.getString("id"),
accountViewModel = accountViewModel, accountViewModel = accountViewModel,
navController = navController nav = nav
) )
}) })
} }
@@ -230,7 +238,7 @@ fun AppNavigation(
actionableNextPage?.let { actionableNextPage?.let {
LaunchedEffect(it) { LaunchedEffect(it) {
navController.navigate(it) nav(it)
} }
actionableNextPage = null actionableNextPage = null
} }
@@ -83,7 +83,7 @@ import kotlinx.coroutines.withContext
@Composable @Composable
fun AppTopBar(followLists: FollowListViewModel, navController: NavHostController, scaffoldState: ScaffoldState, accountViewModel: AccountViewModel) { fun AppTopBar(followLists: FollowListViewModel, navController: NavHostController, scaffoldState: ScaffoldState, accountViewModel: AccountViewModel) {
when (currentRoute(navController)?.substringBefore("?")) { when (currentRoute(navController)?.substringBefore("?")) {
// Route.Profile.route -> TopBarWithBackButton(navController) // Route.Profile.route -> TopBarWithBackButton(nav)
Route.Home.base -> HomeTopBar(followLists, scaffoldState, accountViewModel) Route.Home.base -> HomeTopBar(followLists, scaffoldState, accountViewModel)
Route.Video.base -> StoriesTopBar(followLists, scaffoldState, accountViewModel) Route.Video.base -> StoriesTopBar(followLists, scaffoldState, accountViewModel)
Route.Notification.base -> NotificationTopBar(followLists, scaffoldState, accountViewModel) Route.Notification.base -> NotificationTopBar(followLists, scaffoldState, accountViewModel)
@@ -48,8 +48,6 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.navigation.NavController
import androidx.navigation.NavHostController
import coil.compose.AsyncImage import coil.compose.AsyncImage
import com.vitorpamplona.amethyst.BuildConfig import com.vitorpamplona.amethyst.BuildConfig
import com.vitorpamplona.amethyst.LocalPreferences import com.vitorpamplona.amethyst.LocalPreferences
@@ -69,7 +67,7 @@ import kotlinx.coroutines.launch
@OptIn(ExperimentalMaterialApi::class) @OptIn(ExperimentalMaterialApi::class)
@Composable @Composable
fun DrawerContent( fun DrawerContent(
navController: NavHostController, nav: (String) -> Unit,
scaffoldState: ScaffoldState, scaffoldState: ScaffoldState,
sheetState: ModalBottomSheetState, sheetState: ModalBottomSheetState,
accountViewModel: AccountViewModel accountViewModel: AccountViewModel
@@ -89,7 +87,7 @@ fun DrawerContent(
.padding(horizontal = 25.dp) .padding(horizontal = 25.dp)
.padding(top = 100.dp), .padding(top = 100.dp),
scaffoldState, scaffoldState,
navController nav
) )
Divider( Divider(
thickness = 0.25.dp, thickness = 0.25.dp,
@@ -97,7 +95,7 @@ fun DrawerContent(
) )
ListContent( ListContent(
account.userProfile().pubkeyHex, account.userProfile().pubkeyHex,
navController, nav,
scaffoldState, scaffoldState,
sheetState, sheetState,
modifier = Modifier modifier = Modifier
@@ -106,7 +104,7 @@ fun DrawerContent(
account account
) )
BottomContent(account.userProfile(), scaffoldState, navController) BottomContent(account.userProfile(), scaffoldState, nav)
} }
} }
} }
@@ -116,7 +114,7 @@ fun ProfileContent(
baseAccountUser: User, baseAccountUser: User,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
scaffoldState: ScaffoldState, scaffoldState: ScaffoldState,
navController: NavController nav: (String) -> Unit
) { ) {
val coroutineScope = rememberCoroutineScope() val coroutineScope = rememberCoroutineScope()
@@ -170,7 +168,7 @@ fun ProfileContent(
.border(3.dp, MaterialTheme.colors.background, CircleShape) .border(3.dp, MaterialTheme.colors.background, CircleShape)
.background(MaterialTheme.colors.background) .background(MaterialTheme.colors.background)
.clickable(onClick = { .clickable(onClick = {
navController.navigate(route) nav(route)
coroutineScope.launch { coroutineScope.launch {
scaffoldState.drawerState.close() scaffoldState.drawerState.close()
} }
@@ -185,7 +183,7 @@ fun ProfileContent(
modifier = Modifier modifier = Modifier
.padding(top = 7.dp) .padding(top = 7.dp)
.clickable(onClick = { .clickable(onClick = {
navController.navigate(route) nav(route)
coroutineScope.launch { coroutineScope.launch {
scaffoldState.drawerState.close() scaffoldState.drawerState.close()
} }
@@ -203,7 +201,7 @@ fun ProfileContent(
.padding(top = 15.dp) .padding(top = 15.dp)
.clickable( .clickable(
onClick = { onClick = {
navController.navigate(route) nav(route)
coroutineScope.launch { coroutineScope.launch {
scaffoldState.drawerState.close() scaffoldState.drawerState.close()
} }
@@ -215,7 +213,7 @@ fun ProfileContent(
modifier = Modifier modifier = Modifier
.padding(top = 15.dp) .padding(top = 15.dp)
.clickable(onClick = { .clickable(onClick = {
navController.navigate(route) nav(route)
coroutineScope.launch { coroutineScope.launch {
scaffoldState.drawerState.close() scaffoldState.drawerState.close()
} }
@@ -244,7 +242,7 @@ fun ProfileContent(
@Composable @Composable
fun ListContent( fun ListContent(
accountUserPubKey: String?, accountUserPubKey: String?,
navController: NavHostController, nav: (String) -> Unit,
scaffoldState: ScaffoldState, scaffoldState: ScaffoldState,
sheetState: ModalBottomSheetState, sheetState: ModalBottomSheetState,
modifier: Modifier, modifier: Modifier,
@@ -263,7 +261,7 @@ fun ListContent(
title = stringResource(R.string.profile), title = stringResource(R.string.profile),
icon = Route.Profile.icon, icon = Route.Profile.icon,
tint = MaterialTheme.colors.primary, tint = MaterialTheme.colors.primary,
navController = navController, nav = nav,
scaffoldState = scaffoldState, scaffoldState = scaffoldState,
route = "User/$accountUserPubKey" route = "User/$accountUserPubKey"
) )
@@ -272,7 +270,7 @@ fun ListContent(
title = stringResource(R.string.bookmarks), title = stringResource(R.string.bookmarks),
icon = Route.Bookmarks.icon, icon = Route.Bookmarks.icon,
tint = MaterialTheme.colors.onBackground, tint = MaterialTheme.colors.onBackground,
navController = navController, nav = nav,
scaffoldState = scaffoldState, scaffoldState = scaffoldState,
route = Route.Bookmarks.route route = Route.Bookmarks.route
) )
@@ -282,7 +280,7 @@ fun ListContent(
title = stringResource(R.string.security_filters), title = stringResource(R.string.security_filters),
icon = Route.BlockedUsers.icon, icon = Route.BlockedUsers.icon,
tint = MaterialTheme.colors.onBackground, tint = MaterialTheme.colors.onBackground,
navController = navController, nav = nav,
scaffoldState = scaffoldState, scaffoldState = scaffoldState,
route = Route.BlockedUsers.route route = Route.BlockedUsers.route
) )
@@ -402,13 +400,13 @@ fun NavigationRow(
title: String, title: String,
icon: Int, icon: Int,
tint: Color, tint: Color,
navController: NavHostController, nav: (String) -> Unit,
scaffoldState: ScaffoldState, scaffoldState: ScaffoldState,
route: String route: String
) { ) {
val coroutineScope = rememberCoroutineScope() val coroutineScope = rememberCoroutineScope()
IconRow(title, icon, tint, onClick = { IconRow(title, icon, tint, onClick = {
navController.navigate(route) nav(route)
coroutineScope.launch { coroutineScope.launch {
scaffoldState.drawerState.close() scaffoldState.drawerState.close()
} }
@@ -448,7 +446,7 @@ fun IconRow(title: String, icon: Int, tint: Color, onClick: () -> Unit, onLongCl
} }
@Composable @Composable
fun BottomContent(user: User, scaffoldState: ScaffoldState, navController: NavController) { fun BottomContent(user: User, scaffoldState: ScaffoldState, nav: (String) -> Unit) {
val coroutineScope = rememberCoroutineScope() val coroutineScope = rememberCoroutineScope()
// store the dialog open or close state // store the dialog open or close state
@@ -515,7 +513,7 @@ fun BottomContent(user: User, scaffoldState: ScaffoldState, navController: NavCo
coroutineScope.launch { coroutineScope.launch {
scaffoldState.drawerState.close() scaffoldState.drawerState.close()
} }
navController.navigate(it) nav(it)
}, },
onClose = { dialogOpen = false } onClose = { dialogOpen = false }
) )
@@ -1,8 +1,10 @@
package com.vitorpamplona.amethyst.ui.navigation package com.vitorpamplona.amethyst.ui.navigation
import android.os.Bundle
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.navigation.NamedNavArgument import androidx.navigation.NamedNavArgument
import androidx.navigation.NavDestination
import androidx.navigation.NavHostController import androidx.navigation.NavHostController
import androidx.navigation.NavType import androidx.navigation.NavType
import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.currentBackStackEntryAsState
@@ -185,3 +187,48 @@ object MessagesLatestItem {
return (note.createdAt() ?: 0) > lastTime return (note.createdAt() ?: 0) > lastTime
} }
} }
fun getRouteWithArguments(navController: NavHostController): String? {
val currentEntry = navController.currentBackStackEntry ?: return null
return getRouteWithArguments(currentEntry.destination, currentEntry.arguments)
}
private fun getRouteWithArguments(
destination: NavDestination,
arguments: Bundle?
): String? {
var route = destination.route ?: return null
arguments?.let { bundle ->
destination.arguments.keys.forEach { key ->
val value = destination.arguments[key]?.type?.get(bundle, key)?.toString()
if (value == null) {
val keyStart = route.indexOf("{$key}")
// if it is a parameter, removes the complete segment `var={key}` and adjust connectors `#`, `&` or `&`
if (keyStart > 0 && route[keyStart - 1] == '=') {
val end = keyStart + "{$key}".length
var start = keyStart
for (i in keyStart downTo 0) {
if (route[i] == '#' || route[i] == '?' || route[i] == '&') {
start = i + 1
break
}
}
if (end < route.length && route[end] == '&') {
route = route.removeRange(start, end + 1)
} else if (end < route.length && route[end] == '#') {
route = route.removeRange(start - 1, end)
} else if (end == route.length) {
route = route.removeRange(start - 1, end)
} else {
route = route.removeRange(start, end)
}
} else {
route = route.replaceFirst("{$key}", "")
}
} else {
route = route.replaceFirst("{$key}", value)
}
}
}
return route
}
@@ -32,7 +32,6 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import com.vitorpamplona.amethyst.NotificationCache import com.vitorpamplona.amethyst.NotificationCache
import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.ui.screen.BadgeCard import com.vitorpamplona.amethyst.ui.screen.BadgeCard
@@ -42,7 +41,7 @@ import kotlinx.coroutines.launch
@OptIn(ExperimentalFoundationApi::class) @OptIn(ExperimentalFoundationApi::class)
@Composable @Composable
fun BadgeCompose(likeSetCard: BadgeCard, isInnerNote: Boolean = false, routeForLastRead: String, accountViewModel: AccountViewModel, navController: NavController) { fun BadgeCompose(likeSetCard: BadgeCard, isInnerNote: Boolean = false, routeForLastRead: String, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
val noteState by likeSetCard.note.live().metadata.observeAsState() val noteState by likeSetCard.note.live().metadata.observeAsState()
val note = noteState?.note val note = noteState?.note
@@ -79,7 +78,7 @@ fun BadgeCompose(likeSetCard: BadgeCard, isInnerNote: Boolean = false, routeForL
routeFor( routeFor(
note, note,
accountViewModel.userProfile() accountViewModel.userProfile()
)?.let { navController.navigate(it) } )?.let { nav(it) }
} }
}, },
onLongClick = { popupExpanded = true } onLongClick = { popupExpanded = true }
@@ -149,7 +148,7 @@ fun BadgeCompose(likeSetCard: BadgeCard, isInnerNote: Boolean = false, routeForL
isBoostedNote = true, isBoostedNote = true,
parentBackgroundColor = backgroundColor, parentBackgroundColor = backgroundColor,
accountViewModel = accountViewModel, accountViewModel = accountViewModel,
navController = navController nav = nav
) )
} }
@@ -17,7 +17,6 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import com.google.accompanist.flowlayout.FlowRow import com.google.accompanist.flowlayout.FlowRow
import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.Note
@@ -55,7 +54,7 @@ fun BlankNote(modifier: Modifier = Modifier, isQuote: Boolean = false, idHex: St
} }
@Composable @Composable
fun HiddenNote(reports: Set<Note>, loggedIn: User, modifier: Modifier = Modifier, isQuote: Boolean = false, navController: NavController, onClick: () -> Unit) { fun HiddenNote(reports: Set<Note>, loggedIn: User, modifier: Modifier = Modifier, isQuote: Boolean = false, nav: (String) -> Unit, onClick: () -> Unit) {
Column(modifier = modifier) { Column(modifier = modifier) {
Row(modifier = Modifier.padding(horizontal = if (!isQuote) 12.dp else 6.dp)) { Row(modifier = Modifier.padding(horizontal = if (!isQuote) 12.dp else 6.dp)) {
Column(modifier = Modifier.padding(start = if (!isQuote) 10.dp else 5.dp)) { Column(modifier = Modifier.padding(start = if (!isQuote) 10.dp else 5.dp)) {
@@ -75,7 +74,7 @@ fun HiddenNote(reports: Set<Note>, loggedIn: User, modifier: Modifier = Modifier
reports.forEach { reports.forEach {
NoteAuthorPicture( NoteAuthorPicture(
baseNote = it, baseNote = it,
navController = navController, nav = nav,
userAccount = loggedIn, userAccount = loggedIn,
size = 35.dp size = 35.dp
) )
@@ -25,7 +25,6 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.compositeOver import androidx.compose.ui.graphics.compositeOver
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import com.google.accompanist.flowlayout.FlowRow import com.google.accompanist.flowlayout.FlowRow
import com.vitorpamplona.amethyst.NotificationCache import com.vitorpamplona.amethyst.NotificationCache
import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.R
@@ -37,7 +36,7 @@ import kotlinx.coroutines.withContext
@OptIn(ExperimentalFoundationApi::class) @OptIn(ExperimentalFoundationApi::class)
@Composable @Composable
fun BoostSetCompose(boostSetCard: BoostSetCard, isInnerNote: Boolean = false, routeForLastRead: String, accountViewModel: AccountViewModel, navController: NavController) { fun BoostSetCompose(boostSetCard: BoostSetCard, isInnerNote: Boolean = false, routeForLastRead: String, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
val noteState by boostSetCard.note.live().metadata.observeAsState() val noteState by boostSetCard.note.live().metadata.observeAsState()
val note = noteState?.note val note = noteState?.note
@@ -77,7 +76,7 @@ fun BoostSetCompose(boostSetCard: BoostSetCard, isInnerNote: Boolean = false, ro
routeFor( routeFor(
note, note,
account.userProfile() account.userProfile()
)?.let { navController.navigate(it) } )?.let { nav(it) }
} }
}, },
onLongClick = { popupExpanded = true } onLongClick = { popupExpanded = true }
@@ -114,7 +113,7 @@ fun BoostSetCompose(boostSetCard: BoostSetCard, isInnerNote: Boolean = false, ro
boostSetCard.boostEvents.forEach { boostSetCard.boostEvents.forEach {
NoteAuthorPicture( NoteAuthorPicture(
baseNote = it, baseNote = it,
navController = navController, nav = nav,
userAccount = account.userProfile(), userAccount = account.userProfile(),
size = 35.dp size = 35.dp
) )
@@ -128,7 +127,7 @@ fun BoostSetCompose(boostSetCard: BoostSetCard, isInnerNote: Boolean = false, ro
isBoostedNote = true, isBoostedNote = true,
parentBackgroundColor = backgroundColor, parentBackgroundColor = backgroundColor,
accountViewModel = accountViewModel, accountViewModel = accountViewModel,
navController = navController nav = nav
) )
NoteDropDownMenu(note, popupExpanded, { popupExpanded = false }, accountViewModel) NoteDropDownMenu(note, popupExpanded, { popupExpanded = false }, accountViewModel)
@@ -39,7 +39,6 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.text.withStyle import androidx.compose.ui.text.withStyle
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.navigation.NavController
import com.vitorpamplona.amethyst.NotificationCache import com.vitorpamplona.amethyst.NotificationCache
import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.LocalCache
@@ -57,7 +56,7 @@ import kotlinx.coroutines.launch
fun ChatroomCompose( fun ChatroomCompose(
baseNote: Note, baseNote: Note,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
navController: NavController nav: (String) -> Unit
) { ) {
val noteState by baseNote.live().metadata.observeAsState() val noteState by baseNote.live().metadata.observeAsState()
val note = noteState?.note val note = noteState?.note
@@ -128,7 +127,7 @@ fun ChatroomCompose(
channelLastTime = note.createdAt(), channelLastTime = note.createdAt(),
channelLastContent = "${author?.toBestDisplayName()}: " + description, channelLastContent = "${author?.toBestDisplayName()}: " + description,
hasNewMessages = hasNewMessages, hasNewMessages = hasNewMessages,
onClick = { navController.navigate("Channel/${chan.idHex}") } onClick = { nav("Channel/${chan.idHex}") }
) )
} }
} else { } else {
@@ -172,7 +171,7 @@ fun ChatroomCompose(
channelLastTime = note.createdAt(), channelLastTime = note.createdAt(),
channelLastContent = accountViewModel.decrypt(note), channelLastContent = accountViewModel.decrypt(note),
hasNewMessages = hasNewMessages, hasNewMessages = hasNewMessages,
onClick = { navController.navigate("Room/${user.pubkeyHex}") } onClick = { nav("Room/${user.pubkeyHex}") }
) )
} }
} }
@@ -50,7 +50,6 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.navigation.NavController
import com.google.accompanist.flowlayout.FlowRow import com.google.accompanist.flowlayout.FlowRow
import com.vitorpamplona.amethyst.NotificationCache import com.vitorpamplona.amethyst.NotificationCache
import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.R
@@ -81,7 +80,7 @@ fun ChatroomMessageCompose(
innerQuote: Boolean = false, innerQuote: Boolean = false,
parentBackgroundColor: Color? = null, parentBackgroundColor: Color? = null,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
navController: NavController, nav: (String) -> Unit,
onWantsToReply: (Note) -> Unit onWantsToReply: (Note) -> Unit
) { ) {
val accountState by accountViewModel.accountLiveData.observeAsState() val accountState by accountViewModel.accountLiveData.observeAsState()
@@ -137,7 +136,7 @@ fun ChatroomMessageCompose(
account.userProfile(), account.userProfile(),
Modifier, Modifier,
innerQuote, innerQuote,
navController, nav,
onClick = { showHiddenNote = true } onClick = { showHiddenNote = true }
) )
} else { } else {
@@ -210,7 +209,7 @@ fun ChatroomMessageCompose(
.combinedClickable( .combinedClickable(
onClick = { onClick = {
if (noteEvent is ChannelCreateEvent) { if (noteEvent is ChannelCreateEvent) {
navController.navigate("Channel/${note.idHex}") nav("Channel/${note.idHex}")
} }
}, },
onLongClick = { popupExpanded = true } onLongClick = { popupExpanded = true }
@@ -229,7 +228,7 @@ fun ChatroomMessageCompose(
DrawAuthorInfo( DrawAuthorInfo(
baseNote, baseNote,
alignment, alignment,
navController nav
) )
} else { } else {
Spacer(modifier = Modifier.height(5.dp)) Spacer(modifier = Modifier.height(5.dp))
@@ -245,7 +244,7 @@ fun ChatroomMessageCompose(
innerQuote = true, innerQuote = true,
parentBackgroundColor = backgroundBubbleColor, parentBackgroundColor = backgroundBubbleColor,
accountViewModel = accountViewModel, accountViewModel = accountViewModel,
navController = navController, nav = nav,
onWantsToReply = onWantsToReply onWantsToReply = onWantsToReply
) )
} }
@@ -269,7 +268,7 @@ fun ChatroomMessageCompose(
isAcceptableAndCanPreview.second, isAcceptableAndCanPreview.second,
backgroundBubbleColor, backgroundBubbleColor,
accountViewModel, accountViewModel,
navController nav
) )
} }
} }
@@ -363,7 +362,7 @@ private fun RenderRegularTextNote(
canPreview: Boolean, canPreview: Boolean,
backgroundBubbleColor: Color, backgroundBubbleColor: Color,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
navController: NavController nav: (String) -> Unit
) { ) {
val tags = remember { note.event?.tags() } val tags = remember { note.event?.tags() }
val eventContent = remember { accountViewModel.decrypt(note) } val eventContent = remember { accountViewModel.decrypt(note) }
@@ -371,23 +370,23 @@ private fun RenderRegularTextNote(
if (eventContent != null) { if (eventContent != null) {
TranslatableRichTextViewer( TranslatableRichTextViewer(
eventContent, content = eventContent,
canPreview, canPreview = canPreview,
modifier, modifier = modifier,
tags, tags = tags,
backgroundBubbleColor, backgroundColor = backgroundBubbleColor,
accountViewModel, accountViewModel = accountViewModel,
navController nav = nav
) )
} else { } else {
TranslatableRichTextViewer( TranslatableRichTextViewer(
stringResource(R.string.could_not_decrypt_the_message), content = stringResource(id = R.string.could_not_decrypt_the_message),
true, canPreview = true,
modifier, modifier = modifier,
tags, tags = tags,
backgroundBubbleColor, backgroundColor = backgroundBubbleColor,
accountViewModel, accountViewModel = accountViewModel,
navController nav = nav
) )
} }
} }
@@ -444,7 +443,7 @@ private fun RenderCreateChannelNote(note: Note) {
private fun DrawAuthorInfo( private fun DrawAuthorInfo(
baseNote: Note, baseNote: Note,
alignment: Arrangement.Horizontal, alignment: Arrangement.Horizontal,
navController: NavController nav: (String) -> Unit
) { ) {
val userState by baseNote.author!!.live().metadata.observeAsState() val userState by baseNote.author!!.live().metadata.observeAsState()
@@ -468,7 +467,7 @@ private fun DrawAuthorInfo(
.height(25.dp) .height(25.dp)
.clip(shape = CircleShape) .clip(shape = CircleShape)
.clickable(onClick = { .clickable(onClick = {
navController.navigate(route) nav(route)
}) })
) )
@@ -479,7 +478,7 @@ private fun DrawAuthorInfo(
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
overrideColor = MaterialTheme.colors.onBackground, overrideColor = MaterialTheme.colors.onBackground,
route = route, route = route,
navController = navController nav = nav
) )
} }
} }
@@ -25,7 +25,6 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.compositeOver import androidx.compose.ui.graphics.compositeOver
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import com.google.accompanist.flowlayout.FlowRow import com.google.accompanist.flowlayout.FlowRow
import com.vitorpamplona.amethyst.NotificationCache import com.vitorpamplona.amethyst.NotificationCache
import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.R
@@ -37,7 +36,7 @@ import kotlinx.coroutines.withContext
@OptIn(ExperimentalFoundationApi::class) @OptIn(ExperimentalFoundationApi::class)
@Composable @Composable
fun LikeSetCompose(likeSetCard: LikeSetCard, isInnerNote: Boolean = false, routeForLastRead: String, accountViewModel: AccountViewModel, navController: NavController) { fun LikeSetCompose(likeSetCard: LikeSetCard, isInnerNote: Boolean = false, routeForLastRead: String, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
val noteState by likeSetCard.note.live().metadata.observeAsState() val noteState by likeSetCard.note.live().metadata.observeAsState()
val note = noteState?.note val note = noteState?.note
@@ -76,7 +75,7 @@ fun LikeSetCompose(likeSetCard: LikeSetCard, isInnerNote: Boolean = false, route
routeFor( routeFor(
note, note,
account.userProfile() account.userProfile()
)?.let { navController.navigate(it) } )?.let { nav(it) }
} }
}, },
onLongClick = { popupExpanded = true } onLongClick = { popupExpanded = true }
@@ -113,7 +112,7 @@ fun LikeSetCompose(likeSetCard: LikeSetCard, isInnerNote: Boolean = false, route
likeSetCard.likeEvents.forEach { likeSetCard.likeEvents.forEach {
NoteAuthorPicture( NoteAuthorPicture(
baseNote = it, baseNote = it,
navController = navController, nav = nav,
userAccount = account.userProfile(), userAccount = account.userProfile(),
size = 35.dp size = 35.dp
) )
@@ -127,7 +126,7 @@ fun LikeSetCompose(likeSetCard: LikeSetCard, isInnerNote: Boolean = false, route
isBoostedNote = true, isBoostedNote = true,
parentBackgroundColor = backgroundColor, parentBackgroundColor = backgroundColor,
accountViewModel = accountViewModel, accountViewModel = accountViewModel,
navController = navController nav = nav
) )
NoteDropDownMenu(note, popupExpanded, { popupExpanded = false }, accountViewModel) NoteDropDownMenu(note, popupExpanded, { popupExpanded = false }, accountViewModel)
@@ -25,7 +25,6 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.compositeOver import androidx.compose.ui.graphics.compositeOver
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import com.vitorpamplona.amethyst.NotificationCache import com.vitorpamplona.amethyst.NotificationCache
import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.ui.screen.MessageSetCard import com.vitorpamplona.amethyst.ui.screen.MessageSetCard
@@ -35,7 +34,7 @@ import kotlinx.coroutines.launch
@OptIn(ExperimentalFoundationApi::class) @OptIn(ExperimentalFoundationApi::class)
@Composable @Composable
fun MessageSetCompose(messageSetCard: MessageSetCard, routeForLastRead: String, accountViewModel: AccountViewModel, navController: NavController) { fun MessageSetCompose(messageSetCard: MessageSetCard, routeForLastRead: String, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
val noteState by messageSetCard.note.live().metadata.observeAsState() val noteState by messageSetCard.note.live().metadata.observeAsState()
val note = remember(noteState) { noteState?.note } val note = remember(noteState) { noteState?.note }
@@ -81,7 +80,7 @@ fun MessageSetCompose(messageSetCard: MessageSetCard, routeForLastRead: String,
routeFor( routeFor(
note, note,
accountViewModel.userProfile() accountViewModel.userProfile()
)?.let { navController.navigate(it) } )?.let { nav(it) }
} }
}, },
onLongClick = { popupExpanded = true } onLongClick = { popupExpanded = true }
@@ -108,7 +107,7 @@ fun MessageSetCompose(messageSetCard: MessageSetCard, routeForLastRead: String,
addMarginTop = false, addMarginTop = false,
parentBackgroundColor = backgroundColor, parentBackgroundColor = backgroundColor,
accountViewModel = accountViewModel, accountViewModel = accountViewModel,
navController = navController nav = nav
) )
NoteDropDownMenu(note, popupExpanded, { popupExpanded = false }, accountViewModel) NoteDropDownMenu(note, popupExpanded, { popupExpanded = false }, accountViewModel)
@@ -20,6 +20,7 @@ import androidx.compose.material.icons.filled.Bolt
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.State import androidx.compose.runtime.State
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
@@ -35,7 +36,6 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.navigation.NavController
import com.google.accompanist.flowlayout.FlowRow import com.google.accompanist.flowlayout.FlowRow
import com.vitorpamplona.amethyst.NotificationCache import com.vitorpamplona.amethyst.NotificationCache
import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.R
@@ -51,13 +51,15 @@ import com.vitorpamplona.amethyst.ui.screen.MultiSetCard
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.showAmountAxis import com.vitorpamplona.amethyst.ui.screen.loggedIn.showAmountAxis
import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.ImmutableMap
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.math.BigDecimal import java.math.BigDecimal
@OptIn(ExperimentalFoundationApi::class) @OptIn(ExperimentalFoundationApi::class)
@Composable @Composable
fun MultiSetCompose(multiSetCard: MultiSetCard, routeForLastRead: String, accountViewModel: AccountViewModel, navController: NavController) { fun MultiSetCompose(multiSetCard: MultiSetCard, routeForLastRead: String, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
val baseNote = remember { multiSetCard.note } val baseNote = remember { multiSetCard.note }
val noteState by baseNote.live().metadata.observeAsState() val noteState by baseNote.live().metadata.observeAsState()
@@ -90,15 +92,18 @@ fun MultiSetCompose(multiSetCard: MultiSetCard, routeForLastRead: String, accoun
val primaryColor = MaterialTheme.colors.primary.copy(0.12f) val primaryColor = MaterialTheme.colors.primary.copy(0.12f)
val defaultBackgroundColor = MaterialTheme.colors.background val defaultBackgroundColor = MaterialTheme.colors.background
val backgroundColor = remember(isNew) { val backgroundColor by remember(isNew) {
derivedStateOf {
if (isNew) { if (isNew) {
primaryColor.compositeOver(defaultBackgroundColor) primaryColor.compositeOver(defaultBackgroundColor)
} else { } else {
defaultBackgroundColor defaultBackgroundColor
} }
} }
}
val columnModifier = remember(isNew) { val columnModifier by remember(isNew, backgroundColor) {
derivedStateOf {
Modifier Modifier
.background(backgroundColor) .background(backgroundColor)
.padding( .padding(
@@ -112,13 +117,14 @@ fun MultiSetCompose(multiSetCard: MultiSetCard, routeForLastRead: String, accoun
routeFor( routeFor(
baseNote, baseNote,
account.userProfile() account.userProfile()
)?.let { navController.navigate(it) } )?.let { nav(it) }
} }
}, },
onLongClick = { popupExpanded = true } onLongClick = { popupExpanded = true }
) )
.fillMaxWidth() .fillMaxWidth()
} }
}
val zapEvents = remember { multiSetCard.zapEvents } val zapEvents = remember { multiSetCard.zapEvents }
val boostEvents = remember { multiSetCard.boostEvents } val boostEvents = remember { multiSetCard.boostEvents }
@@ -126,15 +132,15 @@ fun MultiSetCompose(multiSetCard: MultiSetCard, routeForLastRead: String, accoun
Column(modifier = columnModifier) { Column(modifier = columnModifier) {
if (zapEvents.isNotEmpty()) { if (zapEvents.isNotEmpty()) {
RenderZapGallery(zapEvents, navController, account, accountViewModel) RenderZapGallery(zapEvents, backgroundColor, nav, account, accountViewModel)
} }
if (boostEvents.isNotEmpty()) { if (boostEvents.isNotEmpty()) {
RenderBoostGallery(boostEvents, navController, account, accountViewModel) RenderBoostGallery(boostEvents, backgroundColor, nav, account, accountViewModel)
} }
if (likeEvents.isNotEmpty()) { if (likeEvents.isNotEmpty()) {
RenderLikeGallery(likeEvents, navController, account, accountViewModel) RenderLikeGallery(likeEvents, backgroundColor, nav, account, accountViewModel)
} }
Row(Modifier.fillMaxWidth()) { Row(Modifier.fillMaxWidth()) {
@@ -147,7 +153,7 @@ fun MultiSetCompose(multiSetCard: MultiSetCard, routeForLastRead: String, accoun
isBoostedNote = true, isBoostedNote = true,
parentBackgroundColor = backgroundColor, parentBackgroundColor = backgroundColor,
accountViewModel = accountViewModel, accountViewModel = accountViewModel,
navController = navController nav = nav
) )
NoteDropDownMenu(note, popupExpanded, { popupExpanded = false }, accountViewModel) NoteDropDownMenu(note, popupExpanded, { popupExpanded = false }, accountViewModel)
@@ -158,8 +164,9 @@ fun MultiSetCompose(multiSetCard: MultiSetCard, routeForLastRead: String, accoun
@Composable @Composable
private fun RenderLikeGallery( private fun RenderLikeGallery(
likeEvents: List<Note>, likeEvents: ImmutableList<Note>,
navController: NavController, backgroundColor: Color,
nav: (String) -> Unit,
account: Account, account: Account,
accountViewModel: AccountViewModel accountViewModel: AccountViewModel
) { ) {
@@ -183,14 +190,15 @@ private fun RenderLikeGallery(
) )
} }
AuthorGallery(likeEvents, navController, account, accountViewModel) AuthorGallery(likeEvents, backgroundColor, nav, account, accountViewModel)
} }
} }
@Composable @Composable
private fun RenderZapGallery( private fun RenderZapGallery(
zapEvents: Map<Note, Note>, zapEvents: ImmutableMap<Note, Note>,
navController: NavController, backgroundColor: Color,
nav: (String) -> Unit,
account: Account, account: Account,
accountViewModel: AccountViewModel accountViewModel: AccountViewModel
) { ) {
@@ -214,18 +222,23 @@ private fun RenderZapGallery(
) )
} }
AuthorGalleryZaps(zapEvents, navController, account, accountViewModel) AuthorGalleryZaps(zapEvents, backgroundColor, nav, account, accountViewModel)
} }
} }
@Composable @Composable
private fun RenderBoostGallery( private fun RenderBoostGallery(
boostEvents: List<Note>, boostEvents: ImmutableList<Note>,
navController: NavController, backgroundColor: Color,
nav: (String) -> Unit,
account: Account, account: Account,
accountViewModel: AccountViewModel accountViewModel: AccountViewModel
) { ) {
Row(Modifier.fillMaxWidth()) { Row(
modifier = remember {
Modifier.fillMaxWidth()
}
) {
Box( Box(
modifier = remember { modifier = remember {
Modifier Modifier
@@ -245,14 +258,15 @@ private fun RenderBoostGallery(
) )
} }
AuthorGallery(boostEvents, navController, account, accountViewModel) AuthorGallery(boostEvents, backgroundColor, nav, account, accountViewModel)
} }
} }
@Composable @Composable
fun AuthorGalleryZaps( fun AuthorGalleryZaps(
authorNotes: Map<Note, Note>, authorNotes: ImmutableMap<Note, Note>,
navController: NavController, backgroundColor: Color,
nav: (String) -> Unit,
account: Account, account: Account,
accountViewModel: AccountViewModel accountViewModel: AccountViewModel
) { ) {
@@ -261,7 +275,7 @@ fun AuthorGalleryZaps(
Column(modifier = Modifier.padding(start = 10.dp)) { Column(modifier = Modifier.padding(start = 10.dp)) {
FlowRow() { FlowRow() {
authorNotes.forEach { authorNotes.forEach {
AuthorPictureAndComment(it.key, it.value, navController, accountState, accountViewModel) AuthorPictureAndComment(it.key, it.value, backgroundColor, nav, accountState, accountViewModel)
} }
} }
} }
@@ -271,7 +285,8 @@ fun AuthorGalleryZaps(
private fun AuthorPictureAndComment( private fun AuthorPictureAndComment(
zapRequest: Note, zapRequest: Note,
zapEvent: Note?, zapEvent: Note?,
navController: NavController, backgroundColor: Color,
nav: (String) -> Unit,
accountUser: State<UserState?>, accountUser: State<UserState?>,
accountViewModel: AccountViewModel accountViewModel: AccountViewModel
) { ) {
@@ -287,23 +302,39 @@ private fun AuthorPictureAndComment(
val amount = (zapEvent?.event as? LnZapEvent)?.amount val amount = (zapEvent?.event as? LnZapEvent)?.amount
if (decryptedContent != null) { if (decryptedContent != null) {
val newAuthor = LocalCache.getOrCreateUser(decryptedContent.pubKey) val newAuthor = LocalCache.getOrCreateUser(decryptedContent.pubKey)
content = Triple(newAuthor, decryptedContent.content, amount) content = Triple(newAuthor, decryptedContent.content.ifBlank { null }, amount)
} else { } else {
if (!zapRequest.event?.content().isNullOrBlank() || amount != null) { if (!zapRequest.event?.content().isNullOrBlank() || amount != null) {
content = Triple(author, zapRequest.event?.content(), amount) content = Triple(author, zapRequest.event?.content()?.ifBlank { null }, amount)
} }
} }
} }
} }
} }
val modifier = remember(content.second) {
if (!content.second.isNullOrBlank()) {
Modifier
.fillMaxWidth()
.clickable {
nav("User/${author.pubkeyHex}")
}
} else {
Modifier.clickable {
nav("User/${author.pubkeyHex}")
}
}
}
AuthorPictureAndComment( AuthorPictureAndComment(
author = content.first, author = content.first,
comment = content.second, comment = content.second,
amount = showAmountAxis(content.third), amount = showAmountAxis(content.third),
navController = navController, backgroundColor = backgroundColor,
nav = nav,
accountUser = accountUser, accountUser = accountUser,
accountViewModel = accountViewModel accountViewModel = accountViewModel,
modifier = modifier
) )
} }
@@ -312,24 +343,12 @@ private fun AuthorPictureAndComment(
author: User, author: User,
comment: String?, comment: String?,
amount: String?, amount: String?,
navController: NavController, backgroundColor: Color,
nav: (String) -> Unit,
accountUser: State<UserState?>, accountUser: State<UserState?>,
accountViewModel: AccountViewModel accountViewModel: AccountViewModel,
modifier: Modifier
) { ) {
val modifier = remember(comment) {
if (!comment.isNullOrBlank()) {
Modifier
.fillMaxWidth()
.clickable {
navController.navigate("User/${author.pubkeyHex}")
}
} else {
Modifier.clickable {
navController.navigate("User/${author.pubkeyHex}")
}
}
}
Row( Row(
modifier = modifier, modifier = modifier,
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
@@ -358,16 +377,16 @@ private fun AuthorPictureAndComment(
} }
} }
if (!comment.isNullOrBlank()) { comment?.let {
Spacer(modifier = Modifier.width(5.dp)) Spacer(modifier = Modifier.width(5.dp))
TranslatableRichTextViewer( TranslatableRichTextViewer(
content = comment, content = it,
canPreview = true, canPreview = true,
tags = null, tags = null,
modifier = Modifier.weight(1f), modifier = Modifier.weight(1f),
backgroundColor = MaterialTheme.colors.background, backgroundColor = backgroundColor,
accountViewModel = accountViewModel, accountViewModel = accountViewModel,
navController = navController nav = nav
) )
} }
} }
@@ -375,27 +394,26 @@ private fun AuthorPictureAndComment(
@Composable @Composable
fun AuthorGallery( fun AuthorGallery(
authorNotes: Collection<Note>, authorNotes: ImmutableList<Note>,
navController: NavController, backgroundColor: Color,
nav: (String) -> Unit,
account: Account, account: Account,
accountViewModel: AccountViewModel accountViewModel: AccountViewModel
) { ) {
val accountState = account.userProfile().live().follows.observeAsState() val accountState = account.userProfile().live().follows.observeAsState()
val listToRender = remember {
Pair(
authorNotes.take(50).mapNotNull { it.author },
authorNotes.size
)
}
Column(modifier = Modifier.padding(start = 10.dp)) { Column(modifier = Modifier.padding(start = 10.dp)) {
FlowRow() { FlowRow() {
listToRender.first.forEach { author -> authorNotes.forEach { note ->
AuthorPictureAndComment(author, null, null, navController, accountState, accountViewModel) note.author?.let { user ->
val modifier = remember {
Modifier.clickable {
nav("User/${user.pubkeyHex}")
}
} }
if (listToRender.second > 50) { AuthorPictureAndComment(user, null, null, backgroundColor, nav, accountState, accountViewModel, modifier)
Text(" and ${listToRender.second - 50} others") }
} }
} }
} }
@@ -77,7 +77,6 @@ import androidx.compose.ui.unit.sp
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.graphics.drawable.toBitmap import androidx.core.graphics.drawable.toBitmap
import androidx.core.graphics.get import androidx.core.graphics.get
import androidx.navigation.NavController
import coil.compose.AsyncImage import coil.compose.AsyncImage
import coil.compose.AsyncImagePainter import coil.compose.AsyncImagePainter
import com.google.accompanist.flowlayout.FlowRow import com.google.accompanist.flowlayout.FlowRow
@@ -153,7 +152,7 @@ fun NoteCompose(
addMarginTop: Boolean = true, addMarginTop: Boolean = true,
parentBackgroundColor: Color? = null, parentBackgroundColor: Color? = null,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
navController: NavController nav: (String) -> Unit
) { ) {
val accountState by accountViewModel.accountLiveData.observeAsState() val accountState by accountViewModel.accountLiveData.observeAsState()
val account = remember(accountState) { accountState?.account } ?: return val account = remember(accountState) { accountState?.account } ?: return
@@ -212,11 +211,11 @@ fun NoteCompose(
account.userProfile(), account.userProfile(),
modifier, modifier,
isBoostedNote, isBoostedNote,
navController, nav,
onClick = { showHiddenNote = true } onClick = { showHiddenNote = true }
) )
} else if ((noteEvent is ChannelCreateEvent || noteEvent is ChannelMetadataEvent) && baseChannel != null) { } else if ((noteEvent is ChannelCreateEvent || noteEvent is ChannelMetadataEvent) && baseChannel != null) {
ChannelHeader(baseChannel = baseChannel, account = account, navController = navController) ChannelHeader(baseChannel = baseChannel, account = account, nav = nav)
} else if (noteEvent is BadgeDefinitionEvent) { } else if (noteEvent is BadgeDefinitionEvent) {
BadgeDisplay(baseNote = note) BadgeDisplay(baseNote = note)
} else if (noteEvent is FileHeaderEvent) { } else if (noteEvent is FileHeaderEvent) {
@@ -267,11 +266,7 @@ fun NoteCompose(
onClick = { onClick = {
scope.launch { scope.launch {
routeFor(note, loggedIn)?.let { routeFor(note, loggedIn)?.let {
if (note.idHex != navController.currentBackStackEntry?.arguments?.getString("id")) { nav(it)
navController.navigate(it)
} else {
Log.d("Amethyst-Navigation", "Note already exists in the backstack!")
}
} }
} }
}, },
@@ -292,7 +287,7 @@ fun NoteCompose(
} }
) { ) {
if (!isBoostedNote && !isQuotedNote) { if (!isBoostedNote && !isQuotedNote) {
DrawAuthorImages(baseNote, loggedIn, navController) DrawAuthorImages(baseNote, loggedIn, nav)
} }
Column( Column(
@@ -306,14 +301,14 @@ fun NoteCompose(
showAuthorPicture = isQuotedNote, showAuthorPicture = isQuotedNote,
account = account, account = account,
accountViewModel = accountViewModel, accountViewModel = accountViewModel,
navController = navController nav = nav
) )
if (noteEvent !is RepostEvent && !makeItShort && !isQuotedNote) { if (noteEvent !is RepostEvent && !makeItShort && !isQuotedNote) {
SecondUserInfoRow( SecondUserInfoRow(
note, note,
account, account,
navController nav
) )
} }
@@ -326,17 +321,17 @@ fun NoteCompose(
backgroundColor, backgroundColor,
account, account,
accountViewModel, accountViewModel,
navController nav
) )
} }
when (noteEvent) { when (noteEvent) {
is ReactionEvent -> { is ReactionEvent -> {
RenderReaction(note, backgroundColor, accountViewModel, navController) RenderReaction(note, backgroundColor, accountViewModel, nav)
} }
is RepostEvent -> { is RepostEvent -> {
RenderRepost(note, backgroundColor, accountViewModel, navController) RenderRepost(note, backgroundColor, accountViewModel, nav)
} }
is ReportEvent -> { is ReportEvent -> {
@@ -344,31 +339,31 @@ fun NoteCompose(
} }
is LongTextNoteEvent -> { is LongTextNoteEvent -> {
RenderLongFormContent(note, loggedIn, accountViewModel, navController) RenderLongFormContent(note, loggedIn, accountViewModel, nav)
} }
is BadgeAwardEvent -> { is BadgeAwardEvent -> {
RenderBadgeAward(note, backgroundColor, accountViewModel, navController) RenderBadgeAward(note, backgroundColor, accountViewModel, nav)
} }
is PeopleListEvent -> { is PeopleListEvent -> {
RenderPeopleList(noteState, backgroundColor, accountViewModel, navController) RenderPeopleList(noteState, backgroundColor, accountViewModel, nav)
} }
is AudioTrackEvent -> { is AudioTrackEvent -> {
RenderAudioTrack(note, loggedIn, accountViewModel, navController) RenderAudioTrack(note, loggedIn, accountViewModel, nav)
} }
is PinListEvent -> { is PinListEvent -> {
RenderPinListEvent(noteState, backgroundColor, accountViewModel, navController) RenderPinListEvent(noteState, backgroundColor, accountViewModel, nav)
} }
is PrivateDmEvent -> { is PrivateDmEvent -> {
RenderPrivateMessage(note, makeItShort, isAcceptableAndCanPreview.second, backgroundColor, accountViewModel, navController) RenderPrivateMessage(note, makeItShort, isAcceptableAndCanPreview.second, backgroundColor, accountViewModel, nav)
} }
is HighlightEvent -> { is HighlightEvent -> {
RenderHighlight(note, makeItShort, isAcceptableAndCanPreview.second, backgroundColor, accountViewModel, navController) RenderHighlight(note, makeItShort, isAcceptableAndCanPreview.second, backgroundColor, accountViewModel, nav)
} }
is PollNoteEvent -> { is PollNoteEvent -> {
@@ -378,7 +373,7 @@ fun NoteCompose(
isAcceptableAndCanPreview.second, isAcceptableAndCanPreview.second,
backgroundColor, backgroundColor,
accountViewModel, accountViewModel,
navController nav
) )
} }
@@ -389,7 +384,7 @@ fun NoteCompose(
isAcceptableAndCanPreview.second, isAcceptableAndCanPreview.second,
backgroundColor, backgroundColor,
accountViewModel, accountViewModel,
navController nav
) )
} }
} }
@@ -438,7 +433,7 @@ private fun RenderTextEvent(
canPreview: Boolean, canPreview: Boolean,
backgroundColor: Color, backgroundColor: Color,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
navController: NavController nav: (String) -> Unit
) { ) {
val tags = remember(note.event?.id()) { note.event?.tags() } val tags = remember(note.event?.id()) { note.event?.tags() }
val hashtags = remember(note.event?.id()) { note.event?.hashtags() ?: emptyList() } val hashtags = remember(note.event?.id()) { note.event?.hashtags() ?: emptyList() }
@@ -462,15 +457,15 @@ private fun RenderTextEvent(
tags = tags, tags = tags,
backgroundColor = backgroundColor, backgroundColor = backgroundColor,
accountViewModel = accountViewModel, accountViewModel = accountViewModel,
navController = navController nav = nav
) )
DisplayUncitedHashtags(hashtags, eventContent, navController) DisplayUncitedHashtags(hashtags, eventContent, nav)
} }
} }
if (!makeItShort) { if (!makeItShort) {
ReactionsRow(note, accountViewModel, navController) ReactionsRow(note, accountViewModel, nav)
} }
Divider( Divider(
@@ -486,7 +481,7 @@ private fun RenderPoll(
canPreview: Boolean, canPreview: Boolean,
backgroundColor: Color, backgroundColor: Color,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
navController: NavController nav: (String) -> Unit
) { ) {
val noteEvent = note.event as? PollNoteEvent ?: return val noteEvent = note.event as? PollNoteEvent ?: return
val eventContent = noteEvent.content() val eventContent = noteEvent.content()
@@ -506,22 +501,22 @@ private fun RenderPoll(
noteEvent.tags(), noteEvent.tags(),
backgroundColor, backgroundColor,
accountViewModel, accountViewModel,
navController nav
) )
DisplayUncitedHashtags(noteEvent.hashtags(), eventContent, navController) DisplayUncitedHashtags(noteEvent.hashtags(), eventContent, nav)
PollNote( PollNote(
note, note,
canPreview = canPreview && !makeItShort, canPreview = canPreview && !makeItShort,
backgroundColor, backgroundColor,
accountViewModel, accountViewModel,
navController nav
) )
} }
if (!makeItShort) { if (!makeItShort) {
ReactionsRow(note, accountViewModel, navController) ReactionsRow(note, accountViewModel, nav)
} }
Divider( Divider(
@@ -537,7 +532,7 @@ private fun RenderHighlight(
canPreview: Boolean, canPreview: Boolean,
backgroundColor: Color, backgroundColor: Color,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
navController: NavController nav: (String) -> Unit
) { ) {
val noteEvent = note.event as? HighlightEvent ?: return val noteEvent = note.event as? HighlightEvent ?: return
@@ -549,11 +544,11 @@ private fun RenderHighlight(
canPreview, canPreview,
backgroundColor, backgroundColor,
accountViewModel, accountViewModel,
navController nav
) )
if (!makeItShort) { if (!makeItShort) {
ReactionsRow(note, accountViewModel, navController) ReactionsRow(note, accountViewModel, nav)
} }
Divider( Divider(
@@ -569,7 +564,7 @@ private fun RenderPrivateMessage(
canPreview: Boolean, canPreview: Boolean,
backgroundColor: Color, backgroundColor: Color,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
navController: NavController nav: (String) -> Unit
) { ) {
val noteEvent = note.event as? PrivateDmEvent ?: return val noteEvent = note.event as? PrivateDmEvent ?: return
val withMe = remember { noteEvent.with(accountViewModel.userProfile().pubkeyHex) } val withMe = remember { noteEvent.with(accountViewModel.userProfile().pubkeyHex) }
@@ -593,10 +588,10 @@ private fun RenderPrivateMessage(
tags = noteEvent.tags(), tags = noteEvent.tags(),
backgroundColor = backgroundColor, backgroundColor = backgroundColor,
accountViewModel = accountViewModel, accountViewModel = accountViewModel,
navController = navController nav = nav
) )
DisplayUncitedHashtags(noteEvent.hashtags(), eventContent, navController) DisplayUncitedHashtags(noteEvent.hashtags(), eventContent, nav)
} }
} }
} else { } else {
@@ -613,12 +608,12 @@ private fun RenderPrivateMessage(
noteEvent.tags(), noteEvent.tags(),
backgroundColor, backgroundColor,
accountViewModel, accountViewModel,
navController nav
) )
} }
if (!makeItShort) { if (!makeItShort) {
ReactionsRow(note, accountViewModel, navController) ReactionsRow(note, accountViewModel, nav)
} }
Divider( Divider(
@@ -632,12 +627,12 @@ fun RenderPeopleList(
noteState: NoteState?, noteState: NoteState?,
backgroundColor: Color, backgroundColor: Color,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
navController: NavController nav: (String) -> Unit
) { ) {
DisplayPeopleList(noteState, backgroundColor, accountViewModel, navController) DisplayPeopleList(noteState, backgroundColor, accountViewModel, nav)
noteState?.note?.let { noteState?.note?.let {
ReactionsRow(it, accountViewModel, navController) ReactionsRow(it, accountViewModel, nav)
} }
Divider( Divider(
@@ -651,7 +646,7 @@ fun DisplayPeopleList(
noteState: NoteState?, noteState: NoteState?,
backgroundColor: Color, backgroundColor: Color,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
navController: NavController nav: (String) -> Unit
) { ) {
val note = remember(noteState) { noteState?.note } ?: return val note = remember(noteState) { noteState?.note } ?: return
val noteEvent = note.event as? PeopleListEvent ?: return val noteEvent = note.event as? PeopleListEvent ?: return
@@ -696,7 +691,7 @@ fun DisplayPeopleList(
user, user,
overallModifier = Modifier, overallModifier = Modifier,
accountViewModel = accountViewModel, accountViewModel = accountViewModel,
navController = navController nav = nav
) )
} }
} }
@@ -740,7 +735,7 @@ private fun RenderBadgeAward(
note: Note, note: Note,
backgroundColor: Color, backgroundColor: Color,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
navController: NavController nav: (String) -> Unit
) { ) {
if (note.replyTo.isNullOrEmpty()) return if (note.replyTo.isNullOrEmpty()) return
@@ -765,7 +760,7 @@ private fun RenderBadgeAward(
modifier = Modifier modifier = Modifier
.size(size = 35.dp) .size(size = 35.dp)
.clickable { .clickable {
navController.navigate("User/${user.pubkeyHex}") nav("User/${user.pubkeyHex}")
}, },
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
@@ -791,11 +786,11 @@ private fun RenderBadgeAward(
unPackReply = false, unPackReply = false,
parentBackgroundColor = backgroundColor, parentBackgroundColor = backgroundColor,
accountViewModel = accountViewModel, accountViewModel = accountViewModel,
navController = navController nav = nav
) )
} }
ReactionsRow(note, accountViewModel, navController) ReactionsRow(note, accountViewModel, nav)
Divider( Divider(
modifier = Modifier.padding(top = 10.dp), modifier = Modifier.padding(top = 10.dp),
@@ -808,7 +803,7 @@ private fun RenderReaction(
note: Note, note: Note,
backgroundColor: Color, backgroundColor: Color,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
navController: NavController nav: (String) -> Unit
) { ) {
note.replyTo?.lastOrNull()?.let { note.replyTo?.lastOrNull()?.let {
NoteCompose( NoteCompose(
@@ -818,7 +813,7 @@ private fun RenderReaction(
unPackReply = false, unPackReply = false,
parentBackgroundColor = backgroundColor, parentBackgroundColor = backgroundColor,
accountViewModel = accountViewModel, accountViewModel = accountViewModel,
navController = navController nav = nav
) )
} }
@@ -836,7 +831,7 @@ private fun RenderRepost(
note: Note, note: Note,
backgroundColor: Color, backgroundColor: Color,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
navController: NavController nav: (String) -> Unit
) { ) {
val boostedNote = remember { val boostedNote = remember {
note.replyTo?.lastOrNull() note.replyTo?.lastOrNull()
@@ -850,7 +845,7 @@ private fun RenderRepost(
unPackReply = false, unPackReply = false,
parentBackgroundColor = backgroundColor, parentBackgroundColor = backgroundColor,
accountViewModel = accountViewModel, accountViewModel = accountViewModel,
navController = navController nav = nav
) )
} }
} }
@@ -860,13 +855,13 @@ private fun RenderPinListEvent(
noteState: NoteState?, noteState: NoteState?,
backgroundColor: Color, backgroundColor: Color,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
navController: NavController nav: (String) -> Unit
) { ) {
val noteEvent = noteState?.note?.event as? PinListEvent ?: return val noteEvent = noteState?.note?.event as? PinListEvent ?: return
PinListHeader(noteState, backgroundColor, accountViewModel, navController) PinListHeader(noteState, backgroundColor, accountViewModel, nav)
ReactionsRow(noteState?.note, accountViewModel, navController) ReactionsRow(noteState?.note, accountViewModel, nav)
Divider( Divider(
modifier = Modifier.padding(top = 10.dp), modifier = Modifier.padding(top = 10.dp),
@@ -879,7 +874,7 @@ fun PinListHeader(
noteState: NoteState?, noteState: NoteState?,
backgroundColor: Color, backgroundColor: Color,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
navController: NavController nav: (String) -> Unit
) { ) {
val note = remember(noteState) { noteState?.note } ?: return val note = remember(noteState) { noteState?.note } ?: return
val noteEvent = note.event as? PinListEvent ?: return val noteEvent = note.event as? PinListEvent ?: return
@@ -926,7 +921,7 @@ fun PinListHeader(
tags = emptyList(), tags = emptyList(),
backgroundColor = backgroundColor, backgroundColor = backgroundColor,
accountViewModel = accountViewModel, accountViewModel = accountViewModel,
navController = navController nav = nav
) )
} }
} }
@@ -970,13 +965,13 @@ private fun RenderAudioTrack(
note: Note, note: Note,
loggedIn: User, loggedIn: User,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
navController: NavController nav: (String) -> Unit
) { ) {
val noteEvent = note.event as? AudioTrackEvent ?: return val noteEvent = note.event as? AudioTrackEvent ?: return
AudioTrackHeader(noteEvent, note, loggedIn, navController) AudioTrackHeader(noteEvent, note, loggedIn, nav)
ReactionsRow(note, accountViewModel, navController) ReactionsRow(note, accountViewModel, nav)
Divider( Divider(
modifier = Modifier.padding(top = 10.dp), modifier = Modifier.padding(top = 10.dp),
@@ -989,13 +984,13 @@ private fun RenderLongFormContent(
note: Note, note: Note,
loggedIn: User, loggedIn: User,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
navController: NavController nav: (String) -> Unit
) { ) {
val noteEvent = note.event as? LongTextNoteEvent ?: return val noteEvent = note.event as? LongTextNoteEvent ?: return
LongFormHeader(noteEvent, note, loggedIn) LongFormHeader(noteEvent, note, loggedIn)
ReactionsRow(note, accountViewModel, navController) ReactionsRow(note, accountViewModel, nav)
Divider( Divider(
modifier = Modifier.padding(top = 10.dp), modifier = Modifier.padding(top = 10.dp),
@@ -1042,7 +1037,7 @@ private fun ReplyRow(
backgroundColor: Color, backgroundColor: Color,
account: Account, account: Account,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
navController: NavController nav: (String) -> Unit
) { ) {
val noteEvent = note.event val noteEvent = note.event
@@ -1068,16 +1063,16 @@ private fun ReplyRow(
parentBackgroundColor = MaterialTheme.colors.onSurface.copy(alpha = 0.05f) parentBackgroundColor = MaterialTheme.colors.onSurface.copy(alpha = 0.05f)
.compositeOver(backgroundColor), .compositeOver(backgroundColor),
accountViewModel = accountViewModel, accountViewModel = accountViewModel,
navController = navController nav = nav
) )
} else { } else {
ReplyInformation(note.replyTo, noteEvent.mentions(), account, navController) ReplyInformation(note.replyTo, noteEvent.mentions(), account, nav)
} }
Spacer(modifier = Modifier.height(5.dp)) Spacer(modifier = Modifier.height(5.dp))
} else if (noteEvent is ChannelMessageEvent && (note.replyTo != null || noteEvent.hasAnyTaggedUser())) { } else if (noteEvent is ChannelMessageEvent && (note.replyTo != null || noteEvent.hasAnyTaggedUser())) {
note.channel()?.let { note.channel()?.let {
ReplyInformationChannel(note.replyTo, noteEvent.mentions(), it, account, navController) ReplyInformationChannel(note.replyTo, noteEvent.mentions(), it, account, nav)
} }
Spacer(modifier = Modifier.height(5.dp)) Spacer(modifier = Modifier.height(5.dp))
@@ -1088,7 +1083,7 @@ private fun ReplyRow(
private fun SecondUserInfoRow( private fun SecondUserInfoRow(
note: Note, note: Note,
account: Account, account: Account,
navController: NavController nav: (String) -> Unit
) { ) {
val noteEvent = remember { note.event } ?: return val noteEvent = remember { note.event } ?: return
val noteAuthor = remember { note.author } ?: return val noteAuthor = remember { note.author } ?: return
@@ -1098,7 +1093,7 @@ private fun SecondUserInfoRow(
val baseReward = remember { noteEvent.getReward() } val baseReward = remember { noteEvent.getReward() }
if (baseReward != null) { if (baseReward != null) {
DisplayReward(baseReward, note, account, navController) DisplayReward(baseReward, note, account, nav)
} }
val pow = remember { noteEvent.getPoWRank() } val pow = remember { noteEvent.getPoWRank() }
@@ -1114,7 +1109,7 @@ private fun FirstUserInfoRow(
showAuthorPicture: Boolean, showAuthorPicture: Boolean,
account: Account, account: Account,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
navController: NavController nav: (String) -> Unit
) { ) {
var moreActionsExpanded by remember { mutableStateOf(false) } var moreActionsExpanded by remember { mutableStateOf(false) }
val eventNote = remember { baseNote.event } ?: return val eventNote = remember { baseNote.event } ?: return
@@ -1126,7 +1121,7 @@ private fun FirstUserInfoRow(
Row(verticalAlignment = Alignment.CenterVertically) { Row(verticalAlignment = Alignment.CenterVertically) {
if (showAuthorPicture) { if (showAuthorPicture) {
NoteAuthorPicture(baseNote, navController, loggedIn, 25.dp) NoteAuthorPicture(baseNote, nav, loggedIn, 25.dp)
Spacer(padding) Spacer(padding)
NoteUsernameDisplay(baseNote, Modifier.weight(1f)) NoteUsernameDisplay(baseNote, Modifier.weight(1f))
} else { } else {
@@ -1140,7 +1135,7 @@ private fun FirstUserInfoRow(
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f) color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
) )
} else { } else {
DisplayFollowingHashtagsInPost(eventNote, account, navController) DisplayFollowingHashtagsInPost(eventNote, account, nav)
} }
TimeAgo(time) TimeAgo(time)
@@ -1188,17 +1183,17 @@ fun TimeAgo(time: Long) {
} }
@Composable @Composable
private fun DrawAuthorImages(baseNote: Note, loggedIn: User, navController: NavController) { private fun DrawAuthorImages(baseNote: Note, loggedIn: User, nav: (String) -> Unit) {
val baseChannel = remember { baseNote.channel() } val baseChannel = remember { baseNote.channel() }
val modifier = remember { Modifier.width(55.dp) } val modifier = remember { Modifier.width(55.dp) }
Column(modifier) { Column(modifier) {
// Draws the boosted picture outside the boosted card. // Draws the boosted picture outside the boosted card.
Box(modifier = modifier, contentAlignment = Alignment.BottomEnd) { Box(modifier = modifier, contentAlignment = Alignment.BottomEnd) {
NoteAuthorPicture(baseNote, navController, loggedIn, 55.dp) NoteAuthorPicture(baseNote, nav, loggedIn, 55.dp)
if (baseNote.event is RepostEvent) { if (baseNote.event is RepostEvent) {
RepostNoteAuthorPicture(baseNote, navController, loggedIn) RepostNoteAuthorPicture(baseNote, nav, loggedIn)
} }
if (baseNote.event is ChannelMessageEvent && baseChannel != null) { if (baseNote.event is ChannelMessageEvent && baseChannel != null) {
@@ -1262,7 +1257,7 @@ private fun ChannelNotePicture(baseChannel: Channel) {
@Composable @Composable
private fun RepostNoteAuthorPicture( private fun RepostNoteAuthorPicture(
baseNote: Note, baseNote: Note,
navController: NavController, nav: (String) -> Unit,
loggedIn: User loggedIn: User
) { ) {
val baseRepost = remember { baseNote.replyTo?.lastOrNull() } val baseRepost = remember { baseNote.replyTo?.lastOrNull() }
@@ -1277,7 +1272,7 @@ private fun RepostNoteAuthorPicture(
Box(modifier) { Box(modifier) {
NoteAuthorPicture( NoteAuthorPicture(
it, it,
navController, nav,
loggedIn, loggedIn,
35.dp, 35.dp,
pictureModifier = Modifier.border( pictureModifier = Modifier.border(
@@ -1299,7 +1294,7 @@ fun DisplayHighlight(
canPreview: Boolean, canPreview: Boolean,
backgroundColor: Color, backgroundColor: Color,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
navController: NavController nav: (String) -> Unit
) { ) {
val quote = val quote =
remember { remember {
@@ -1316,7 +1311,7 @@ fun DisplayHighlight(
emptyList(), emptyList(),
backgroundColor, backgroundColor,
accountViewModel, accountViewModel,
navController nav
) )
var userBase by remember { mutableStateOf<User?>(null) } var userBase by remember { mutableStateOf<User?>(null) }
@@ -1343,7 +1338,7 @@ fun DisplayHighlight(
suffix = " ", suffix = " ",
tags = userTags, tags = userTags,
route = route, route = route,
navController = navController nav = nav
) )
} }
} }
@@ -1371,7 +1366,7 @@ fun DisplayHighlight(
fun DisplayFollowingHashtagsInPost( fun DisplayFollowingHashtagsInPost(
noteEvent: EventInterface, noteEvent: EventInterface,
account: Account, account: Account,
navController: NavController nav: (String) -> Unit
) { ) {
var firstTag by remember { mutableStateOf<String?>(null) } var firstTag by remember { mutableStateOf<String?>(null) }
@@ -1386,7 +1381,7 @@ fun DisplayFollowingHashtagsInPost(
firstTag?.let { firstTag?.let {
ClickableText( ClickableText(
text = AnnotatedString(" #$firstTag"), text = AnnotatedString(" #$firstTag"),
onClick = { navController.navigate("Hashtag/$firstTag") }, onClick = { nav("Hashtag/$firstTag") },
style = LocalTextStyle.current.copy( style = LocalTextStyle.current.copy(
color = MaterialTheme.colors.primary.copy( color = MaterialTheme.colors.primary.copy(
alpha = 0.52f alpha = 0.52f
@@ -1402,7 +1397,7 @@ fun DisplayFollowingHashtagsInPost(
fun DisplayUncitedHashtags( fun DisplayUncitedHashtags(
hashtags: List<String>, hashtags: List<String>,
eventContent: String, eventContent: String,
navController: NavController nav: (String) -> Unit
) { ) {
if (hashtags.isNotEmpty()) { if (hashtags.isNotEmpty()) {
FlowRow( FlowRow(
@@ -1412,7 +1407,7 @@ fun DisplayUncitedHashtags(
if (!eventContent.contains(hashtag, true)) { if (!eventContent.contains(hashtag, true)) {
ClickableText( ClickableText(
text = AnnotatedString("#$hashtag "), text = AnnotatedString("#$hashtag "),
onClick = { navController.navigate("Hashtag/$hashtag") }, onClick = { nav("Hashtag/$hashtag") },
style = LocalTextStyle.current.copy( style = LocalTextStyle.current.copy(
color = MaterialTheme.colors.primary.copy( color = MaterialTheme.colors.primary.copy(
alpha = 0.52f alpha = 0.52f
@@ -1444,7 +1439,7 @@ fun DisplayReward(
baseReward: BigDecimal, baseReward: BigDecimal,
baseNote: Note, baseNote: Note,
account: Account, account: Account,
navController: NavController nav: (String) -> Unit
) { ) {
var popupExpanded by remember { mutableStateOf(false) } var popupExpanded by remember { mutableStateOf(false) }
@@ -1455,7 +1450,7 @@ fun DisplayReward(
) { ) {
ClickableText( ClickableText(
text = AnnotatedString("#bounty"), text = AnnotatedString("#bounty"),
onClick = { navController.navigate("Hashtag/bounty") }, onClick = { nav("Hashtag/bounty") },
style = LocalTextStyle.current.copy( style = LocalTextStyle.current.copy(
color = MaterialTheme.colors.primary.copy( color = MaterialTheme.colors.primary.copy(
alpha = 0.52f alpha = 0.52f
@@ -1676,7 +1671,7 @@ fun FileStorageHeaderDisplay(baseNote: Note) {
} }
@Composable @Composable
fun AudioTrackHeader(noteEvent: AudioTrackEvent, note: Note, loggedIn: User, navController: NavController) { fun AudioTrackHeader(noteEvent: AudioTrackEvent, note: Note, loggedIn: User, nav: (String) -> Unit) {
val media = remember { noteEvent.media() } val media = remember { noteEvent.media() }
val cover = remember { noteEvent.cover() } val cover = remember { noteEvent.cover() }
val subject = remember { noteEvent.subject() } val subject = remember { noteEvent.subject() }
@@ -1713,7 +1708,7 @@ fun AudioTrackHeader(noteEvent: AudioTrackEvent, note: Note, loggedIn: User, nav
modifier = Modifier modifier = Modifier
.padding(top = 5.dp, start = 10.dp, end = 10.dp) .padding(top = 5.dp, start = 10.dp, end = 10.dp)
.clickable { .clickable {
navController.navigate("User/${it.second.pubkeyHex}") nav("User/${it.second.pubkeyHex}")
} }
) { ) {
UserPicture(it.second, loggedIn, 25.dp) UserPicture(it.second, loggedIn, 25.dp)
@@ -1962,17 +1957,13 @@ private fun ShowMoreRelaysButton(onClick: () -> Unit) {
@Composable @Composable
fun NoteAuthorPicture( fun NoteAuthorPicture(
baseNote: Note, baseNote: Note,
navController: NavController, nav: (String) -> Unit,
userAccount: User, userAccount: User,
size: Dp, size: Dp,
pictureModifier: Modifier = Modifier pictureModifier: Modifier = Modifier
) { ) {
NoteAuthorPicture(baseNote, userAccount, size, pictureModifier) { NoteAuthorPicture(baseNote, userAccount, size, pictureModifier) {
if (it.pubkeyHex != navController.currentBackStackEntry?.arguments?.getString("id")) { nav("User/${it.pubkeyHex}")
navController.navigate("User/${it.pubkeyHex}")
} else {
Log.d("Amethyst-Navigation", "Profile destination already exists in the backstack!")
}
} }
} }
@@ -2019,17 +2010,13 @@ fun NoteAuthorPicture(
@Composable @Composable
fun UserPicture( fun UserPicture(
user: User, user: User,
navController: NavController, nav: (String) -> Unit,
userAccount: User, userAccount: User,
size: Dp, size: Dp,
pictureModifier: Modifier = Modifier pictureModifier: Modifier = Modifier
) { ) {
UserPicture(user, userAccount, size, pictureModifier) { UserPicture(user, userAccount, size, pictureModifier) {
if (it.pubkeyHex != navController.currentBackStackEntry?.arguments?.getString("id")) { nav("User/${it.pubkeyHex}")
navController.navigate("User/${it.pubkeyHex}")
} else {
Log.d("Amethyst-Navigation", "Profile destination already exists in the backstack!")
}
} }
} }
@@ -29,7 +29,6 @@ import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties import androidx.compose.ui.window.DialogProperties
import androidx.compose.ui.window.Popup import androidx.compose.ui.window.Popup
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer
@@ -47,7 +46,7 @@ fun PollNote(
canPreview: Boolean, canPreview: Boolean,
backgroundColor: Color, backgroundColor: Color,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
navController: NavController nav: (String) -> Unit
) { ) {
val accountState by accountViewModel.accountLiveData.observeAsState() val accountState by accountViewModel.accountLiveData.observeAsState()
val account = remember(accountState) { accountState?.account } ?: return val account = remember(accountState) { accountState?.account } ?: return
@@ -64,7 +63,7 @@ fun PollNote(
canPreview = canPreview, canPreview = canPreview,
backgroundColor = backgroundColor, backgroundColor = backgroundColor,
accountViewModel = accountViewModel, accountViewModel = accountViewModel,
navController = navController nav = nav
) )
} }
@@ -75,7 +74,7 @@ fun PollNote(
canPreview: Boolean, canPreview: Boolean,
backgroundColor: Color, backgroundColor: Color,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
navController: NavController nav: (String) -> Unit
) { ) {
val zapsState by baseNote.live().zaps.observeAsState() val zapsState by baseNote.live().zaps.observeAsState()
@@ -93,7 +92,7 @@ fun PollNote(
accountViewModel, accountViewModel,
canPreview, canPreview,
backgroundColor, backgroundColor,
navController nav
) )
} }
} }
@@ -106,7 +105,7 @@ private fun OptionNote(
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
canPreview: Boolean, canPreview: Boolean,
backgroundColor: Color, backgroundColor: Color,
navController: NavController nav: (String) -> Unit
) { ) {
Row( Row(
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
@@ -168,7 +167,7 @@ private fun OptionNote(
pollViewModel.pollEvent?.tags(), pollViewModel.pollEvent?.tags(),
backgroundColor, backgroundColor,
accountViewModel, accountViewModel,
navController nav
) )
} }
} }
@@ -202,7 +201,7 @@ private fun OptionNote(
pollViewModel.pollEvent?.tags(), pollViewModel.pollEvent?.tags(),
backgroundColor, backgroundColor,
accountViewModel, accountViewModel,
navController nav
) )
} }
} }
@@ -50,7 +50,6 @@ import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.compose.ui.window.Popup import androidx.compose.ui.window.Popup
import androidx.navigation.NavController
import coil.compose.AsyncImage import coil.compose.AsyncImage
import coil.request.CachePolicy import coil.request.CachePolicy
import coil.request.ImageRequest import coil.request.ImageRequest
@@ -66,7 +65,7 @@ import java.math.RoundingMode
import kotlin.math.roundToInt import kotlin.math.roundToInt
@Composable @Composable
fun ReactionsRow(baseNote: Note, accountViewModel: AccountViewModel, navController: NavController) { fun ReactionsRow(baseNote: Note, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
val accountState by accountViewModel.accountLiveData.observeAsState() val accountState by accountViewModel.accountLiveData.observeAsState()
val account = remember(accountState) { accountState?.account } ?: return val account = remember(accountState) { accountState?.account } ?: return
@@ -81,11 +80,11 @@ fun ReactionsRow(baseNote: Note, accountViewModel: AccountViewModel, navControll
} }
if (wantsToReplyTo != null) { if (wantsToReplyTo != null) {
NewPostView({ wantsToReplyTo = null }, wantsToReplyTo, null, account, accountViewModel, navController) NewPostView({ wantsToReplyTo = null }, wantsToReplyTo, null, account, accountViewModel, nav)
} }
if (wantsToQuote != null) { if (wantsToQuote != null) {
NewPostView({ wantsToQuote = null }, null, wantsToQuote, account, accountViewModel, navController) NewPostView({ wantsToQuote = null }, null, wantsToQuote, account, accountViewModel, nav)
} }
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
@@ -14,7 +14,6 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.navigation.NavController
import com.google.accompanist.flowlayout.FlowRow import com.google.accompanist.flowlayout.FlowRow
import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.* import com.vitorpamplona.amethyst.model.*
@@ -23,7 +22,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@Composable @Composable
fun ReplyInformation(replyTo: List<Note>?, mentions: List<String>, account: Account, navController: NavController) { fun ReplyInformation(replyTo: List<Note>?, mentions: List<String>, account: Account, nav: (String) -> Unit) {
var dupMentions by remember { mutableStateOf<List<User>?>(null) } var dupMentions by remember { mutableStateOf<List<User>?>(null) }
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
@@ -34,7 +33,7 @@ fun ReplyInformation(replyTo: List<Note>?, mentions: List<String>, account: Acco
if (dupMentions != null) { if (dupMentions != null) {
ReplyInformation(replyTo, dupMentions, account) { ReplyInformation(replyTo, dupMentions, account) {
navController.navigate("User/${it.pubkeyHex}") nav("User/${it.pubkeyHex}")
} }
} }
} }
@@ -116,7 +115,7 @@ fun ReplyInformation(replyTo: List<Note>?, dupMentions: List<User>?, account: Ac
} }
@Composable @Composable
fun ReplyInformationChannel(replyTo: List<Note>?, mentions: List<String>, channel: Channel, account: Account, navController: NavController) { fun ReplyInformationChannel(replyTo: List<Note>?, mentions: List<String>, channel: Channel, account: Account, nav: (String) -> Unit) {
var sortedMentions by remember { mutableStateOf<List<User>?>(null) } var sortedMentions by remember { mutableStateOf<List<User>?>(null) }
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
@@ -134,26 +133,26 @@ fun ReplyInformationChannel(replyTo: List<Note>?, mentions: List<String>, channe
sortedMentions, sortedMentions,
channel, channel,
onUserTagClick = { onUserTagClick = {
navController.navigate("User/${it.pubkeyHex}") nav("User/${it.pubkeyHex}")
}, },
onChannelTagClick = { onChannelTagClick = {
navController.navigate("Channel/${it.idHex}") nav("Channel/${it.idHex}")
} }
) )
} }
} }
@Composable @Composable
fun ReplyInformationChannel(replyTo: List<Note>?, mentions: List<User>?, channel: Channel, navController: NavController) { fun ReplyInformationChannel(replyTo: List<Note>?, mentions: List<User>?, channel: Channel, nav: (String) -> Unit) {
ReplyInformationChannel( ReplyInformationChannel(
replyTo, replyTo,
mentions, mentions,
channel, channel,
onUserTagClick = { onUserTagClick = {
navController.navigate("User/${it.pubkeyHex}") nav("User/${it.pubkeyHex}")
}, },
onChannelTagClick = { onChannelTagClick = {
navController.navigate("Channel/${it.idHex}") nav("Channel/${it.idHex}")
} }
) )
} }
@@ -15,7 +15,6 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.FollowButton import com.vitorpamplona.amethyst.ui.screen.loggedIn.FollowButton
@@ -34,7 +33,7 @@ fun UserCompose(
top = 10.dp top = 10.dp
), ),
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
navController: NavController nav: (String) -> Unit
) { ) {
val accountState by accountViewModel.accountLiveData.observeAsState() val accountState by accountViewModel.accountLiveData.observeAsState()
val account = accountState?.account ?: return val account = accountState?.account ?: return
@@ -47,14 +46,14 @@ fun UserCompose(
Column( Column(
modifier = modifier =
Modifier.clickable( Modifier.clickable(
onClick = { navController.navigate("User/${baseUser.pubkeyHex}") } onClick = { nav("User/${baseUser.pubkeyHex}") }
) )
) { ) {
Row( Row(
modifier = overallModifier, modifier = overallModifier,
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
UserPicture(baseUser, navController, account.userProfile(), 55.dp) UserPicture(baseUser, nav, account.userProfile(), 55.dp)
Column(modifier = Modifier.padding(start = 10.dp).weight(1f)) { Column(modifier = Modifier.padding(start = 10.dp).weight(1f)) {
Row(verticalAlignment = Alignment.CenterVertically) { Row(verticalAlignment = Alignment.CenterVertically) {
@@ -26,7 +26,6 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import androidx.navigation.NavController
import com.patrykandpatrick.vico.core.chart.composed.ComposedChartEntryModel import com.patrykandpatrick.vico.core.chart.composed.ComposedChartEntryModel
import com.patrykandpatrick.vico.core.entry.ChartEntryModel import com.patrykandpatrick.vico.core.entry.ChartEntryModel
import com.patrykandpatrick.vico.core.entry.ChartEntryModelProducer import com.patrykandpatrick.vico.core.entry.ChartEntryModelProducer
@@ -54,7 +53,7 @@ import java.time.ZoneId
import java.time.format.DateTimeFormatter import java.time.format.DateTimeFormatter
@Composable @Composable
fun UserReactionsRow(model: UserReactionsViewModel, accountViewModel: AccountViewModel, navController: NavController, onClick: () -> Unit) { fun UserReactionsRow(model: UserReactionsViewModel, accountViewModel: AccountViewModel, nav: (String) -> Unit, onClick: () -> Unit) {
Row( Row(
verticalAlignment = CenterVertically, verticalAlignment = CenterVertically,
modifier = Modifier modifier = Modifier
@@ -23,7 +23,6 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.navigation.NavController
import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.service.model.LnZapEvent import com.vitorpamplona.amethyst.service.model.LnZapEvent
@@ -37,7 +36,7 @@ import kotlinx.coroutines.launch
import java.math.BigDecimal import java.math.BigDecimal
@Composable @Composable
fun ZapNoteCompose(baseNote: Pair<Note, Note>, accountViewModel: AccountViewModel, navController: NavController) { fun ZapNoteCompose(baseNote: Pair<Note, Note>, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
val accountState by accountViewModel.accountLiveData.observeAsState() val accountState by accountViewModel.accountLiveData.observeAsState()
val account = accountState?.account ?: return val account = accountState?.account ?: return
@@ -60,7 +59,7 @@ fun ZapNoteCompose(baseNote: Pair<Note, Note>, accountViewModel: AccountViewMode
Column( Column(
modifier = modifier =
Modifier.clickable( Modifier.clickable(
onClick = { navController.navigate("User/${baseAuthor.pubkeyHex}") } onClick = { nav("User/${baseAuthor.pubkeyHex}") }
), ),
verticalArrangement = Arrangement.Center verticalArrangement = Arrangement.Center
) { ) {
@@ -73,7 +72,7 @@ fun ZapNoteCompose(baseNote: Pair<Note, Note>, accountViewModel: AccountViewMode
), ),
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
UserPicture(baseAuthor, navController, account.userProfile(), 55.dp) UserPicture(baseAuthor, nav, account.userProfile(), 55.dp)
Column(modifier = Modifier.padding(start = 10.dp).weight(1f)) { Column(modifier = Modifier.padding(start = 10.dp).weight(1f)) {
Row(verticalAlignment = Alignment.CenterVertically) { Row(verticalAlignment = Alignment.CenterVertically) {
@@ -26,7 +26,6 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.compositeOver import androidx.compose.ui.graphics.compositeOver
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import com.google.accompanist.flowlayout.FlowRow import com.google.accompanist.flowlayout.FlowRow
import com.vitorpamplona.amethyst.NotificationCache import com.vitorpamplona.amethyst.NotificationCache
import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.R
@@ -39,7 +38,7 @@ import kotlinx.coroutines.withContext
@OptIn(ExperimentalFoundationApi::class) @OptIn(ExperimentalFoundationApi::class)
@Composable @Composable
fun ZapSetCompose(zapSetCard: ZapSetCard, isInnerNote: Boolean = false, routeForLastRead: String, accountViewModel: AccountViewModel, navController: NavController) { fun ZapSetCompose(zapSetCard: ZapSetCard, isInnerNote: Boolean = false, routeForLastRead: String, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
val noteState by zapSetCard.note.live().metadata.observeAsState() val noteState by zapSetCard.note.live().metadata.observeAsState()
val note = noteState?.note val note = noteState?.note
@@ -78,7 +77,7 @@ fun ZapSetCompose(zapSetCard: ZapSetCard, isInnerNote: Boolean = false, routeFor
routeFor( routeFor(
note, note,
account.userProfile() account.userProfile()
)?.let { navController.navigate(it) } )?.let { nav(it) }
} }
}, },
onLongClick = { popupExpanded = true } onLongClick = { popupExpanded = true }
@@ -115,7 +114,7 @@ fun ZapSetCompose(zapSetCard: ZapSetCard, isInnerNote: Boolean = false, routeFor
zapSetCard.zapEvents.forEach { zapSetCard.zapEvents.forEach {
NoteAuthorPicture( NoteAuthorPicture(
baseNote = it.key, baseNote = it.key,
navController = navController, nav = nav,
userAccount = account.userProfile(), userAccount = account.userProfile(),
size = 35.dp size = 35.dp
) )
@@ -129,7 +128,7 @@ fun ZapSetCompose(zapSetCard: ZapSetCard, isInnerNote: Boolean = false, routeFor
isBoostedNote = true, isBoostedNote = true,
parentBackgroundColor = backgroundColor, parentBackgroundColor = backgroundColor,
accountViewModel = accountViewModel, accountViewModel = accountViewModel,
navController = navController nav = nav
) )
NoteDropDownMenu(note, popupExpanded, { popupExpanded = false }, accountViewModel) NoteDropDownMenu(note, popupExpanded, { popupExpanded = false }, accountViewModel)
@@ -25,7 +25,6 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.compositeOver import androidx.compose.ui.graphics.compositeOver
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import com.google.accompanist.flowlayout.FlowRow import com.google.accompanist.flowlayout.FlowRow
import com.vitorpamplona.amethyst.NotificationCache import com.vitorpamplona.amethyst.NotificationCache
import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.R
@@ -37,7 +36,7 @@ import kotlinx.coroutines.withContext
@OptIn(ExperimentalFoundationApi::class) @OptIn(ExperimentalFoundationApi::class)
@Composable @Composable
fun ZapUserSetCompose(zapSetCard: ZapUserSetCard, isInnerNote: Boolean = false, routeForLastRead: String, accountViewModel: AccountViewModel, navController: NavController) { fun ZapUserSetCompose(zapSetCard: ZapUserSetCard, isInnerNote: Boolean = false, routeForLastRead: String, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
val accountState by accountViewModel.accountLiveData.observeAsState() val accountState by accountViewModel.accountLiveData.observeAsState()
val account = accountState?.account ?: return val account = accountState?.account ?: return
@@ -61,7 +60,7 @@ fun ZapUserSetCompose(zapSetCard: ZapUserSetCard, isInnerNote: Boolean = false,
modifier = Modifier modifier = Modifier
.background(backgroundColor) .background(backgroundColor)
.clickable { .clickable {
navController.navigate("User/${zapSetCard.user.pubkeyHex}") nav("User/${zapSetCard.user.pubkeyHex}")
} }
) { ) {
Row( Row(
@@ -95,14 +94,14 @@ fun ZapUserSetCompose(zapSetCard: ZapUserSetCard, isInnerNote: Boolean = false,
zapSetCard.zapEvents.forEach { zapSetCard.zapEvents.forEach {
NoteAuthorPicture( NoteAuthorPicture(
baseNote = it.key, baseNote = it.key,
navController = navController, nav = nav,
userAccount = account.userProfile(), userAccount = account.userProfile(),
size = 35.dp size = 35.dp
) )
} }
} }
UserCompose(baseUser = zapSetCard.user, accountViewModel = accountViewModel, navController = navController) UserCompose(baseUser = zapSetCard.user, accountViewModel = accountViewModel, nav = nav)
} }
} }
} }
@@ -6,6 +6,7 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.lifecycle.viewmodel.compose.viewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.MainScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.MainScreen
import com.vitorpamplona.amethyst.ui.screen.loggedOff.LoginPage import com.vitorpamplona.amethyst.ui.screen.loggedOff.LoginPage
@@ -21,10 +22,20 @@ fun AccountScreen(accountStateViewModel: AccountStateViewModel, startingPage: St
LoginPage(accountStateViewModel, isFirstLogin = true) LoginPage(accountStateViewModel, isFirstLogin = true)
} }
is AccountState.LoggedIn -> { is AccountState.LoggedIn -> {
MainScreen(AccountViewModel(state.account), accountStateViewModel, startingPage) val accountViewModel: AccountViewModel = viewModel(
key = state.account.userProfile().pubkeyHex,
factory = AccountViewModel.Factory(state.account)
)
MainScreen(accountViewModel, accountStateViewModel, startingPage)
} }
is AccountState.LoggedInViewOnly -> { is AccountState.LoggedInViewOnly -> {
MainScreen(AccountViewModel(state.account), accountStateViewModel, startingPage) val accountViewModel: AccountViewModel = viewModel(
key = state.account.userProfile().pubkeyHex,
factory = AccountViewModel.Factory(state.account)
)
MainScreen(accountViewModel, accountStateViewModel, startingPage)
} }
} }
} }
@@ -4,7 +4,10 @@ import androidx.compose.runtime.Immutable
import androidx.compose.runtime.MutableState import androidx.compose.runtime.MutableState
import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.model.User
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.ImmutableMap
@Immutable
abstract class Card() { abstract class Card() {
abstract fun createdAt(): Long abstract fun createdAt(): Long
abstract fun id(): String abstract fun id(): String
@@ -47,7 +50,7 @@ class ZapSetCard(val note: Note, val zapEvents: Map<Note, Note>) : Card() {
} }
@Immutable @Immutable
class ZapUserSetCard(val user: User, val zapEvents: Map<Note, Note>) : Card() { class ZapUserSetCard(val user: User, val zapEvents: ImmutableMap<Note, Note>) : Card() {
val createdAt = zapEvents.maxOf { it.value.createdAt() ?: 0 } val createdAt = zapEvents.maxOf { it.value.createdAt() ?: 0 }
override fun createdAt(): Long { override fun createdAt(): Long {
return createdAt return createdAt
@@ -56,7 +59,7 @@ class ZapUserSetCard(val user: User, val zapEvents: Map<Note, Note>) : Card() {
} }
@Immutable @Immutable
class MultiSetCard(val note: Note, val boostEvents: List<Note>, val likeEvents: List<Note>, val zapEvents: Map<Note, Note>) : Card() { class MultiSetCard(val note: Note, val boostEvents: ImmutableList<Note>, val likeEvents: ImmutableList<Note>, val zapEvents: ImmutableMap<Note, Note>) : Card() {
val createdAt = maxOf( val createdAt = maxOf(
zapEvents.maxOfOrNull { it.value.createdAt() ?: 0 } ?: 0, zapEvents.maxOfOrNull { it.value.createdAt() ?: 0 } ?: 0,
likeEvents.maxOfOrNull { it.createdAt() ?: 0 } ?: 0, likeEvents.maxOfOrNull { it.createdAt() ?: 0 } ?: 0,
@@ -91,7 +94,7 @@ class MessageSetCard(val note: Note) : Card() {
sealed class CardFeedState { sealed class CardFeedState {
object Loading : CardFeedState() object Loading : CardFeedState()
class Loaded(val feed: MutableState<List<Card>>) : CardFeedState() class Loaded(val feed: MutableState<ImmutableList<Card>>) : CardFeedState()
object Empty : CardFeedState() object Empty : CardFeedState()
class FeedError(val errorMessage: String) : CardFeedState() class FeedError(val errorMessage: String) : CardFeedState()
} }
@@ -1,6 +1,5 @@
package com.vitorpamplona.amethyst.ui.screen package com.vitorpamplona.amethyst.ui.screen
import android.util.Log
import androidx.compose.animation.Crossfade import androidx.compose.animation.Crossfade
import androidx.compose.animation.core.tween import androidx.compose.animation.core.tween
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
@@ -23,7 +22,6 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import com.vitorpamplona.amethyst.ui.note.BadgeCompose import com.vitorpamplona.amethyst.ui.note.BadgeCompose
import com.vitorpamplona.amethyst.ui.note.BoostSetCompose import com.vitorpamplona.amethyst.ui.note.BoostSetCompose
import com.vitorpamplona.amethyst.ui.note.LikeSetCompose import com.vitorpamplona.amethyst.ui.note.LikeSetCompose
@@ -33,15 +31,13 @@ import com.vitorpamplona.amethyst.ui.note.NoteCompose
import com.vitorpamplona.amethyst.ui.note.ZapSetCompose import com.vitorpamplona.amethyst.ui.note.ZapSetCompose
import com.vitorpamplona.amethyst.ui.note.ZapUserSetCompose import com.vitorpamplona.amethyst.ui.note.ZapUserSetCompose
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import kotlin.time.ExperimentalTime
import kotlin.time.measureTimedValue
@OptIn(ExperimentalMaterialApi::class) @OptIn(ExperimentalMaterialApi::class)
@Composable @Composable
fun CardFeedView( fun CardFeedView(
viewModel: CardFeedViewModel, viewModel: CardFeedViewModel,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
navController: NavController, nav: (String) -> Unit,
routeForLastRead: String, routeForLastRead: String,
scrollStateKey: String? = null, scrollStateKey: String? = null,
scrollToTop: Boolean = false scrollToTop: Boolean = false
@@ -74,7 +70,7 @@ fun CardFeedView(
FeedLoaded( FeedLoaded(
state = state, state = state,
accountViewModel = accountViewModel, accountViewModel = accountViewModel,
navController = navController, nav = nav,
routeForLastRead = routeForLastRead, routeForLastRead = routeForLastRead,
scrollStateKey = scrollStateKey, scrollStateKey = scrollStateKey,
scrollToTop = scrollToTop scrollToTop = scrollToTop
@@ -91,12 +87,11 @@ fun CardFeedView(
} }
} }
@OptIn(ExperimentalTime::class)
@Composable @Composable
private fun FeedLoaded( private fun FeedLoaded(
state: CardFeedState.Loaded, state: CardFeedState.Loaded,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
navController: NavController, nav: (String) -> Unit,
routeForLastRead: String, routeForLastRead: String,
scrollStateKey: String?, scrollStateKey: String?,
scrollToTop: Boolean = false scrollToTop: Boolean = false
@@ -121,64 +116,61 @@ private fun FeedLoaded(
state = listState state = listState
) { ) {
itemsIndexed(state.feed.value, key = { _, item -> item.id() }) { _, item -> itemsIndexed(state.feed.value, key = { _, item -> item.id() }) { _, item ->
val (value, elapsed) = measureTimedValue {
when (item) { when (item) {
is NoteCard -> NoteCompose( is NoteCard -> NoteCompose(
item.note, item.note,
isBoostedNote = false, isBoostedNote = false,
accountViewModel = accountViewModel, accountViewModel = accountViewModel,
navController = navController, nav = nav,
routeForLastRead = routeForLastRead routeForLastRead = routeForLastRead
) )
is ZapSetCard -> ZapSetCompose( is ZapSetCard -> ZapSetCompose(
item, item,
isInnerNote = false, isInnerNote = false,
accountViewModel = accountViewModel, accountViewModel = accountViewModel,
navController = navController, nav = nav,
routeForLastRead = routeForLastRead routeForLastRead = routeForLastRead
) )
is ZapUserSetCard -> ZapUserSetCompose( is ZapUserSetCard -> ZapUserSetCompose(
item, item,
isInnerNote = false, isInnerNote = false,
accountViewModel = accountViewModel, accountViewModel = accountViewModel,
navController = navController, nav = nav,
routeForLastRead = routeForLastRead routeForLastRead = routeForLastRead
) )
is LikeSetCard -> LikeSetCompose( is LikeSetCard -> LikeSetCompose(
item, item,
isInnerNote = false, isInnerNote = false,
accountViewModel = accountViewModel, accountViewModel = accountViewModel,
navController = navController, nav = nav,
routeForLastRead = routeForLastRead routeForLastRead = routeForLastRead
) )
is BoostSetCard -> BoostSetCompose( is BoostSetCard -> BoostSetCompose(
item, item,
isInnerNote = false, isInnerNote = false,
accountViewModel = accountViewModel, accountViewModel = accountViewModel,
navController = navController, nav = nav,
routeForLastRead = routeForLastRead routeForLastRead = routeForLastRead
) )
is MultiSetCard -> MultiSetCompose( is MultiSetCard -> MultiSetCompose(
item, item,
accountViewModel = accountViewModel, accountViewModel = accountViewModel,
navController = navController, nav = nav,
routeForLastRead = routeForLastRead routeForLastRead = routeForLastRead
) )
is BadgeCard -> BadgeCompose( is BadgeCard -> BadgeCompose(
item, item,
accountViewModel = accountViewModel, accountViewModel = accountViewModel,
navController = navController, nav = nav,
routeForLastRead = routeForLastRead routeForLastRead = routeForLastRead
) )
is MessageSetCard -> MessageSetCompose( is MessageSetCard -> MessageSetCompose(
messageSetCard = item, messageSetCard = item,
routeForLastRead = routeForLastRead, routeForLastRead = routeForLastRead,
accountViewModel = accountViewModel, accountViewModel = accountViewModel,
navController = navController nav = nav
) )
} }
} }
Log.d("Time", "${item.javaClass.simpleName} Feed in $elapsed ${item.id()}")
}
} }
} }
@@ -20,6 +20,9 @@ import com.vitorpamplona.amethyst.ui.components.BundledUpdate
import com.vitorpamplona.amethyst.ui.dal.AdditiveFeedFilter import com.vitorpamplona.amethyst.ui.dal.AdditiveFeedFilter
import com.vitorpamplona.amethyst.ui.dal.FeedFilter import com.vitorpamplona.amethyst.ui.dal.FeedFilter
import com.vitorpamplona.amethyst.ui.dal.NotificationFeedFilter import com.vitorpamplona.amethyst.ui.dal.NotificationFeedFilter
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList
import kotlinx.collections.immutable.toImmutableMap
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
@@ -59,17 +62,28 @@ open class CardFeedViewModel(val localFilter: FeedFilter<Note>) : ViewModel() {
if (newCards.isNotEmpty()) { if (newCards.isNotEmpty()) {
lastNotes = notes.toSet() lastNotes = notes.toSet()
lastAccount = (localFilter as? NotificationFeedFilter)?.account lastAccount = (localFilter as? NotificationFeedFilter)?.account
val singleList = (oldNotesState.feed.value + newCards)
val updatedCards = (oldNotesState.feed.value + newCards)
.distinctBy { it.id() } .distinctBy { it.id() }
.sortedWith(compareBy({ it.createdAt() }, { it.id() })) .sortedWith(compareBy({ it.createdAt() }, { it.id() }))
.reversed() .reversed()
.take(1000) .take(1000)
updateFeed(singleList) .toImmutableList()
if (!equalImmutableLists(oldNotesState.feed.value, updatedCards)) {
updateFeed(updatedCards)
}
} }
} else { } else {
val cards = convertToCard(notes)
lastNotes = notes.toSet() lastNotes = notes.toSet()
lastAccount = (localFilter as? NotificationFeedFilter)?.account lastAccount = (localFilter as? NotificationFeedFilter)?.account
val cards = convertToCard(notes)
.sortedWith(compareBy({ it.createdAt() }, { it.id() }))
.reversed()
.take(1000)
.toImmutableList()
updateFeed(cards) updateFeed(cards)
} }
} }
@@ -138,9 +152,9 @@ open class CardFeedViewModel(val localFilter: FeedFilter<Note>) : ViewModel() {
singleList.chunked(50).map { chunk -> singleList.chunked(50).map { chunk ->
MultiSetCard( MultiSetCard(
baseNote, baseNote,
boostsInCard.filter { it in chunk }, boostsInCard.filter { it in chunk }.toImmutableList(),
reactionsInCard.filter { it in chunk }, reactionsInCard.filter { it in chunk }.toImmutableList(),
zapsInCard.filter { it.value in chunk } zapsInCard.filter { it.value in chunk }.toImmutableMap()
) )
} }
}.flatten() }.flatten()
@@ -148,7 +162,7 @@ open class CardFeedViewModel(val localFilter: FeedFilter<Note>) : ViewModel() {
val userZaps = zapsPerUser.map { val userZaps = zapsPerUser.map {
ZapUserSetCard( ZapUserSetCard(
it.key, it.key,
it.value it.value.toImmutableMap()
) )
} }
@@ -165,7 +179,7 @@ open class CardFeedViewModel(val localFilter: FeedFilter<Note>) : ViewModel() {
return (multiCards + textNoteCards + userZaps).sortedWith(compareBy({ it.createdAt() }, { it.id() })).reversed() return (multiCards + textNoteCards + userZaps).sortedWith(compareBy({ it.createdAt() }, { it.id() })).reversed()
} }
private fun updateFeed(notes: List<Card>) { private fun updateFeed(notes: ImmutableList<Card>) {
val scope = CoroutineScope(Job() + Dispatchers.Main) val scope = CoroutineScope(Job() + Dispatchers.Main)
scope.launch { scope.launch {
val currentState = _feedContent.value val currentState = _feedContent.value
@@ -173,7 +187,6 @@ open class CardFeedViewModel(val localFilter: FeedFilter<Note>) : ViewModel() {
if (notes.isEmpty()) { if (notes.isEmpty()) {
_feedContent.update { CardFeedState.Empty } _feedContent.update { CardFeedState.Empty }
} else if (currentState is CardFeedState.Loaded) { } else if (currentState is CardFeedState.Loaded) {
// updates the current list
currentState.feed.value = notes currentState.feed.value = notes
} else { } else {
_feedContent.update { CardFeedState.Loaded(mutableStateOf(notes)) } _feedContent.update { CardFeedState.Loaded(mutableStateOf(notes)) }
@@ -207,9 +220,12 @@ open class CardFeedViewModel(val localFilter: FeedFilter<Note>) : ViewModel() {
.sortedWith(compareBy({ it.createdAt() }, { it.id() })) .sortedWith(compareBy({ it.createdAt() }, { it.id() }))
.reversed() .reversed()
.take(1000) .take(1000)
.toImmutableList()
if (!equalImmutableLists(oldNotesState.feed.value, updatedCards)) {
updateFeed(updatedCards) updateFeed(updatedCards)
} }
}
} else { } else {
// Refresh Everything // Refresh Everything
refreshSuspended() refreshSuspended()
@@ -227,8 +243,8 @@ open class CardFeedViewModel(val localFilter: FeedFilter<Note>) : ViewModel() {
} }
private val bundlerInsert = BundledInsert<Set<Note>>(250, Dispatchers.IO) private val bundlerInsert = BundledInsert<Set<Note>>(250, Dispatchers.IO)
fun invalidateData() { fun invalidateData(ignoreIfDoing: Boolean = false) {
bundler.invalidate() bundler.invalidate(ignoreIfDoing)
} }
@OptIn(ExperimentalTime::class) @OptIn(ExperimentalTime::class)
@@ -270,3 +286,13 @@ open class CardFeedViewModel(val localFilter: FeedFilter<Note>) : ViewModel() {
super.onCleared() super.onCleared()
} }
} }
fun <T> equalImmutableLists(list1: ImmutableList<T>, list2: ImmutableList<T>): Boolean {
if (list1.size != list2.size) return false
for (i in 0 until list1.size) {
if (list1[i] !== list2[i]) {
return false
}
}
return true
}
@@ -14,13 +14,12 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.ui.note.ChatroomMessageCompose import com.vitorpamplona.amethyst.ui.note.ChatroomMessageCompose
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
@Composable @Composable
fun ChatroomFeedView(viewModel: FeedViewModel, accountViewModel: AccountViewModel, navController: NavController, routeForLastRead: String, onWantsToReply: (Note) -> Unit) { fun ChatroomFeedView(viewModel: FeedViewModel, accountViewModel: AccountViewModel, nav: (String) -> Unit, routeForLastRead: String, onWantsToReply: (Note) -> Unit) {
val feedState by viewModel.feedContent.collectAsState() val feedState by viewModel.feedContent.collectAsState()
var isRefreshing by remember { mutableStateOf(false) } var isRefreshing by remember { mutableStateOf(false) }
@@ -63,7 +62,7 @@ fun ChatroomFeedView(viewModel: FeedViewModel, accountViewModel: AccountViewMode
state = listState state = listState
) { ) {
itemsIndexed(state.feed.value, key = { _, item -> item.idHex }) { _, item -> itemsIndexed(state.feed.value, key = { _, item -> item.idHex }) { _, item ->
ChatroomMessageCompose(item, routeForLastRead, accountViewModel = accountViewModel, navController = navController, onWantsToReply = onWantsToReply) ChatroomMessageCompose(item, routeForLastRead, accountViewModel = accountViewModel, nav = nav, onWantsToReply = onWantsToReply)
} }
} }
} }
@@ -24,7 +24,6 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.NavController
import com.vitorpamplona.amethyst.NotificationCache import com.vitorpamplona.amethyst.NotificationCache
import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.service.model.PrivateDmEvent import com.vitorpamplona.amethyst.service.model.PrivateDmEvent
@@ -36,7 +35,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
fun ChatroomListFeedView( fun ChatroomListFeedView(
viewModel: FeedViewModel, viewModel: FeedViewModel,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
navController: NavController, nav: (String) -> Unit,
markAsRead: MutableState<Boolean> markAsRead: MutableState<Boolean>
) { ) {
val feedState by viewModel.feedContent.collectAsStateWithLifecycle() val feedState by viewModel.feedContent.collectAsStateWithLifecycle()
@@ -68,7 +67,7 @@ fun ChatroomListFeedView(
if (refreshing) { if (refreshing) {
refreshing = false refreshing = false
} }
FeedLoaded(state, accountViewModel, navController, markAsRead) FeedLoaded(state, accountViewModel, nav, markAsRead)
} }
FeedState.Loading -> { FeedState.Loading -> {
@@ -86,7 +85,7 @@ fun ChatroomListFeedView(
private fun FeedLoaded( private fun FeedLoaded(
state: FeedState.Loaded, state: FeedState.Loaded,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
navController: NavController, nav: (String) -> Unit,
markAsRead: MutableState<Boolean> markAsRead: MutableState<Boolean>
) { ) {
val listState = rememberLazyListState() val listState = rememberLazyListState()
@@ -139,7 +138,7 @@ private fun FeedLoaded(
ChatroomCompose( ChatroomCompose(
item, item,
accountViewModel = accountViewModel, accountViewModel = accountViewModel,
navController = navController nav = nav
) )
} }
} }
@@ -29,7 +29,6 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.ui.note.NoteCompose import com.vitorpamplona.amethyst.ui.note.NoteCompose
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
@@ -39,7 +38,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
fun FeedView( fun FeedView(
viewModel: FeedViewModel, viewModel: FeedViewModel,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
navController: NavController, nav: (String) -> Unit,
routeForLastRead: String?, routeForLastRead: String?,
scrollStateKey: String? = null, scrollStateKey: String? = null,
scrollToTop: Boolean = false, scrollToTop: Boolean = false,
@@ -84,7 +83,7 @@ fun FeedView(
state, state,
routeForLastRead, routeForLastRead,
accountViewModel, accountViewModel,
navController, nav,
scrollStateKey, scrollStateKey,
scrollToTop scrollToTop
) )
@@ -108,7 +107,7 @@ private fun FeedLoaded(
state: FeedState.Loaded, state: FeedState.Loaded,
routeForLastRead: String?, routeForLastRead: String?,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
navController: NavController, nav: (String) -> Unit,
scrollStateKey: String?, scrollStateKey: String?,
scrollToTop: Boolean = false scrollToTop: Boolean = false
) { ) {
@@ -142,7 +141,7 @@ private fun FeedLoaded(
modifier = baseModifier, modifier = baseModifier,
isBoostedNote = false, isBoostedNote = false,
accountViewModel = accountViewModel, accountViewModel = accountViewModel,
navController = navController nav = nav
) )
} }
} }
@@ -21,7 +21,6 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import com.vitorpamplona.amethyst.ui.note.ZapNoteCompose import com.vitorpamplona.amethyst.ui.note.ZapNoteCompose
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
@@ -30,7 +29,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
fun LnZapFeedView( fun LnZapFeedView(
viewModel: LnZapFeedViewModel, viewModel: LnZapFeedViewModel,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
navController: NavController, nav: (String) -> Unit,
enablePullRefresh: Boolean = true enablePullRefresh: Boolean = true
) { ) {
val feedState by viewModel.feedContent.collectAsState() val feedState by viewModel.feedContent.collectAsState()
@@ -64,7 +63,7 @@ fun LnZapFeedView(
refreshing = false refreshing = false
} }
LnZapFeedLoaded(state, accountViewModel, navController) LnZapFeedLoaded(state, accountViewModel, nav)
} }
is LnZapFeedState.Loading -> { is LnZapFeedState.Loading -> {
LoadingFeed() LoadingFeed()
@@ -83,7 +82,7 @@ fun LnZapFeedView(
private fun LnZapFeedLoaded( private fun LnZapFeedLoaded(
state: LnZapFeedState.Loaded, state: LnZapFeedState.Loaded,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
navController: NavController nav: (String) -> Unit
) { ) {
val listState = rememberLazyListState() val listState = rememberLazyListState()
@@ -95,7 +94,7 @@ private fun LnZapFeedLoaded(
state = listState state = listState
) { ) {
itemsIndexed(state.feed.value, key = { _, item -> item.second.idHex }) { _, item -> itemsIndexed(state.feed.value, key = { _, item -> item.second.idHex }) { _, item ->
ZapNoteCompose(item, accountViewModel = accountViewModel, navController = navController) ZapNoteCompose(item, accountViewModel = accountViewModel, nav = nav)
} }
} }
} }
@@ -49,7 +49,6 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.navigation.NavController
import coil.compose.AsyncImage import coil.compose.AsyncImage
import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.Note
@@ -82,7 +81,7 @@ import kotlinx.coroutines.delay
@OptIn(ExperimentalMaterialApi::class) @OptIn(ExperimentalMaterialApi::class)
@Composable @Composable
fun ThreadFeedView(noteId: String, viewModel: FeedViewModel, accountViewModel: AccountViewModel, navController: NavController) { fun ThreadFeedView(noteId: String, viewModel: FeedViewModel, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
val feedState by viewModel.feedContent.collectAsState() val feedState by viewModel.feedContent.collectAsState()
val listState = rememberLazyListState() val listState = rememberLazyListState()
@@ -139,7 +138,7 @@ fun ThreadFeedView(noteId: String, viewModel: FeedViewModel, accountViewModel: A
if (item.idHex == noteId) MaterialTheme.colors.primary.copy(alpha = 0.52f) else MaterialTheme.colors.onSurface.copy(alpha = 0.32f) if (item.idHex == noteId) MaterialTheme.colors.primary.copy(alpha = 0.52f) else MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
), ),
accountViewModel = accountViewModel, accountViewModel = accountViewModel,
navController = navController nav = nav
) )
} else { } else {
Column() { Column() {
@@ -155,7 +154,7 @@ fun ThreadFeedView(noteId: String, viewModel: FeedViewModel, accountViewModel: A
isBoostedNote = false, isBoostedNote = false,
unPackReply = false, unPackReply = false,
accountViewModel = accountViewModel, accountViewModel = accountViewModel,
navController = navController nav = nav
) )
} }
} }
@@ -204,7 +203,7 @@ fun NoteMaster(
baseNote: Note, baseNote: Note,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
navController: NavController nav: (String) -> Unit
) { ) {
val noteState by baseNote.live().metadata.observeAsState() val noteState by baseNote.live().metadata.observeAsState()
val note = noteState?.note val note = noteState?.note
@@ -233,7 +232,7 @@ fun NoteMaster(
account.userProfile(), account.userProfile(),
Modifier, Modifier,
false, false,
navController, nav,
onClick = { showHiddenNote = true } onClick = { showHiddenNote = true }
) )
} else { } else {
@@ -247,13 +246,13 @@ fun NoteMaster(
.padding(start = 12.dp, end = 12.dp) .padding(start = 12.dp, end = 12.dp)
.clickable(onClick = { .clickable(onClick = {
note.author?.let { note.author?.let {
navController.navigate("User/${it.pubkeyHex}") nav("User/${it.pubkeyHex}")
} }
}) })
) { ) {
NoteAuthorPicture( NoteAuthorPicture(
baseNote = baseNote, baseNote = baseNote,
navController = navController, nav = nav,
userAccount = account.userProfile(), userAccount = account.userProfile(),
size = 55.dp size = 55.dp
) )
@@ -262,7 +261,7 @@ fun NoteMaster(
Row(verticalAlignment = Alignment.CenterVertically) { Row(verticalAlignment = Alignment.CenterVertically) {
NoteUsernameDisplay(baseNote, Modifier.weight(1f)) NoteUsernameDisplay(baseNote, Modifier.weight(1f))
DisplayFollowingHashtagsInPost(noteEvent, account, navController) DisplayFollowingHashtagsInPost(noteEvent, account, nav)
Text( Text(
timeAgo(note.createdAt(), context = context), timeAgo(note.createdAt(), context = context),
@@ -290,7 +289,7 @@ fun NoteMaster(
val baseReward = noteEvent.getReward() val baseReward = noteEvent.getReward()
if (baseReward != null) { if (baseReward != null) {
DisplayReward(baseReward, baseNote, account, navController) DisplayReward(baseReward, baseNote, account, nav)
} }
val pow = noteEvent.getPoWRank() val pow = noteEvent.getPoWRank()
@@ -353,11 +352,11 @@ fun NoteMaster(
) { ) {
Column() { Column() {
if (noteEvent is PeopleListEvent) { if (noteEvent is PeopleListEvent) {
DisplayPeopleList(noteState, MaterialTheme.colors.background, accountViewModel, navController) DisplayPeopleList(noteState, MaterialTheme.colors.background, accountViewModel, nav)
} else if (noteEvent is AudioTrackEvent) { } else if (noteEvent is AudioTrackEvent) {
AudioTrackHeader(noteEvent, note, account.userProfile(), navController) AudioTrackHeader(noteEvent, note, account.userProfile(), nav)
} else if (noteEvent is PinListEvent) { } else if (noteEvent is PinListEvent) {
PinListHeader(noteState, MaterialTheme.colors.background, accountViewModel, navController) PinListHeader(noteState, MaterialTheme.colors.background, accountViewModel, nav)
} else if (noteEvent is HighlightEvent) { } else if (noteEvent is HighlightEvent) {
DisplayHighlight( DisplayHighlight(
noteEvent.quote(), noteEvent.quote(),
@@ -367,7 +366,7 @@ fun NoteMaster(
true, true,
backgroundColor, backgroundColor,
accountViewModel, accountViewModel,
navController nav
) )
} else { } else {
val eventContent = note.event?.content() val eventContent = note.event?.content()
@@ -384,10 +383,10 @@ fun NoteMaster(
note.event?.tags(), note.event?.tags(),
MaterialTheme.colors.background, MaterialTheme.colors.background,
accountViewModel, accountViewModel,
navController nav
) )
DisplayUncitedHashtags(noteEvent.hashtags(), eventContent, navController) DisplayUncitedHashtags(noteEvent.hashtags(), eventContent, nav)
if (noteEvent is PollNoteEvent) { if (noteEvent is PollNoteEvent) {
PollNote( PollNote(
@@ -395,13 +394,13 @@ fun NoteMaster(
canPreview, canPreview,
backgroundColor, backgroundColor,
accountViewModel, accountViewModel,
navController nav
) )
} }
} }
} }
ReactionsRow(note, accountViewModel, navController) ReactionsRow(note, accountViewModel, nav)
Divider( Divider(
modifier = Modifier.padding(top = 10.dp), modifier = Modifier.padding(top = 10.dp),
@@ -21,7 +21,6 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import com.vitorpamplona.amethyst.ui.note.UserCompose import com.vitorpamplona.amethyst.ui.note.UserCompose
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
@@ -30,7 +29,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
fun UserFeedView( fun UserFeedView(
viewModel: UserFeedViewModel, viewModel: UserFeedViewModel,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
navController: NavController, nav: (String) -> Unit,
enablePullRefresh: Boolean = true enablePullRefresh: Boolean = true
) { ) {
val feedState by viewModel.feedContent.collectAsState() val feedState by viewModel.feedContent.collectAsState()
@@ -61,7 +60,7 @@ fun UserFeedView(
} }
is UserFeedState.Loaded -> { is UserFeedState.Loaded -> {
refreshing = false refreshing = false
FeedLoaded(state, accountViewModel, navController) FeedLoaded(state, accountViewModel, nav)
} }
is UserFeedState.Loading -> { is UserFeedState.Loading -> {
LoadingFeed() LoadingFeed()
@@ -80,7 +79,7 @@ fun UserFeedView(
private fun FeedLoaded( private fun FeedLoaded(
state: UserFeedState.Loaded, state: UserFeedState.Loaded,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
navController: NavController nav: (String) -> Unit
) { ) {
val listState = rememberLazyListState() val listState = rememberLazyListState()
@@ -92,7 +91,7 @@ private fun FeedLoaded(
state = listState state = listState
) { ) {
itemsIndexed(state.feed.value, key = { _, item -> item.pubkeyHex }) { _, item -> itemsIndexed(state.feed.value, key = { _, item -> item.pubkeyHex }) { _, item ->
UserCompose(item, accountViewModel = accountViewModel, navController = navController) UserCompose(item, accountViewModel = accountViewModel, nav = nav)
} }
} }
} }
@@ -3,9 +3,11 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import androidx.compose.runtime.Immutable
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.map import androidx.lifecycle.map
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.R
@@ -23,6 +25,7 @@ import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.util.Locale import java.util.Locale
@Immutable
class AccountViewModel(private val account: Account) : ViewModel() { class AccountViewModel(private val account: Account) : ViewModel() {
val accountLiveData: LiveData<AccountState> = account.live.map { it } val accountLiveData: LiveData<AccountState> = account.live.map { it }
val accountLanguagesLiveData: LiveData<AccountState> = account.liveLanguages.map { it } val accountLanguagesLiveData: LiveData<AccountState> = account.liveLanguages.map { it }
@@ -228,4 +231,10 @@ class AccountViewModel(private val account: Account) : ViewModel() {
fun dontShowBlockAlertDialog() { fun dontShowBlockAlertDialog() {
account.setHideBlockAlertDialog() account.setHideBlockAlertDialog()
} }
class Factory(val account: Account) : ViewModelProvider.Factory {
override fun <AccountViewModel : ViewModel> create(modelClass: Class<AccountViewModel>): AccountViewModel {
return AccountViewModel(account) as AccountViewModel
}
}
} }
@@ -19,7 +19,6 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.ui.dal.BookmarkPrivateFeedFilter import com.vitorpamplona.amethyst.ui.dal.BookmarkPrivateFeedFilter
import com.vitorpamplona.amethyst.ui.dal.BookmarkPublicFeedFilter import com.vitorpamplona.amethyst.ui.dal.BookmarkPublicFeedFilter
@@ -30,7 +29,7 @@ import kotlinx.coroutines.launch
@OptIn(ExperimentalFoundationApi::class) @OptIn(ExperimentalFoundationApi::class)
@Composable @Composable
fun BookmarkListScreen(accountViewModel: AccountViewModel, navController: NavController) { fun BookmarkListScreen(accountViewModel: AccountViewModel, nav: (String) -> Unit) {
val accountState by accountViewModel.accountLiveData.observeAsState() val accountState by accountViewModel.accountLiveData.observeAsState()
val account = accountState?.account val account = accountState?.account
@@ -74,8 +73,8 @@ fun BookmarkListScreen(accountViewModel: AccountViewModel, navController: NavCon
} }
HorizontalPager(pageCount = 2, state = pagerState) { page -> HorizontalPager(pageCount = 2, state = pagerState) { page ->
when (page) { when (page) {
0 -> FeedView(privateFeedViewModel, accountViewModel, navController, null) 0 -> FeedView(privateFeedViewModel, accountViewModel, nav, null)
1 -> FeedView(publicFeedViewModel, accountViewModel, navController, null) 1 -> FeedView(publicFeedViewModel, accountViewModel, nav, null)
} }
} }
} }
@@ -60,7 +60,6 @@ import androidx.compose.ui.unit.sp
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.Channel import com.vitorpamplona.amethyst.model.Channel
@@ -83,7 +82,7 @@ import com.vitorpamplona.amethyst.ui.screen.NostrChannelFeedViewModel
fun ChannelScreen( fun ChannelScreen(
channelId: String?, channelId: String?,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
navController: NavController nav: (String) -> Unit
) { ) {
val accountState by accountViewModel.accountLiveData.observeAsState() val accountState by accountViewModel.accountLiveData.observeAsState()
val account = accountState?.account val account = accountState?.account
@@ -143,7 +142,7 @@ fun ChannelScreen(
ChannelHeader( ChannelHeader(
channel, channel,
account, account,
navController = navController nav = nav
) )
Column( Column(
@@ -152,7 +151,7 @@ fun ChannelScreen(
.padding(vertical = 0.dp) .padding(vertical = 0.dp)
.weight(1f, true) .weight(1f, true)
) { ) {
ChatroomFeedView(feedViewModel, accountViewModel, navController, "Channel/$channelId") { ChatroomFeedView(feedViewModel, accountViewModel, nav, "Channel/$channelId") {
replyTo.value = it replyTo.value = it
} }
} }
@@ -168,7 +167,7 @@ fun ChannelScreen(
null, null,
innerQuote = true, innerQuote = true,
accountViewModel = accountViewModel, accountViewModel = accountViewModel,
navController = navController, nav = nav,
onWantsToReply = { onWantsToReply = {
replyTo.value = it replyTo.value = it
} }
@@ -248,7 +247,7 @@ fun ChannelScreen(
} }
@Composable @Composable
fun ChannelHeader(baseChannel: Channel, account: Account, navController: NavController) { fun ChannelHeader(baseChannel: Channel, account: Account, nav: (String) -> Unit) {
val channelState by baseChannel.live.observeAsState() val channelState by baseChannel.live.observeAsState()
val channel = channelState?.channel ?: return val channel = channelState?.channel ?: return
@@ -256,7 +255,7 @@ fun ChannelHeader(baseChannel: Channel, account: Account, navController: NavCont
Column( Column(
Modifier.clickable { Modifier.clickable {
navController.navigate("Channel/${baseChannel.idHex}") nav("Channel/${baseChannel.idHex}")
} }
) { ) {
Column(modifier = Modifier.padding(12.dp)) { Column(modifier = Modifier.padding(12.dp)) {
@@ -306,9 +305,9 @@ fun ChannelHeader(baseChannel: Channel, account: Account, navController: NavCont
} }
if (account.followingChannels.contains(channel.idHex)) { if (account.followingChannels.contains(channel.idHex)) {
LeaveButton(account, channel, navController) LeaveButton(account, channel, nav)
} else { } else {
JoinButton(account, channel, navController) JoinButton(account, channel, nav)
} }
} }
} }
@@ -386,7 +385,7 @@ private fun EditButton(account: Account, channel: Channel) {
} }
@Composable @Composable
private fun JoinButton(account: Account, channel: Channel, navController: NavController) { private fun JoinButton(account: Account, channel: Channel, nav: (String) -> Unit) {
Button( Button(
modifier = Modifier.padding(horizontal = 3.dp), modifier = Modifier.padding(horizontal = 3.dp),
onClick = { onClick = {
@@ -404,12 +403,12 @@ private fun JoinButton(account: Account, channel: Channel, navController: NavCon
} }
@Composable @Composable
private fun LeaveButton(account: Account, channel: Channel, navController: NavController) { private fun LeaveButton(account: Account, channel: Channel, nav: (String) -> Unit) {
Button( Button(
modifier = Modifier.padding(horizontal = 3.dp), modifier = Modifier.padding(horizontal = 3.dp),
onClick = { onClick = {
account.leaveChannel(channel.idHex) account.leaveChannel(channel.idHex)
navController.navigate(Route.Message.route) nav(Route.Message.route)
}, },
shape = RoundedCornerShape(20.dp), shape = RoundedCornerShape(20.dp),
colors = ButtonDefaults colors = ButtonDefaults
@@ -39,7 +39,6 @@ import androidx.compose.ui.unit.dp
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.service.NostrChatroomListDataSource import com.vitorpamplona.amethyst.service.NostrChatroomListDataSource
import com.vitorpamplona.amethyst.ui.dal.ChatroomListKnownFeedFilter import com.vitorpamplona.amethyst.ui.dal.ChatroomListKnownFeedFilter
@@ -52,7 +51,7 @@ import kotlinx.coroutines.launch
@OptIn(ExperimentalFoundationApi::class) @OptIn(ExperimentalFoundationApi::class)
@Composable @Composable
fun ChatroomListScreen(accountViewModel: AccountViewModel, navController: NavController) { fun ChatroomListScreen(accountViewModel: AccountViewModel, nav: (String) -> Unit) {
val pagerState = rememberPagerState() val pagerState = rememberPagerState()
val coroutineScope = rememberCoroutineScope() val coroutineScope = rememberCoroutineScope()
@@ -151,7 +150,7 @@ fun ChatroomListScreen(accountViewModel: AccountViewModel, navController: NavCon
ChatroomListFeedView( ChatroomListFeedView(
viewModel = tabs[page].viewModel, viewModel = tabs[page].viewModel,
accountViewModel = accountViewModel, accountViewModel = accountViewModel,
navController = navController, nav = nav,
markAsRead = tabs[page].markAsRead markAsRead = tabs[page].markAsRead
) )
} }
@@ -44,7 +44,6 @@ import androidx.compose.ui.unit.dp
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.model.User
@@ -61,7 +60,7 @@ import com.vitorpamplona.amethyst.ui.screen.ChatroomFeedView
import com.vitorpamplona.amethyst.ui.screen.NostrChatRoomFeedViewModel import com.vitorpamplona.amethyst.ui.screen.NostrChatRoomFeedViewModel
@Composable @Composable
fun ChatroomScreen(userId: String?, accountViewModel: AccountViewModel, navController: NavController) { fun ChatroomScreen(userId: String?, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
val accountState by accountViewModel.accountLiveData.observeAsState() val accountState by accountViewModel.accountLiveData.observeAsState()
val account = accountState?.account val account = accountState?.account
val context = LocalContext.current val context = LocalContext.current
@@ -106,7 +105,7 @@ fun ChatroomScreen(userId: String?, accountViewModel: AccountViewModel, navContr
Column(Modifier.fillMaxHeight()) { Column(Modifier.fillMaxHeight()) {
NostrChatroomDataSource.withUser?.let { NostrChatroomDataSource.withUser?.let {
ChatroomHeader(it, account.userProfile(), navController = navController) ChatroomHeader(it, account.userProfile(), nav = nav)
} }
Column( Column(
@@ -115,7 +114,7 @@ fun ChatroomScreen(userId: String?, accountViewModel: AccountViewModel, navContr
.padding(vertical = 0.dp) .padding(vertical = 0.dp)
.weight(1f, true) .weight(1f, true)
) { ) {
ChatroomFeedView(feedViewModel, accountViewModel, navController, "Room/$userId") { ChatroomFeedView(feedViewModel, accountViewModel, nav, "Room/$userId") {
replyTo.value = it replyTo.value = it
} }
} }
@@ -131,7 +130,7 @@ fun ChatroomScreen(userId: String?, accountViewModel: AccountViewModel, navContr
null, null,
innerQuote = true, innerQuote = true,
accountViewModel = accountViewModel, accountViewModel = accountViewModel,
navController = navController, nav = nav,
onWantsToReply = { onWantsToReply = {
replyTo.value = it replyTo.value = it
} }
@@ -208,10 +207,10 @@ fun ChatroomScreen(userId: String?, accountViewModel: AccountViewModel, navContr
} }
@Composable @Composable
fun ChatroomHeader(baseUser: User, accountUser: User, navController: NavController) { fun ChatroomHeader(baseUser: User, accountUser: User, nav: (String) -> Unit) {
Column( Column(
modifier = Modifier.clickable( modifier = Modifier.clickable(
onClick = { navController.navigate("User/${baseUser.pubkeyHex}") } onClick = { nav("User/${baseUser.pubkeyHex}") }
) )
) { ) {
Column(modifier = Modifier.padding(12.dp)) { Column(modifier = Modifier.padding(12.dp)) {
@@ -22,7 +22,6 @@ import androidx.compose.ui.unit.dp
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.service.NostrHashtagDataSource import com.vitorpamplona.amethyst.service.NostrHashtagDataSource
import com.vitorpamplona.amethyst.ui.dal.HashtagFeedFilter import com.vitorpamplona.amethyst.ui.dal.HashtagFeedFilter
@@ -32,7 +31,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@Composable @Composable
fun HashtagScreen(tag: String?, accountViewModel: AccountViewModel, navController: NavController) { fun HashtagScreen(tag: String?, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
val accountState by accountViewModel.accountLiveData.observeAsState() val accountState by accountViewModel.accountLiveData.observeAsState()
val account = accountState?.account ?: return val account = accountState?.account ?: return
@@ -76,7 +75,7 @@ fun HashtagScreen(tag: String?, accountViewModel: AccountViewModel, navControlle
modifier = Modifier.padding(vertical = 0.dp) modifier = Modifier.padding(vertical = 0.dp)
) { ) {
HashtagHeader(tag, account) HashtagHeader(tag, account)
FeedView(feedViewModel, accountViewModel, navController, null) FeedView(feedViewModel, accountViewModel, nav, null)
} }
} }
} }
@@ -18,7 +18,6 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.ui.dal.HiddenAccountsFeedFilter import com.vitorpamplona.amethyst.ui.dal.HiddenAccountsFeedFilter
import com.vitorpamplona.amethyst.ui.screen.NostrHiddenAccountsFeedViewModel import com.vitorpamplona.amethyst.ui.screen.NostrHiddenAccountsFeedViewModel
@@ -27,7 +26,7 @@ import kotlinx.coroutines.launch
@OptIn(ExperimentalFoundationApi::class) @OptIn(ExperimentalFoundationApi::class)
@Composable @Composable
fun HiddenUsersScreen(accountViewModel: AccountViewModel, navController: NavController) { fun HiddenUsersScreen(accountViewModel: AccountViewModel, nav: (String) -> Unit) {
val accountState by accountViewModel.accountLiveData.observeAsState() val accountState by accountViewModel.accountLiveData.observeAsState()
val account = accountState?.account val account = accountState?.account
@@ -55,7 +54,7 @@ fun HiddenUsersScreen(accountViewModel: AccountViewModel, navController: NavCont
} }
HorizontalPager(pageCount = 1, state = pagerState) { page -> HorizontalPager(pageCount = 1, state = pagerState) { page ->
when (page) { when (page) {
0 -> UserFeedView(feedViewModel, accountViewModel, navController) 0 -> UserFeedView(feedViewModel, accountViewModel, nav)
} }
} }
} }
@@ -25,7 +25,6 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver import androidx.lifecycle.LifecycleEventObserver
import androidx.navigation.NavController
import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.service.NostrHomeDataSource import com.vitorpamplona.amethyst.service.NostrHomeDataSource
import com.vitorpamplona.amethyst.ui.dal.HomeConversationsFeedFilter import com.vitorpamplona.amethyst.ui.dal.HomeConversationsFeedFilter
@@ -45,7 +44,7 @@ fun HomeScreen(
homeFeedViewModel: NostrHomeFeedViewModel, homeFeedViewModel: NostrHomeFeedViewModel,
repliesFeedViewModel: NostrHomeRepliesFeedViewModel, repliesFeedViewModel: NostrHomeRepliesFeedViewModel,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
navController: NavController, nav: (String) -> Unit,
pagerState: PagerState, pagerState: PagerState,
scrollToTop: Boolean = false, scrollToTop: Boolean = false,
nip47: String? = null nip47: String? = null
@@ -120,7 +119,7 @@ fun HomeScreen(
FeedView( FeedView(
viewModel = tabs[page].viewModel, viewModel = tabs[page].viewModel,
accountViewModel = accountViewModel, accountViewModel = accountViewModel,
navController = navController, nav = nav,
routeForLastRead = tabs[page].routeForLastRead, routeForLastRead = tabs[page].routeForLastRead,
scrollStateKey = tabs[page].scrollStateKey, scrollStateKey = tabs[page].scrollStateKey,
scrollToTop = scrollToTop scrollToTop = scrollToTop
@@ -53,6 +53,14 @@ fun MainScreen(accountViewModel: AccountViewModel, accountStateViewModel: Accoun
skipHalfExpanded = true skipHalfExpanded = true
) )
val nav = remember {
{ route: String ->
if (getRouteWithArguments(navController) != route) {
navController.navigate(route)
}
}
}
val accountState by accountViewModel.accountLiveData.observeAsState() val accountState by accountViewModel.accountLiveData.observeAsState()
val account = remember(accountState) { accountState?.account } val account = remember(accountState) { accountState?.account }
@@ -76,7 +84,7 @@ fun MainScreen(accountViewModel: AccountViewModel, accountStateViewModel: Accoun
AppTopBar(followLists, navController, scaffoldState, accountViewModel) AppTopBar(followLists, navController, scaffoldState, accountViewModel)
}, },
drawerContent = { drawerContent = {
DrawerContent(navController, scaffoldState, sheetState, accountViewModel) DrawerContent(nav, scaffoldState, sheetState, accountViewModel)
BackHandler(enabled = scaffoldState.drawerState.isOpen) { BackHandler(enabled = scaffoldState.drawerState.isOpen) {
scope.launch { scaffoldState.drawerState.close() } scope.launch { scaffoldState.drawerState.close() }
} }
@@ -97,6 +105,14 @@ fun MainScreen(accountViewModel: AccountViewModel, accountStateViewModel: Accoun
fun FloatingButtons(navController: NavHostController, accountViewModel: AccountViewModel, accountStateViewModel: AccountStateViewModel) { fun FloatingButtons(navController: NavHostController, accountViewModel: AccountViewModel, accountStateViewModel: AccountStateViewModel) {
val accountState by accountStateViewModel.accountContent.collectAsState() val accountState by accountStateViewModel.accountContent.collectAsState()
val nav = remember {
{ route: String ->
if (getRouteWithArguments(navController) != route) {
navController.navigate(route)
}
}
}
Crossfade(targetState = accountState, animationSpec = tween(durationMillis = 100)) { state -> Crossfade(targetState = accountState, animationSpec = tween(durationMillis = 100)) { state ->
when (state) { when (state) {
is AccountState.LoggedInViewOnly -> { is AccountState.LoggedInViewOnly -> {
@@ -107,13 +123,13 @@ fun FloatingButtons(navController: NavHostController, accountViewModel: AccountV
} }
is AccountState.LoggedIn -> { is AccountState.LoggedIn -> {
if (currentRoute(navController)?.substringBefore("?") == Route.Home.base) { if (currentRoute(navController)?.substringBefore("?") == Route.Home.base) {
NewNoteButton(state.account, accountViewModel, navController) NewNoteButton(state.account, accountViewModel, nav)
} }
if (currentRoute(navController) == Route.Message.base) { if (currentRoute(navController) == Route.Message.base) {
ChannelFabColumn(state.account, navController) ChannelFabColumn(state.account, nav)
} }
if (currentRoute(navController)?.substringBefore("?") == Route.Video.base) { if (currentRoute(navController)?.substringBefore("?") == Route.Video.base) {
NewImageButton(accountViewModel, navController) NewImageButton(accountViewModel, nav)
} }
} }
} }
@@ -23,7 +23,6 @@ import androidx.compose.ui.unit.dp
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import com.patrykandpatrick.vico.compose.axis.horizontal.bottomAxis import com.patrykandpatrick.vico.compose.axis.horizontal.bottomAxis
import com.patrykandpatrick.vico.compose.axis.vertical.endAxis import com.patrykandpatrick.vico.compose.axis.vertical.endAxis
import com.patrykandpatrick.vico.compose.axis.vertical.startAxis import com.patrykandpatrick.vico.compose.axis.vertical.startAxis
@@ -61,7 +60,7 @@ fun NotificationScreen(
notifFeedViewModel: NotificationViewModel, notifFeedViewModel: NotificationViewModel,
userReactionsStatsModel: UserReactionsViewModel, userReactionsStatsModel: UserReactionsViewModel,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
navController: NavController, nav: (String) -> Unit,
scrollToTop: Boolean = false scrollToTop: Boolean = false
) { ) {
val accountState by accountViewModel.accountLiveData.observeAsState() val accountState by accountViewModel.accountLiveData.observeAsState()
@@ -74,8 +73,6 @@ fun NotificationScreen(
LaunchedEffect(account.userProfile().pubkeyHex, account.defaultNotificationFollowList) { LaunchedEffect(account.userProfile().pubkeyHex, account.defaultNotificationFollowList) {
NostrAccountDataSource.resetFilters() NostrAccountDataSource.resetFilters()
NotificationFeedFilter.account = account NotificationFeedFilter.account = account
notifFeedViewModel.clear()
notifFeedViewModel.invalidateData()
} }
val lifeCycleOwner = LocalLifecycleOwner.current val lifeCycleOwner = LocalLifecycleOwner.current
@@ -83,7 +80,7 @@ fun NotificationScreen(
val observer = LifecycleEventObserver { _, event -> val observer = LifecycleEventObserver { _, event ->
if (event == Lifecycle.Event.ON_RESUME) { if (event == Lifecycle.Event.ON_RESUME) {
NotificationFeedFilter.account = account NotificationFeedFilter.account = account
notifFeedViewModel.invalidateData() notifFeedViewModel.invalidateData(true)
} }
} }
@@ -97,11 +94,11 @@ fun NotificationScreen(
Column( Column(
modifier = Modifier.padding(vertical = 0.dp) modifier = Modifier.padding(vertical = 0.dp)
) { ) {
SummaryBar(userReactionsStatsModel, accountViewModel, navController) SummaryBar(userReactionsStatsModel, accountViewModel, nav)
CardFeedView( CardFeedView(
viewModel = notifFeedViewModel, viewModel = notifFeedViewModel,
accountViewModel = accountViewModel, accountViewModel = accountViewModel,
navController = navController, nav = nav,
routeForLastRead = Route.Notification.base, routeForLastRead = Route.Notification.base,
scrollStateKey = ScrollStateKeys.NOTIFICATION_SCREEN, scrollStateKey = ScrollStateKeys.NOTIFICATION_SCREEN,
scrollToTop = scrollToTop scrollToTop = scrollToTop
@@ -111,12 +108,12 @@ fun NotificationScreen(
} }
@Composable @Composable
fun SummaryBar(model: UserReactionsViewModel, accountViewModel: AccountViewModel, navController: NavController) { fun SummaryBar(model: UserReactionsViewModel, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
var showChart by remember { var showChart by remember {
mutableStateOf(false) mutableStateOf(false)
} }
UserReactionsRow(model, accountViewModel, navController) { UserReactionsRow(model, accountViewModel, nav) {
showChart = !showChart showChart = !showChart
} }
@@ -49,7 +49,6 @@ import androidx.core.content.ContextCompat
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import coil.compose.AsyncImage import coil.compose.AsyncImage
import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.Account
@@ -103,7 +102,7 @@ import kotlinx.coroutines.withContext
import java.math.BigDecimal import java.math.BigDecimal
@Composable @Composable
fun ProfileScreen(userId: String?, accountViewModel: AccountViewModel, navController: NavController) { fun ProfileScreen(userId: String?, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
if (userId == null) return if (userId == null) return
var userBase by remember { mutableStateOf<User?>(null) } var userBase by remember { mutableStateOf<User?>(null) }
@@ -118,14 +117,14 @@ fun ProfileScreen(userId: String?, accountViewModel: AccountViewModel, navContro
ProfileScreen( ProfileScreen(
user = it, user = it,
accountViewModel = accountViewModel, accountViewModel = accountViewModel,
navController = navController nav = nav
) )
} }
} }
@OptIn(ExperimentalFoundationApi::class) @OptIn(ExperimentalFoundationApi::class)
@Composable @Composable
fun ProfileScreen(user: User, accountViewModel: AccountViewModel, navController: NavController) { fun ProfileScreen(user: User, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
val accountState by accountViewModel.accountLiveData.observeAsState() val accountState by accountViewModel.accountLiveData.observeAsState()
val account = remember(accountState) { accountState?.account } ?: return val account = remember(accountState) { accountState?.account } ?: return
@@ -210,7 +209,7 @@ fun ProfileScreen(user: User, accountViewModel: AccountViewModel, navController:
.fillMaxHeight() .fillMaxHeight()
) { ) {
Column(modifier = Modifier.padding()) { Column(modifier = Modifier.padding()) {
ProfileHeader(baseUser, navController, account, accountViewModel) ProfileHeader(baseUser, nav, account, accountViewModel)
ScrollableTabRow( ScrollableTabRow(
backgroundColor = MaterialTheme.colors.background, backgroundColor = MaterialTheme.colors.background,
selectedTabIndex = pagerState.currentPage, selectedTabIndex = pagerState.currentPage,
@@ -246,13 +245,13 @@ fun ProfileScreen(user: User, accountViewModel: AccountViewModel, navController:
} }
) { page -> ) { page ->
when (page) { when (page) {
0 -> TabNotesNewThreads(accountViewModel, navController) 0 -> TabNotesNewThreads(accountViewModel, nav)
1 -> TabNotesConversations(accountViewModel, navController) 1 -> TabNotesConversations(accountViewModel, nav)
2 -> TabFollows(baseUser, accountViewModel, navController) 2 -> TabFollows(baseUser, accountViewModel, nav)
3 -> TabFollowers(baseUser, accountViewModel, navController) 3 -> TabFollowers(baseUser, accountViewModel, nav)
4 -> TabReceivedZaps(baseUser, accountViewModel, navController) 4 -> TabReceivedZaps(baseUser, accountViewModel, nav)
5 -> TabBookmarks(baseUser, accountViewModel, navController) 5 -> TabBookmarks(baseUser, accountViewModel, nav)
6 -> TabReports(baseUser, accountViewModel, navController) 6 -> TabReports(baseUser, accountViewModel, nav)
7 -> TabRelays(baseUser, accountViewModel) 7 -> TabRelays(baseUser, accountViewModel)
} }
} }
@@ -331,7 +330,7 @@ private fun FollowTabHeader(baseUser: User) {
@Composable @Composable
private fun ProfileHeader( private fun ProfileHeader(
baseUser: User, baseUser: User,
navController: NavController, nav: (String) -> Unit,
account: Account, account: Account,
accountViewModel: AccountViewModel accountViewModel: AccountViewModel
) { ) {
@@ -412,7 +411,7 @@ private fun ProfileHeader(
.height(35.dp) .height(35.dp)
.padding(bottom = 3.dp) .padding(bottom = 3.dp)
) { ) {
MessageButton(baseUser, navController) MessageButton(baseUser, nav)
// No need for this button anymore // No need for this button anymore
// NPubCopyButton(baseUser) // NPubCopyButton(baseUser)
@@ -421,7 +420,7 @@ private fun ProfileHeader(
} }
} }
DrawAdditionalInfo(baseUser, account, accountViewModel, navController) DrawAdditionalInfo(baseUser, account, accountViewModel, nav)
Divider(modifier = Modifier.padding(top = 6.dp)) Divider(modifier = Modifier.padding(top = 6.dp))
} }
@@ -468,7 +467,7 @@ private fun ProfileActions(
} }
@Composable @Composable
private fun DrawAdditionalInfo(baseUser: User, account: Account, accountViewModel: AccountViewModel, navController: NavController) { private fun DrawAdditionalInfo(baseUser: User, account: Account, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
val userState by baseUser.live().metadata.observeAsState() val userState by baseUser.live().metadata.observeAsState()
val user = remember(userState) { userState?.user } ?: return val user = remember(userState) { userState?.user } ?: return
val tags = remember(userState) { userState?.user?.info?.latestMetadata?.tags } val tags = remember(userState) { userState?.user?.info?.latestMetadata?.tags }
@@ -534,7 +533,7 @@ private fun DrawAdditionalInfo(baseUser: User, account: Account, accountViewMode
user, user,
onScan = { onScan = {
dialogOpen = false dialogOpen = false
navController.navigate(it) nav(it)
}, },
onClose = { dialogOpen = false } onClose = { dialogOpen = false }
) )
@@ -553,7 +552,7 @@ private fun DrawAdditionalInfo(baseUser: User, account: Account, accountViewMode
} }
} }
DisplayBadges(baseUser, navController) DisplayBadges(baseUser, nav)
DisplayNip05ProfileStatus(user) DisplayNip05ProfileStatus(user)
@@ -613,7 +612,7 @@ private fun DrawAdditionalInfo(baseUser: User, account: Account, accountViewMode
tags = null, tags = null,
backgroundColor = MaterialTheme.colors.background, backgroundColor = MaterialTheme.colors.background,
accountViewModel = accountViewModel, accountViewModel = accountViewModel,
navController = navController nav = nav
) )
} }
} }
@@ -701,7 +700,7 @@ private fun DisplayLNAddress(
@OptIn(ExperimentalLayoutApi::class) @OptIn(ExperimentalLayoutApi::class)
private fun DisplayBadges( private fun DisplayBadges(
baseUser: User, baseUser: User,
navController: NavController nav: (String) -> Unit
) { ) {
val userBadgeState by baseUser.live().badges.observeAsState() val userBadgeState by baseUser.live().badges.observeAsState()
val userBadge = remember(userBadgeState) { userBadgeState?.user } ?: return val userBadge = remember(userBadgeState) { userBadgeState?.user } ?: return
@@ -716,7 +715,7 @@ private fun DisplayBadges(
val baseBadgeDefinition = badgeAwardState?.note?.replyTo?.firstOrNull() val baseBadgeDefinition = badgeAwardState?.note?.replyTo?.firstOrNull()
if (baseBadgeDefinition != null) { if (baseBadgeDefinition != null) {
BadgeThumb(baseBadgeDefinition, navController, 35.dp) BadgeThumb(baseBadgeDefinition, nav, 35.dp)
} }
} }
} }
@@ -728,12 +727,12 @@ private fun DisplayBadges(
@Composable @Composable
fun BadgeThumb( fun BadgeThumb(
note: Note, note: Note,
navController: NavController, nav: (String) -> Unit,
size: Dp, size: Dp,
pictureModifier: Modifier = Modifier pictureModifier: Modifier = Modifier
) { ) {
BadgeThumb(note, size, pictureModifier) { BadgeThumb(note, size, pictureModifier) {
navController.navigate("Note/${it.idHex}") nav("Note/${it.idHex}")
} }
} }
@@ -831,7 +830,7 @@ private fun DrawBanner(baseUser: User) {
} }
@Composable @Composable
fun TabNotesNewThreads(accountViewModel: AccountViewModel, navController: NavController) { fun TabNotesNewThreads(accountViewModel: AccountViewModel, nav: (String) -> Unit) {
val feedViewModel: NostrUserProfileNewThreadsFeedViewModel = viewModel() val feedViewModel: NostrUserProfileNewThreadsFeedViewModel = viewModel()
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
@@ -842,13 +841,13 @@ fun TabNotesNewThreads(accountViewModel: AccountViewModel, navController: NavCon
Column( Column(
modifier = Modifier.padding(vertical = 0.dp) modifier = Modifier.padding(vertical = 0.dp)
) { ) {
FeedView(feedViewModel, accountViewModel, navController, null, enablePullRefresh = false) FeedView(feedViewModel, accountViewModel, nav, null, enablePullRefresh = false)
} }
} }
} }
@Composable @Composable
fun TabNotesConversations(accountViewModel: AccountViewModel, navController: NavController) { fun TabNotesConversations(accountViewModel: AccountViewModel, nav: (String) -> Unit) {
val feedViewModel: NostrUserProfileConversationsFeedViewModel = viewModel() val feedViewModel: NostrUserProfileConversationsFeedViewModel = viewModel()
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
@@ -859,13 +858,13 @@ fun TabNotesConversations(accountViewModel: AccountViewModel, navController: Nav
Column( Column(
modifier = Modifier.padding(vertical = 0.dp) modifier = Modifier.padding(vertical = 0.dp)
) { ) {
FeedView(feedViewModel, accountViewModel, navController, null, enablePullRefresh = false) FeedView(feedViewModel, accountViewModel, nav, null, enablePullRefresh = false)
} }
} }
} }
@Composable @Composable
fun TabBookmarks(baseUser: User, accountViewModel: AccountViewModel, navController: NavController) { fun TabBookmarks(baseUser: User, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
val feedViewModel: NostrUserProfileBookmarksFeedViewModel = viewModel() val feedViewModel: NostrUserProfileBookmarksFeedViewModel = viewModel()
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
@@ -876,13 +875,13 @@ fun TabBookmarks(baseUser: User, accountViewModel: AccountViewModel, navControll
Column( Column(
modifier = Modifier.padding(vertical = 0.dp) modifier = Modifier.padding(vertical = 0.dp)
) { ) {
FeedView(feedViewModel, accountViewModel, navController, null, enablePullRefresh = false) FeedView(feedViewModel, accountViewModel, nav, null, enablePullRefresh = false)
} }
} }
} }
@Composable @Composable
fun TabFollows(baseUser: User, accountViewModel: AccountViewModel, navController: NavController) { fun TabFollows(baseUser: User, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
val feedViewModel: NostrUserProfileFollowsUserFeedViewModel = viewModel() val feedViewModel: NostrUserProfileFollowsUserFeedViewModel = viewModel()
val userState by baseUser.live().follows.observeAsState() val userState by baseUser.live().follows.observeAsState()
@@ -895,13 +894,13 @@ fun TabFollows(baseUser: User, accountViewModel: AccountViewModel, navController
Column( Column(
modifier = Modifier.padding(vertical = 0.dp) modifier = Modifier.padding(vertical = 0.dp)
) { ) {
UserFeedView(feedViewModel, accountViewModel, navController, enablePullRefresh = false) UserFeedView(feedViewModel, accountViewModel, nav, enablePullRefresh = false)
} }
} }
} }
@Composable @Composable
fun TabFollowers(baseUser: User, accountViewModel: AccountViewModel, navController: NavController) { fun TabFollowers(baseUser: User, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
val feedViewModel: NostrUserProfileFollowersUserFeedViewModel = viewModel() val feedViewModel: NostrUserProfileFollowersUserFeedViewModel = viewModel()
val userState by baseUser.live().follows.observeAsState() val userState by baseUser.live().follows.observeAsState()
@@ -914,13 +913,13 @@ fun TabFollowers(baseUser: User, accountViewModel: AccountViewModel, navControll
Column( Column(
modifier = Modifier.padding(vertical = 0.dp) modifier = Modifier.padding(vertical = 0.dp)
) { ) {
UserFeedView(feedViewModel, accountViewModel, navController, enablePullRefresh = false) UserFeedView(feedViewModel, accountViewModel, nav, enablePullRefresh = false)
} }
} }
} }
@Composable @Composable
fun TabReceivedZaps(baseUser: User, accountViewModel: AccountViewModel, navController: NavController) { fun TabReceivedZaps(baseUser: User, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
val feedViewModel: NostrUserProfileZapsFeedViewModel = viewModel() val feedViewModel: NostrUserProfileZapsFeedViewModel = viewModel()
val userState by baseUser.live().zaps.observeAsState() val userState by baseUser.live().zaps.observeAsState()
@@ -933,13 +932,13 @@ fun TabReceivedZaps(baseUser: User, accountViewModel: AccountViewModel, navContr
Column( Column(
modifier = Modifier.padding(vertical = 0.dp) modifier = Modifier.padding(vertical = 0.dp)
) { ) {
LnZapFeedView(feedViewModel, accountViewModel, navController, enablePullRefresh = false) LnZapFeedView(feedViewModel, accountViewModel, nav, enablePullRefresh = false)
} }
} }
} }
@Composable @Composable
fun TabReports(baseUser: User, accountViewModel: AccountViewModel, navController: NavController) { fun TabReports(baseUser: User, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
val feedViewModel: NostrUserProfileReportFeedViewModel = viewModel() val feedViewModel: NostrUserProfileReportFeedViewModel = viewModel()
val userState by baseUser.live().reports.observeAsState() val userState by baseUser.live().reports.observeAsState()
@@ -952,7 +951,7 @@ fun TabReports(baseUser: User, accountViewModel: AccountViewModel, navController
Column( Column(
modifier = Modifier.padding(vertical = 0.dp) modifier = Modifier.padding(vertical = 0.dp)
) { ) {
FeedView(feedViewModel, accountViewModel, navController, null, enablePullRefresh = false) FeedView(feedViewModel, accountViewModel, nav, null, enablePullRefresh = false)
} }
} }
} }
@@ -993,12 +992,12 @@ fun TabRelays(user: User, accountViewModel: AccountViewModel) {
} }
@Composable @Composable
private fun MessageButton(user: User, navController: NavController) { private fun MessageButton(user: User, nav: (String) -> Unit) {
Button( Button(
modifier = Modifier modifier = Modifier
.padding(horizontal = 3.dp) .padding(horizontal = 3.dp)
.width(50.dp), .width(50.dp),
onClick = { navController.navigate("Room/${user.pubkeyHex}") }, onClick = { nav("Room/${user.pubkeyHex}") },
shape = RoundedCornerShape(20.dp), shape = RoundedCornerShape(20.dp),
colors = ButtonDefaults colors = ButtonDefaults
.buttonColors( .buttonColors(
@@ -48,7 +48,6 @@ import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.Channel import com.vitorpamplona.amethyst.model.Channel
@@ -83,7 +82,7 @@ import kotlinx.coroutines.channels.Channel as CoroutineChannel
fun SearchScreen( fun SearchScreen(
searchFeedViewModel: NostrGlobalFeedViewModel, searchFeedViewModel: NostrGlobalFeedViewModel,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
navController: NavController, nav: (String) -> Unit,
scrollToTop: Boolean = false scrollToTop: Boolean = false
) { ) {
val lifeCycleOwner = LocalLifecycleOwner.current val lifeCycleOwner = LocalLifecycleOwner.current
@@ -124,8 +123,8 @@ fun SearchScreen(
Column( Column(
modifier = Modifier.padding(vertical = 0.dp) modifier = Modifier.padding(vertical = 0.dp)
) { ) {
SearchBar(accountViewModel, navController) SearchBar(accountViewModel, nav)
FeedView(searchFeedViewModel, accountViewModel, navController, null, ScrollStateKeys.GLOBAL_SCREEN, scrollToTop) FeedView(searchFeedViewModel, accountViewModel, nav, null, ScrollStateKeys.GLOBAL_SCREEN, scrollToTop)
} }
} }
} }
@@ -185,7 +184,7 @@ class SearchBarViewModel : ViewModel() {
@OptIn(FlowPreview::class) @OptIn(FlowPreview::class)
@Composable @Composable
private fun SearchBar(accountViewModel: AccountViewModel, navController: NavController) { private fun SearchBar(accountViewModel: AccountViewModel, nav: (String) -> Unit) {
val searchBarViewModel: SearchBarViewModel = viewModel() val searchBarViewModel: SearchBarViewModel = viewModel()
searchBarViewModel.account = accountViewModel.accountLiveData.value?.account searchBarViewModel.account = accountViewModel.accountLiveData.value?.account
@@ -306,12 +305,12 @@ private fun SearchBar(accountViewModel: AccountViewModel, navController: NavCont
) { ) {
itemsIndexed(searchBarViewModel.hashtagResults.value, key = { _, item -> "#" + item }) { _, item -> itemsIndexed(searchBarViewModel.hashtagResults.value, key = { _, item -> "#" + item }) { _, item ->
HashtagLine(item) { HashtagLine(item) {
navController.navigate("Hashtag/$item") nav("Hashtag/$item")
} }
} }
itemsIndexed(searchBarViewModel.searchResults.value, key = { _, item -> "u" + item.pubkeyHex }) { _, item -> itemsIndexed(searchBarViewModel.searchResults.value, key = { _, item -> "u" + item.pubkeyHex }) { _, item ->
UserCompose(item, accountViewModel = accountViewModel, navController = navController) UserCompose(item, accountViewModel = accountViewModel, nav = nav)
} }
itemsIndexed(searchBarViewModel.searchResultsChannels.value, key = { _, item -> "c" + item.idHex }) { _, item -> itemsIndexed(searchBarViewModel.searchResultsChannels.value, key = { _, item -> "c" + item.idHex }) { _, item ->
@@ -327,12 +326,12 @@ private fun SearchBar(accountViewModel: AccountViewModel, navController: NavCont
channelLastTime = null, channelLastTime = null,
channelLastContent = item.info.about, channelLastContent = item.info.about,
false, false,
onClick = { navController.navigate("Channel/${item.idHex}") } onClick = { nav("Channel/${item.idHex}") }
) )
} }
itemsIndexed(searchBarViewModel.searchResultsNotes.value, key = { _, item -> "n" + item.idHex }) { _, item -> itemsIndexed(searchBarViewModel.searchResultsNotes.value, key = { _, item -> "n" + item.idHex }) { _, item ->
NoteCompose(item, accountViewModel = accountViewModel, navController = navController) NoteCompose(item, accountViewModel = accountViewModel, nav = nav)
} }
} }
} }
@@ -14,14 +14,13 @@ import androidx.compose.ui.unit.dp
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import com.vitorpamplona.amethyst.service.NostrThreadDataSource import com.vitorpamplona.amethyst.service.NostrThreadDataSource
import com.vitorpamplona.amethyst.ui.dal.ThreadFeedFilter import com.vitorpamplona.amethyst.ui.dal.ThreadFeedFilter
import com.vitorpamplona.amethyst.ui.screen.NostrThreadFeedViewModel import com.vitorpamplona.amethyst.ui.screen.NostrThreadFeedViewModel
import com.vitorpamplona.amethyst.ui.screen.ThreadFeedView import com.vitorpamplona.amethyst.ui.screen.ThreadFeedView
@Composable @Composable
fun ThreadScreen(noteId: String?, accountViewModel: AccountViewModel, navController: NavController) { fun ThreadScreen(noteId: String?, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
val account by accountViewModel.accountLiveData.observeAsState() val account by accountViewModel.accountLiveData.observeAsState()
val lifeCycleOwner = LocalLifecycleOwner.current val lifeCycleOwner = LocalLifecycleOwner.current
@@ -63,7 +62,7 @@ fun ThreadScreen(noteId: String?, accountViewModel: AccountViewModel, navControl
Column( Column(
modifier = Modifier.padding(vertical = 0.dp) modifier = Modifier.padding(vertical = 0.dp)
) { ) {
ThreadFeedView(noteId, feedViewModel, accountViewModel, navController) ThreadFeedView(noteId, feedViewModel, accountViewModel, nav)
} }
} }
} }
@@ -59,7 +59,6 @@ import androidx.compose.ui.unit.sp
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import com.google.accompanist.flowlayout.FlowRow import com.google.accompanist.flowlayout.FlowRow
import com.google.accompanist.permissions.ExperimentalPermissionsApi import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.isGranted import com.google.accompanist.permissions.isGranted
@@ -99,7 +98,7 @@ import kotlinx.coroutines.launch
fun VideoScreen( fun VideoScreen(
videoFeedView: NostrVideoFeedViewModel, videoFeedView: NostrVideoFeedViewModel,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
navController: NavController, nav: (String) -> Unit,
scrollToTop: Boolean = false scrollToTop: Boolean = false
) { ) {
val lifeCycleOwner = LocalLifecycleOwner.current val lifeCycleOwner = LocalLifecycleOwner.current
@@ -142,7 +141,7 @@ fun VideoScreen(
Column( Column(
modifier = Modifier.padding(vertical = 0.dp) modifier = Modifier.padding(vertical = 0.dp)
) { ) {
FeedView(videoFeedView, accountViewModel, navController, ScrollStateKeys.VIDEO_SCREEN, scrollToTop) FeedView(videoFeedView, accountViewModel, nav, ScrollStateKeys.VIDEO_SCREEN, scrollToTop)
} }
} }
} }
@@ -151,7 +150,7 @@ fun VideoScreen(
fun FeedView( fun FeedView(
videoFeedView: NostrVideoFeedViewModel, videoFeedView: NostrVideoFeedViewModel,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
navController: NavController, nav: (String) -> Unit,
scrollStateKey: String? = null, scrollStateKey: String? = null,
scrollToTop: Boolean = false scrollToTop: Boolean = false
) { ) {
@@ -176,7 +175,7 @@ fun FeedView(
SlidingCarousel( SlidingCarousel(
state.feed, state.feed,
accountViewModel, accountViewModel,
navController, nav,
scrollStateKey, scrollStateKey,
scrollToTop scrollToTop
) )
@@ -196,7 +195,7 @@ fun FeedView(
fun SlidingCarousel( fun SlidingCarousel(
feed: MutableState<List<Note>>, feed: MutableState<List<Note>>,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
navController: NavController, nav: (String) -> Unit,
scrollStateKey: String? = null, scrollStateKey: String? = null,
scrollToTop: Boolean = false scrollToTop: Boolean = false
) { ) {
@@ -222,7 +221,7 @@ fun SlidingCarousel(
} }
) { index -> ) { index ->
feed.value.getOrNull(index)?.let { note -> feed.value.getOrNull(index)?.let { note ->
RenderVideoOrPictureNote(note, accountViewModel, navController) RenderVideoOrPictureNote(note, accountViewModel, nav)
} }
} }
} }
@@ -231,7 +230,7 @@ fun SlidingCarousel(
private fun RenderVideoOrPictureNote( private fun RenderVideoOrPictureNote(
note: Note, note: Note,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
navController: NavController nav: (String) -> Unit
) { ) {
val noteEvent = note.event val noteEvent = note.event
@@ -255,7 +254,7 @@ private fun RenderVideoOrPictureNote(
Column(Modifier.weight(1f)) { Column(Modifier.weight(1f)) {
Row(Modifier.padding(10.dp), verticalAlignment = Alignment.Bottom) { Row(Modifier.padding(10.dp), verticalAlignment = Alignment.Bottom) {
Column(Modifier.size(55.dp), verticalArrangement = Arrangement.Center) { Column(Modifier.size(55.dp), verticalArrangement = Arrangement.Center) {
NoteAuthorPicture(note, navController, loggedIn, 55.dp) NoteAuthorPicture(note, nav, loggedIn, 55.dp)
} }
Column( Column(
@@ -299,7 +298,7 @@ private fun RenderVideoOrPictureNote(
verticalArrangement = Arrangement.Center verticalArrangement = Arrangement.Center
) { ) {
Row(horizontalArrangement = Arrangement.Center) { Row(horizontalArrangement = Arrangement.Center) {
ReactionsColumn(note, accountViewModel, navController) ReactionsColumn(note, accountViewModel, nav)
} }
} }
} }
@@ -342,7 +341,7 @@ private fun RelayBadges(baseNote: Note) {
} }
@Composable @Composable
fun ReactionsColumn(baseNote: Note, accountViewModel: AccountViewModel, navController: NavController) { fun ReactionsColumn(baseNote: Note, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
val accountState by accountViewModel.accountLiveData.observeAsState() val accountState by accountViewModel.accountLiveData.observeAsState()
val account = accountState?.account ?: return val account = accountState?.account ?: return
@@ -355,11 +354,11 @@ fun ReactionsColumn(baseNote: Note, accountViewModel: AccountViewModel, navContr
} }
if (wantsToReplyTo != null) { if (wantsToReplyTo != null) {
NewPostView({ wantsToReplyTo = null }, wantsToReplyTo, null, account, accountViewModel, navController) NewPostView({ wantsToReplyTo = null }, wantsToReplyTo, null, account, accountViewModel, nav)
} }
if (wantsToQuote != null) { if (wantsToQuote != null) {
NewPostView({ wantsToQuote = null }, null, wantsToQuote, account, accountViewModel, navController) NewPostView({ wantsToQuote = null }, null, wantsToQuote, account, accountViewModel, nav)
} }
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
@@ -380,7 +379,7 @@ fun ReactionsColumn(baseNote: Note, accountViewModel: AccountViewModel, navContr
@OptIn(ExperimentalPermissionsApi::class) @OptIn(ExperimentalPermissionsApi::class)
@Composable @Composable
fun NewImageButton(accountViewModel: AccountViewModel, navController: NavController) { fun NewImageButton(accountViewModel: AccountViewModel, nav: (String) -> Unit) {
var wantsToPost by remember { var wantsToPost by remember {
mutableStateOf(false) mutableStateOf(false)
} }
@@ -397,15 +396,7 @@ fun NewImageButton(accountViewModel: AccountViewModel, navController: NavControl
// awaits an refresh on the list // awaits an refresh on the list
delay(250) delay(250)
val route = Route.Video.route.replace("{scrollToTop}", "true") val route = Route.Video.route.replace("{scrollToTop}", "true")
navController.navigate(route) { nav(route)
navController.graph.startDestinationRoute?.let { start ->
popUpTo(start) { inclusive = false }
restoreState = true
}
launchSingleTop = true
restoreState = true
}
} }
} }
@@ -445,7 +436,7 @@ fun NewImageButton(accountViewModel: AccountViewModel, navController: NavControl
onClose = { pickedURI = null }, onClose = { pickedURI = null },
postViewModel = postViewModel, postViewModel = postViewModel,
accountViewModel = accountViewModel, accountViewModel = accountViewModel,
navController = navController nav = nav
) )
} }
@@ -30,7 +30,6 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.text.withStyle import androidx.compose.ui.text.withStyle
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.core.os.ConfigurationCompat import androidx.core.os.ConfigurationCompat
import androidx.navigation.NavController
import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.service.lang.LanguageTranslatorService import com.vitorpamplona.amethyst.service.lang.LanguageTranslatorService
import com.vitorpamplona.amethyst.service.lang.ResultOrError import com.vitorpamplona.amethyst.service.lang.ResultOrError
@@ -47,7 +46,7 @@ fun TranslatableRichTextViewer(
tags: List<List<String>>?, tags: List<List<String>>?,
backgroundColor: Color, backgroundColor: Color,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
navController: NavController nav: (String) -> Unit
) { ) {
var translatedTextState by remember { var translatedTextState by remember {
mutableStateOf(ResultOrError(content, null, null, null)) mutableStateOf(ResultOrError(content, null, null, null))
@@ -93,7 +92,7 @@ fun TranslatableRichTextViewer(
tags, tags,
backgroundColor, backgroundColor,
accountViewModel, accountViewModel,
navController nav
) )
val target = translatedTextState.targetLang val target = translatedTextState.targetLang