mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2025-09-27 22:16:22 +02:00
Thread View
This commit is contained in:
@@ -4,6 +4,9 @@ import androidx.lifecycle.LiveData
|
|||||||
import com.vitorpamplona.amethyst.service.NostrSingleEventDataSource
|
import com.vitorpamplona.amethyst.service.NostrSingleEventDataSource
|
||||||
import com.vitorpamplona.amethyst.ui.note.toDisplayHex
|
import com.vitorpamplona.amethyst.ui.note.toDisplayHex
|
||||||
import fr.acinq.secp256k1.Hex
|
import fr.acinq.secp256k1.Hex
|
||||||
|
import java.time.Instant
|
||||||
|
import java.time.ZoneId
|
||||||
|
import java.time.format.DateTimeFormatter
|
||||||
import java.util.Collections
|
import java.util.Collections
|
||||||
import nostr.postr.events.Event
|
import nostr.postr.events.Event
|
||||||
|
|
||||||
@@ -29,6 +32,33 @@ class Note(val idHex: String) {
|
|||||||
refreshObservers()
|
refreshObservers()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun formattedDateTime(timestamp: Long): String {
|
||||||
|
return Instant.ofEpochSecond(timestamp).atZone(ZoneId.systemDefault())
|
||||||
|
.format(DateTimeFormatter.ofPattern("uuuu-MM-dd-HH:mm:ss"))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun replyLevelSignature(): String {
|
||||||
|
val replyTo = replyTo
|
||||||
|
if (replyTo == null || replyTo.isEmpty()) {
|
||||||
|
return "/" + formattedDateTime(event?.createdAt ?: 0) + ";"
|
||||||
|
}
|
||||||
|
|
||||||
|
return replyTo
|
||||||
|
.map { it.replyLevelSignature() }
|
||||||
|
.maxBy { it.length }.removeSuffix(";") + "/" + formattedDateTime(event?.createdAt ?: 0) + ";"
|
||||||
|
}
|
||||||
|
|
||||||
|
fun replyLevel(): Int {
|
||||||
|
val replyTo = replyTo
|
||||||
|
if (replyTo == null || replyTo.isEmpty()) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return replyTo.maxOf {
|
||||||
|
it.replyLevel()
|
||||||
|
} + 1
|
||||||
|
}
|
||||||
|
|
||||||
fun addReply(note: Note) {
|
fun addReply(note: Note) {
|
||||||
if (replies.add(note))
|
if (replies.add(note))
|
||||||
refreshObservers()
|
refreshObservers()
|
||||||
|
@@ -21,18 +21,26 @@ object NostrSingleEventDataSource: NostrDataSource("SingleEventFeed") {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun createLoadEventsIfNotLoadedFilter(): JsonFilter? {
|
fun createLoadEventsIfNotLoadedFilter(): JsonFilter? {
|
||||||
val eventsToLoad = eventsToWatch
|
val directEventsToLoad = eventsToWatch
|
||||||
.map { LocalCache.notes[it] }
|
.mapNotNull { LocalCache.notes[it] }
|
||||||
.filterNotNull()
|
|
||||||
.filter { it.event == null }
|
.filter { it.event == null }
|
||||||
|
|
||||||
|
val threadingEventsToLoad = eventsToWatch
|
||||||
|
.mapNotNull { LocalCache.notes[it] }
|
||||||
|
.mapNotNull { it.replyTo }
|
||||||
|
.flatten()
|
||||||
|
.filter { it.event == null }
|
||||||
|
|
||||||
|
val interestedEvents =
|
||||||
|
(directEventsToLoad + threadingEventsToLoad)
|
||||||
.map { it.idHex.substring(0, 8) }
|
.map { it.idHex.substring(0, 8) }
|
||||||
|
|
||||||
if (eventsToLoad.isEmpty()) {
|
if (interestedEvents.isEmpty()) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
return JsonFilter(
|
return JsonFilter(
|
||||||
ids = eventsToLoad
|
ids = interestedEvents
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -0,0 +1,82 @@
|
|||||||
|
package com.vitorpamplona.amethyst.service
|
||||||
|
|
||||||
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
|
import java.util.Collections
|
||||||
|
import nostr.postr.JsonFilter
|
||||||
|
|
||||||
|
object NostrThreadDataSource: NostrDataSource("SingleThreadFeed") {
|
||||||
|
val eventsToWatch = Collections.synchronizedList(mutableListOf<String>())
|
||||||
|
|
||||||
|
fun createRepliesAndReactionsFilter(): JsonFilter? {
|
||||||
|
val reactionsToWatch = eventsToWatch.map { it.substring(0, 8) }
|
||||||
|
|
||||||
|
if (reactionsToWatch.isEmpty()) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return JsonFilter(
|
||||||
|
tags = mapOf("e" to reactionsToWatch)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createLoadEventsIfNotLoadedFilter(): JsonFilter? {
|
||||||
|
val eventsToLoad = eventsToWatch
|
||||||
|
.map { LocalCache.notes[it] }
|
||||||
|
.filterNotNull()
|
||||||
|
.filter { it.event == null }
|
||||||
|
.map { it.idHex.substring(0, 8) }
|
||||||
|
|
||||||
|
if (eventsToLoad.isEmpty()) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return JsonFilter(
|
||||||
|
ids = eventsToLoad
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val repliesAndReactionsChannel = requestNewChannel()
|
||||||
|
val loadEventsChannel = requestNewChannel()
|
||||||
|
|
||||||
|
override fun feed(): List<Note> {
|
||||||
|
return eventsToWatch.map {
|
||||||
|
LocalCache.notes[it]
|
||||||
|
}.filterNotNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun updateChannelFilters() {
|
||||||
|
repliesAndReactionsChannel.filter = createRepliesAndReactionsFilter()
|
||||||
|
loadEventsChannel.filter = createLoadEventsIfNotLoadedFilter()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun loadThread(noteId: String) {
|
||||||
|
val note = LocalCache.notes[noteId] ?: return
|
||||||
|
|
||||||
|
val thread = mutableListOf<Note>()
|
||||||
|
val threadSet = mutableSetOf<Note>()
|
||||||
|
|
||||||
|
val threadRoot = note.replyTo?.firstOrNull() ?: note
|
||||||
|
|
||||||
|
loadDown(threadRoot, thread, threadSet)
|
||||||
|
|
||||||
|
// Currently orders by date of each event, descending, at each level of the reply stack
|
||||||
|
val order = compareByDescending<Note> { it.replyLevelSignature() }
|
||||||
|
|
||||||
|
eventsToWatch.clear()
|
||||||
|
eventsToWatch.addAll(thread.sortedWith(order).map { it.idHex })
|
||||||
|
|
||||||
|
resetFilters()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun loadDown(note: Note, thread: MutableList<Note>, threadSet: MutableSet<Note>) {
|
||||||
|
if (note !in threadSet) {
|
||||||
|
thread.add(note)
|
||||||
|
threadSet.add(note)
|
||||||
|
|
||||||
|
note.replies.forEach {
|
||||||
|
loadDown(it, thread, threadSet)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -15,6 +15,7 @@ import com.vitorpamplona.amethyst.service.NostrHomeDataSource
|
|||||||
import com.vitorpamplona.amethyst.service.NostrNotificationDataSource
|
import com.vitorpamplona.amethyst.service.NostrNotificationDataSource
|
||||||
import com.vitorpamplona.amethyst.service.NostrSingleEventDataSource
|
import com.vitorpamplona.amethyst.service.NostrSingleEventDataSource
|
||||||
import com.vitorpamplona.amethyst.service.NostrSingleUserDataSource
|
import com.vitorpamplona.amethyst.service.NostrSingleUserDataSource
|
||||||
|
import com.vitorpamplona.amethyst.service.NostrThreadDataSource
|
||||||
import com.vitorpamplona.amethyst.service.relays.Client
|
import com.vitorpamplona.amethyst.service.relays.Client
|
||||||
import com.vitorpamplona.amethyst.ui.screen.AccountScreen
|
import com.vitorpamplona.amethyst.ui.screen.AccountScreen
|
||||||
import com.vitorpamplona.amethyst.ui.screen.AccountStateViewModel
|
import com.vitorpamplona.amethyst.ui.screen.AccountStateViewModel
|
||||||
@@ -54,6 +55,7 @@ class MainActivity : ComponentActivity() {
|
|||||||
NostrNotificationDataSource.stop()
|
NostrNotificationDataSource.stop()
|
||||||
NostrSingleEventDataSource.stop()
|
NostrSingleEventDataSource.stop()
|
||||||
NostrSingleUserDataSource.stop()
|
NostrSingleUserDataSource.stop()
|
||||||
|
NostrThreadDataSource.stop()
|
||||||
Client.disconnect()
|
Client.disconnect()
|
||||||
super.onPause()
|
super.onPause()
|
||||||
}
|
}
|
||||||
|
@@ -4,6 +4,7 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.navigation.NavHostController
|
import androidx.navigation.NavHostController
|
||||||
import androidx.navigation.compose.NavHost
|
import androidx.navigation.compose.NavHost
|
||||||
import androidx.navigation.compose.composable
|
import androidx.navigation.compose.composable
|
||||||
|
import androidx.navigation.compose.rememberNavController
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -13,7 +14,7 @@ fun AppNavigation(
|
|||||||
) {
|
) {
|
||||||
NavHost(navController, startDestination = Route.Home.route) {
|
NavHost(navController, startDestination = Route.Home.route) {
|
||||||
Routes.forEach {
|
Routes.forEach {
|
||||||
composable(it.route, content = it.buildScreen(accountViewModel))
|
composable(it.route, it.arguments, content = it.buildScreen(accountViewModel, navController))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -2,12 +2,17 @@ package com.vitorpamplona.amethyst.ui.navigation
|
|||||||
|
|
||||||
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.NavBackStackEntry
|
import androidx.navigation.NavBackStackEntry
|
||||||
|
import androidx.navigation.NavController
|
||||||
import androidx.navigation.NavHostController
|
import androidx.navigation.NavHostController
|
||||||
|
import androidx.navigation.NavType
|
||||||
import androidx.navigation.compose.currentBackStackEntryAsState
|
import androidx.navigation.compose.currentBackStackEntryAsState
|
||||||
|
import androidx.navigation.navArgument
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.ui.screen.HomeScreen
|
import com.vitorpamplona.amethyst.ui.screen.HomeScreen
|
||||||
import com.vitorpamplona.amethyst.ui.screen.MessageScreen
|
import com.vitorpamplona.amethyst.ui.screen.MessageScreen
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.ThreadScreen
|
||||||
import com.vitorpamplona.amethyst.ui.screen.NotificationScreen
|
import com.vitorpamplona.amethyst.ui.screen.NotificationScreen
|
||||||
import com.vitorpamplona.amethyst.ui.screen.ProfileScreen
|
import com.vitorpamplona.amethyst.ui.screen.ProfileScreen
|
||||||
import com.vitorpamplona.amethyst.ui.screen.SearchScreen
|
import com.vitorpamplona.amethyst.ui.screen.SearchScreen
|
||||||
@@ -17,17 +22,23 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
|||||||
sealed class Route(
|
sealed class Route(
|
||||||
val route: String,
|
val route: String,
|
||||||
val icon: Int,
|
val icon: Int,
|
||||||
val buildScreen: (AccountViewModel) -> @Composable (NavBackStackEntry) -> Unit
|
val arguments: List<NamedNavArgument> = emptyList(),
|
||||||
|
val buildScreen: (AccountViewModel, NavController) -> @Composable (NavBackStackEntry) -> Unit
|
||||||
) {
|
) {
|
||||||
object Home : Route("Home", R.drawable.ic_home, { acc -> { _ -> HomeScreen(acc) } })
|
object Home : Route("Home", R.drawable.ic_home, buildScreen = { acc, nav -> { _ -> HomeScreen(acc, nav) } })
|
||||||
object Search : Route("Search", R.drawable.ic_search, { acc -> { _ -> SearchScreen(acc) }})
|
object Search : Route("Search", R.drawable.ic_search, buildScreen = { acc, nav -> { _ -> SearchScreen(acc, nav) }})
|
||||||
object Notification : Route("Notification", R.drawable.ic_notifications, { acc -> { _ -> NotificationScreen(acc) }})
|
object Notification : Route("Notification", R.drawable.ic_notifications,buildScreen = { acc, nav -> { _ -> NotificationScreen(acc, nav) }})
|
||||||
object Message : Route("Message", R.drawable.ic_dm, { acc -> { _ -> MessageScreen(acc) }})
|
object Message : Route("Message", R.drawable.ic_dm, buildScreen = { acc, nav -> { _ -> MessageScreen(acc) }})
|
||||||
object Profile : Route("Profile", R.drawable.ic_profile, { acc -> { _ -> ProfileScreen(acc) }})
|
object Profile : Route("Profile", R.drawable.ic_profile, buildScreen = { acc, nav -> { _ -> ProfileScreen(acc) }})
|
||||||
object Lists : Route("Lists", R.drawable.ic_lists, { acc -> { _ -> ProfileScreen(acc) }})
|
object Lists : Route("Lists", R.drawable.ic_lists, buildScreen = { acc, nav -> { _ -> ProfileScreen(acc) }})
|
||||||
object Topics : Route("Topics", R.drawable.ic_topics, { acc -> { _ -> ProfileScreen(acc) }})
|
object Topics : Route("Topics", R.drawable.ic_topics, buildScreen = { acc, nav -> { _ -> ProfileScreen(acc) }})
|
||||||
object Bookmarks : Route("Bookmarks", R.drawable.ic_bookmarks, { acc -> { _ -> ProfileScreen(acc) }})
|
object Bookmarks : Route("Bookmarks", R.drawable.ic_bookmarks, buildScreen = { acc, nav -> { _ -> ProfileScreen(acc) }})
|
||||||
object Moments : Route("Moments", R.drawable.ic_moments, { acc -> { _ -> ProfileScreen(acc) }})
|
object Moments : Route("Moments", R.drawable.ic_moments, buildScreen = { acc, nav -> { _ -> ProfileScreen(acc) }})
|
||||||
|
|
||||||
|
object Note : Route("Note/{id}", R.drawable.ic_moments,
|
||||||
|
arguments = listOf(navArgument("id") { type = NavType.StringType } ),
|
||||||
|
buildScreen = { acc, nav -> { ThreadScreen(it.arguments?.getString("id"), acc, nav) }}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val Routes = listOf(
|
val Routes = listOf(
|
||||||
@@ -42,7 +53,10 @@ val Routes = listOf(
|
|||||||
Route.Lists,
|
Route.Lists,
|
||||||
Route.Topics,
|
Route.Topics,
|
||||||
Route.Bookmarks,
|
Route.Bookmarks,
|
||||||
Route.Moments
|
Route.Moments,
|
||||||
|
|
||||||
|
//inner
|
||||||
|
Route.Note
|
||||||
)
|
)
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
@@ -12,7 +12,7 @@ import androidx.compose.ui.graphics.Color
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun BlankNote(modifier: Modifier = Modifier, isQuote: Boolean) {
|
fun BlankNote(modifier: Modifier = Modifier, isQuote: Boolean = false) {
|
||||||
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)) {
|
||||||
|
@@ -26,6 +26,7 @@ import androidx.compose.ui.graphics.Color
|
|||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
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 coil.compose.AsyncImage
|
import coil.compose.AsyncImage
|
||||||
import com.google.accompanist.flowlayout.FlowRow
|
import com.google.accompanist.flowlayout.FlowRow
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
@@ -39,7 +40,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
|||||||
import nostr.postr.events.TextNoteEvent
|
import nostr.postr.events.TextNoteEvent
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun BoostSetCompose(likeSetCard: BoostSetCard, modifier: Modifier = Modifier, isInnerNote: Boolean = false, accountViewModel: AccountViewModel) {
|
fun BoostSetCompose(likeSetCard: BoostSetCard, modifier: Modifier = Modifier, isInnerNote: Boolean = false, accountViewModel: AccountViewModel, navController: NavController) {
|
||||||
val noteState by likeSetCard.note.live.observeAsState()
|
val noteState by likeSetCard.note.live.observeAsState()
|
||||||
val note = noteState?.note
|
val note = noteState?.note
|
||||||
|
|
||||||
@@ -84,7 +85,7 @@ fun BoostSetCompose(likeSetCard: BoostSetCard, modifier: Modifier = Modifier, is
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
NoteCompose(note, modifier = Modifier.padding(top = 5.dp), isInnerNote = true, accountViewModel = accountViewModel)
|
NoteCompose(note, Modifier.padding(top = 5.dp), true, accountViewModel, navController)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -26,6 +26,7 @@ import androidx.compose.ui.graphics.Color
|
|||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
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 coil.compose.AsyncImage
|
import coil.compose.AsyncImage
|
||||||
import com.google.accompanist.flowlayout.FlowRow
|
import com.google.accompanist.flowlayout.FlowRow
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
@@ -38,7 +39,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
|||||||
import nostr.postr.events.TextNoteEvent
|
import nostr.postr.events.TextNoteEvent
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun LikeSetCompose(likeSetCard: LikeSetCard, modifier: Modifier = Modifier, isInnerNote: Boolean = false, accountViewModel: AccountViewModel) {
|
fun LikeSetCompose(likeSetCard: LikeSetCard, modifier: Modifier = Modifier, isInnerNote: Boolean = false, accountViewModel: AccountViewModel, navController: NavController) {
|
||||||
val noteState by likeSetCard.note.live.observeAsState()
|
val noteState by likeSetCard.note.live.observeAsState()
|
||||||
val note = noteState?.note
|
val note = noteState?.note
|
||||||
|
|
||||||
@@ -83,7 +84,7 @@ fun LikeSetCompose(likeSetCard: LikeSetCard, modifier: Modifier = Modifier, isIn
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
NoteCompose(note, modifier = Modifier.padding(top = 5.dp), isInnerNote = true, accountViewModel = accountViewModel)
|
NoteCompose(note, Modifier.padding(top = 5.dp), true, accountViewModel, navController)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -4,6 +4,7 @@ import android.text.format.DateUtils
|
|||||||
import android.text.format.DateUtils.getRelativeTimeSpanString
|
import android.text.format.DateUtils.getRelativeTimeSpanString
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.border
|
import androidx.compose.foundation.border
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
@@ -21,6 +22,8 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
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 androidx.navigation.compose.rememberNavController
|
||||||
import coil.compose.AsyncImage
|
import coil.compose.AsyncImage
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
import com.vitorpamplona.amethyst.service.model.ReactionEvent
|
import com.vitorpamplona.amethyst.service.model.ReactionEvent
|
||||||
@@ -30,7 +33,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
|||||||
import nostr.postr.events.TextNoteEvent
|
import nostr.postr.events.TextNoteEvent
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun NoteCompose(baseNote: Note, modifier: Modifier = Modifier, isInnerNote: Boolean = false, accountViewModel: AccountViewModel) {
|
fun NoteCompose(baseNote: Note, modifier: Modifier = Modifier, isInnerNote: Boolean = false, accountViewModel: AccountViewModel, navController: NavController) {
|
||||||
val noteState by baseNote.live.observeAsState()
|
val noteState by baseNote.live.observeAsState()
|
||||||
val note = noteState?.note
|
val note = noteState?.note
|
||||||
|
|
||||||
@@ -41,7 +44,14 @@ fun NoteCompose(baseNote: Note, modifier: Modifier = Modifier, isInnerNote: Bool
|
|||||||
val author = authorState?.user
|
val author = authorState?.user
|
||||||
|
|
||||||
Column(modifier = modifier) {
|
Column(modifier = modifier) {
|
||||||
Row(modifier = Modifier.padding(horizontal = if (!isInnerNote) 12.dp else 0.dp)) {
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(
|
||||||
|
start = if (!isInnerNote) 12.dp else 0.dp,
|
||||||
|
end = if (!isInnerNote) 12.dp else 0.dp,
|
||||||
|
top = 10.dp)
|
||||||
|
.clickable ( onClick = { navController.navigate("Note/${note.idHex}") } )
|
||||||
|
) {
|
||||||
|
|
||||||
// Draws the boosted picture outside the boosted card.
|
// Draws the boosted picture outside the boosted card.
|
||||||
if (!isInnerNote) {
|
if (!isInnerNote) {
|
||||||
@@ -100,7 +110,8 @@ fun NoteCompose(baseNote: Note, modifier: Modifier = Modifier, isInnerNote: Bool
|
|||||||
note,
|
note,
|
||||||
modifier = Modifier.padding(top = 5.dp),
|
modifier = Modifier.padding(top = 5.dp),
|
||||||
isInnerNote = true,
|
isInnerNote = true,
|
||||||
accountViewModel = accountViewModel
|
accountViewModel = accountViewModel,
|
||||||
|
navController = navController
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,7 +132,7 @@ fun NoteCompose(baseNote: Note, modifier: Modifier = Modifier, isInnerNote: Bool
|
|||||||
ReactionsRowState(note, accountViewModel)
|
ReactionsRowState(note, accountViewModel)
|
||||||
|
|
||||||
Divider(
|
Divider(
|
||||||
modifier = Modifier.padding(vertical = 10.dp),
|
modifier = Modifier.padding(top = 10.dp),
|
||||||
thickness = 0.25.dp
|
thickness = 0.25.dp
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@@ -10,6 +10,7 @@ import com.vitorpamplona.amethyst.service.NostrHomeDataSource
|
|||||||
import com.vitorpamplona.amethyst.service.NostrNotificationDataSource
|
import com.vitorpamplona.amethyst.service.NostrNotificationDataSource
|
||||||
import com.vitorpamplona.amethyst.service.NostrSingleEventDataSource
|
import com.vitorpamplona.amethyst.service.NostrSingleEventDataSource
|
||||||
import com.vitorpamplona.amethyst.service.NostrSingleUserDataSource
|
import com.vitorpamplona.amethyst.service.NostrSingleUserDataSource
|
||||||
|
import com.vitorpamplona.amethyst.service.NostrThreadDataSource
|
||||||
import fr.acinq.secp256k1.Hex
|
import fr.acinq.secp256k1.Hex
|
||||||
import java.util.regex.Pattern
|
import java.util.regex.Pattern
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
@@ -65,6 +66,7 @@ class AccountStateViewModel(private val encryptedPreferences: EncryptedSharedPre
|
|||||||
NostrNotificationDataSource.start()
|
NostrNotificationDataSource.start()
|
||||||
NostrSingleEventDataSource.start()
|
NostrSingleEventDataSource.start()
|
||||||
NostrSingleUserDataSource.start()
|
NostrSingleUserDataSource.start()
|
||||||
|
NostrThreadDataSource.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun newKey() {
|
fun newKey() {
|
||||||
|
@@ -23,6 +23,7 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.compose.ExperimentalLifecycleComposeApi
|
import androidx.lifecycle.compose.ExperimentalLifecycleComposeApi
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
|
import androidx.navigation.NavController
|
||||||
import com.google.accompanist.swiperefresh.SwipeRefresh
|
import com.google.accompanist.swiperefresh.SwipeRefresh
|
||||||
import com.google.accompanist.swiperefresh.rememberSwipeRefreshState
|
import com.google.accompanist.swiperefresh.rememberSwipeRefreshState
|
||||||
import com.vitorpamplona.amethyst.ui.note.BoostSetCompose
|
import com.vitorpamplona.amethyst.ui.note.BoostSetCompose
|
||||||
@@ -32,7 +33,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
|||||||
|
|
||||||
@OptIn(ExperimentalLifecycleComposeApi::class)
|
@OptIn(ExperimentalLifecycleComposeApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun CardFeedView(viewModel: CardFeedViewModel, accountViewModel: AccountViewModel) {
|
fun CardFeedView(viewModel: CardFeedViewModel, accountViewModel: AccountViewModel, navController: NavController) {
|
||||||
val feedState by viewModel.feedContent.collectAsStateWithLifecycle()
|
val feedState by viewModel.feedContent.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
var isRefreshing by remember { mutableStateOf(false) }
|
var isRefreshing by remember { mutableStateOf(false) }
|
||||||
@@ -76,9 +77,9 @@ fun CardFeedView(viewModel: CardFeedViewModel, accountViewModel: AccountViewMode
|
|||||||
) {
|
) {
|
||||||
itemsIndexed(state.feed) { index, item ->
|
itemsIndexed(state.feed) { index, item ->
|
||||||
when (item) {
|
when (item) {
|
||||||
is NoteCard -> NoteCompose(item.note, isInnerNote = false, accountViewModel = accountViewModel)
|
is NoteCard -> NoteCompose(item.note, isInnerNote = false, accountViewModel = accountViewModel, navController = navController)
|
||||||
is LikeSetCard -> LikeSetCompose(item, isInnerNote = false, accountViewModel = accountViewModel)
|
is LikeSetCard -> LikeSetCompose(item, isInnerNote = false, accountViewModel = accountViewModel, navController = navController)
|
||||||
is BoostSetCard -> BoostSetCompose(item, isInnerNote = false, accountViewModel = accountViewModel)
|
is BoostSetCard -> BoostSetCompose(item, isInnerNote = false, accountViewModel = accountViewModel, navController = navController)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -23,6 +23,7 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.compose.ExperimentalLifecycleComposeApi
|
import androidx.lifecycle.compose.ExperimentalLifecycleComposeApi
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
|
import androidx.navigation.NavController
|
||||||
import com.google.accompanist.swiperefresh.SwipeRefresh
|
import com.google.accompanist.swiperefresh.SwipeRefresh
|
||||||
import com.google.accompanist.swiperefresh.rememberSwipeRefreshState
|
import com.google.accompanist.swiperefresh.rememberSwipeRefreshState
|
||||||
import com.vitorpamplona.amethyst.ui.note.NoteCompose
|
import com.vitorpamplona.amethyst.ui.note.NoteCompose
|
||||||
@@ -30,7 +31,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
|||||||
|
|
||||||
@OptIn(ExperimentalLifecycleComposeApi::class)
|
@OptIn(ExperimentalLifecycleComposeApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun FeedView(viewModel: FeedViewModel, accountViewModel: AccountViewModel) {
|
fun FeedView(viewModel: FeedViewModel, accountViewModel: AccountViewModel, navController: NavController) {
|
||||||
val feedState by viewModel.feedContent.collectAsStateWithLifecycle()
|
val feedState by viewModel.feedContent.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
var isRefreshing by remember { mutableStateOf(false) }
|
var isRefreshing by remember { mutableStateOf(false) }
|
||||||
@@ -73,7 +74,7 @@ fun FeedView(viewModel: FeedViewModel, accountViewModel: AccountViewModel) {
|
|||||||
state = listState
|
state = listState
|
||||||
) {
|
) {
|
||||||
itemsIndexed(state.feed, key = { _, item -> item.idHex }) { index, item ->
|
itemsIndexed(state.feed, key = { _, item -> item.idHex }) { index, item ->
|
||||||
NoteCompose(item, isInnerNote = false, accountViewModel = accountViewModel)
|
NoteCompose(item, isInnerNote = false, accountViewModel = accountViewModel, navController = navController)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -6,7 +6,6 @@ import com.vitorpamplona.amethyst.model.LocalCache
|
|||||||
import com.vitorpamplona.amethyst.model.LocalCacheState
|
import com.vitorpamplona.amethyst.model.LocalCacheState
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
import com.vitorpamplona.amethyst.service.NostrDataSource
|
import com.vitorpamplona.amethyst.service.NostrDataSource
|
||||||
import com.vitorpamplona.amethyst.service.model.ReactionEvent
|
|
||||||
import kotlinx.coroutines.cancel
|
import kotlinx.coroutines.cancel
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
@@ -0,0 +1,227 @@
|
|||||||
|
package com.vitorpamplona.amethyst.ui.screen
|
||||||
|
|
||||||
|
import android.content.res.Resources.Theme
|
||||||
|
import androidx.compose.animation.Crossfade
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.border
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.IntrinsicSize
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.foundation.shape.GenericShape
|
||||||
|
import androidx.compose.material.Button
|
||||||
|
import androidx.compose.material.Divider
|
||||||
|
import androidx.compose.material.MaterialTheme
|
||||||
|
import androidx.compose.material.OutlinedButton
|
||||||
|
import androidx.compose.material.Text
|
||||||
|
import androidx.compose.material.rememberScaffoldState
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.livedata.observeAsState
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.draw.drawBehind
|
||||||
|
import androidx.compose.ui.geometry.Offset
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.Shape
|
||||||
|
import androidx.compose.ui.graphics.drawscope.DrawScope
|
||||||
|
import androidx.compose.ui.platform.debugInspectorInfo
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.lifecycle.compose.ExperimentalLifecycleComposeApi
|
||||||
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
|
import androidx.navigation.NavController
|
||||||
|
import coil.compose.AsyncImage
|
||||||
|
import com.google.accompanist.swiperefresh.SwipeRefresh
|
||||||
|
import com.google.accompanist.swiperefresh.rememberSwipeRefreshState
|
||||||
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
|
import com.vitorpamplona.amethyst.ui.components.RichTextViewer
|
||||||
|
import com.vitorpamplona.amethyst.ui.note.BlankNote
|
||||||
|
import com.vitorpamplona.amethyst.ui.note.NoteCompose
|
||||||
|
import com.vitorpamplona.amethyst.ui.note.ReactionsRowState
|
||||||
|
import com.vitorpamplona.amethyst.ui.note.UserDisplay
|
||||||
|
import com.vitorpamplona.amethyst.ui.note.timeAgo
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
@OptIn(ExperimentalLifecycleComposeApi::class)
|
||||||
|
@Composable
|
||||||
|
fun ThreadFeedView(noteId: String, viewModel: FeedViewModel, accountViewModel: AccountViewModel, navController: NavController) {
|
||||||
|
val feedState by viewModel.feedContent.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
|
var isRefreshing by remember { mutableStateOf(false) }
|
||||||
|
val swipeRefreshState = rememberSwipeRefreshState(isRefreshing)
|
||||||
|
|
||||||
|
val listState = rememberLazyListState()
|
||||||
|
|
||||||
|
LaunchedEffect(isRefreshing) {
|
||||||
|
if (isRefreshing) {
|
||||||
|
viewModel.refresh()
|
||||||
|
isRefreshing = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SwipeRefresh(
|
||||||
|
state = swipeRefreshState,
|
||||||
|
onRefresh = {
|
||||||
|
isRefreshing = true
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
Column() {
|
||||||
|
Crossfade(targetState = feedState) { state ->
|
||||||
|
when (state) {
|
||||||
|
is FeedState.Empty -> {
|
||||||
|
FeedEmpty {
|
||||||
|
isRefreshing = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is FeedState.FeedError -> {
|
||||||
|
FeedError(state.errorMessage) {
|
||||||
|
isRefreshing = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is FeedState.Loaded -> {
|
||||||
|
var noteIdPositionInThread by remember { mutableStateOf(0) }
|
||||||
|
// only in the first transition
|
||||||
|
LaunchedEffect(noteIdPositionInThread) {
|
||||||
|
listState.animateScrollToItem(noteIdPositionInThread, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
val notePosition = state.feed.filter { it.idHex == noteId}.firstOrNull()
|
||||||
|
if (notePosition != null) {
|
||||||
|
noteIdPositionInThread = state.feed.indexOf(notePosition)
|
||||||
|
}
|
||||||
|
|
||||||
|
LazyColumn(
|
||||||
|
contentPadding = PaddingValues(
|
||||||
|
top = 10.dp,
|
||||||
|
bottom = 10.dp
|
||||||
|
),
|
||||||
|
state = listState
|
||||||
|
) {
|
||||||
|
itemsIndexed(state.feed, key = { _, item -> item.idHex }) { index, item ->
|
||||||
|
if (index == 0)
|
||||||
|
NoteMaster(item, accountViewModel = accountViewModel, navController = navController)
|
||||||
|
else {
|
||||||
|
Column() {
|
||||||
|
Row() {
|
||||||
|
NoteCompose(
|
||||||
|
item,
|
||||||
|
Modifier.drawReplyLevel(item.replyLevel(), MaterialTheme.colors.onSurface.copy(alpha = 0.32f)),
|
||||||
|
isInnerNote = false,
|
||||||
|
accountViewModel = accountViewModel,
|
||||||
|
navController = navController,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
FeedState.Loading -> {
|
||||||
|
LoadingFeed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates a Zebra pattern where each bar is a reply level.
|
||||||
|
fun Modifier.drawReplyLevel(level: Int, color: Color): Modifier = this
|
||||||
|
.drawBehind {
|
||||||
|
val paddingDp = 2
|
||||||
|
val strokeWidthDp = 2
|
||||||
|
val levelWidthDp = strokeWidthDp + 1
|
||||||
|
|
||||||
|
val padding = paddingDp.dp.toPx()
|
||||||
|
val strokeWidth = strokeWidthDp.dp.toPx()
|
||||||
|
val levelWidth = levelWidthDp.dp.toPx()
|
||||||
|
|
||||||
|
repeat(level) {
|
||||||
|
this.drawLine(
|
||||||
|
color,
|
||||||
|
Offset(padding + it * levelWidth, 0f),
|
||||||
|
Offset(padding + it * levelWidth, size.height),
|
||||||
|
strokeWidth = strokeWidth
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return@drawBehind
|
||||||
|
}
|
||||||
|
.padding(start = (2 + (level * 3)).dp)
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun NoteMaster(baseNote: Note, accountViewModel: AccountViewModel, navController: NavController) {
|
||||||
|
val noteState by baseNote.live.observeAsState()
|
||||||
|
val note = noteState?.note
|
||||||
|
|
||||||
|
if (note?.event == null) {
|
||||||
|
BlankNote()
|
||||||
|
} else {
|
||||||
|
val authorState by note.author!!.live.observeAsState()
|
||||||
|
val author = authorState?.user
|
||||||
|
|
||||||
|
Column(
|
||||||
|
Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(top = 10.dp)) {
|
||||||
|
Row(modifier = Modifier.padding(start = 12.dp, end = 12.dp)) {
|
||||||
|
// Draws the boosted picture outside the boosted card.
|
||||||
|
AsyncImage(
|
||||||
|
model = author?.profilePicture(),
|
||||||
|
contentDescription = "Profile Image",
|
||||||
|
modifier = Modifier
|
||||||
|
.width(55.dp)
|
||||||
|
.clip(shape = CircleShape)
|
||||||
|
)
|
||||||
|
|
||||||
|
Column(modifier = Modifier.padding(start = 10.dp)) {
|
||||||
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
|
if (author != null)
|
||||||
|
UserDisplay(author)
|
||||||
|
}
|
||||||
|
|
||||||
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
|
Text(
|
||||||
|
timeAgo(note.event?.createdAt),
|
||||||
|
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Row(modifier = Modifier.padding(horizontal = 12.dp)) {
|
||||||
|
Column() {
|
||||||
|
val eventContent = note.event?.content
|
||||||
|
if (eventContent != null)
|
||||||
|
RichTextViewer(eventContent, note.event?.tags)
|
||||||
|
|
||||||
|
ReactionsRowState(note, accountViewModel)
|
||||||
|
|
||||||
|
Divider(
|
||||||
|
modifier = Modifier.padding(top = 10.dp),
|
||||||
|
thickness = 0.25.dp
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -10,12 +10,13 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.compose.ExperimentalLifecycleComposeApi
|
import androidx.lifecycle.compose.ExperimentalLifecycleComposeApi
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import androidx.navigation.NavController
|
||||||
import com.vitorpamplona.amethyst.service.NostrHomeDataSource
|
import com.vitorpamplona.amethyst.service.NostrHomeDataSource
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
|
|
||||||
@OptIn(ExperimentalLifecycleComposeApi::class)
|
@OptIn(ExperimentalLifecycleComposeApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun HomeScreen(accountViewModel: AccountViewModel) {
|
fun HomeScreen(accountViewModel: AccountViewModel, navController: NavController) {
|
||||||
val account by accountViewModel.accountLiveData.observeAsState()
|
val account by accountViewModel.accountLiveData.observeAsState()
|
||||||
|
|
||||||
if (account != null) {
|
if (account != null) {
|
||||||
@@ -25,7 +26,7 @@ fun HomeScreen(accountViewModel: AccountViewModel) {
|
|||||||
Column(
|
Column(
|
||||||
modifier = Modifier.padding(vertical = 0.dp)
|
modifier = Modifier.padding(vertical = 0.dp)
|
||||||
) {
|
) {
|
||||||
FeedView(feedViewModel, accountViewModel)
|
FeedView(feedViewModel, accountViewModel, navController)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -10,12 +10,13 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.compose.ExperimentalLifecycleComposeApi
|
import androidx.lifecycle.compose.ExperimentalLifecycleComposeApi
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import androidx.navigation.NavController
|
||||||
import com.vitorpamplona.amethyst.service.NostrNotificationDataSource
|
import com.vitorpamplona.amethyst.service.NostrNotificationDataSource
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
|
|
||||||
@OptIn(ExperimentalLifecycleComposeApi::class)
|
@OptIn(ExperimentalLifecycleComposeApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun NotificationScreen(accountViewModel: AccountViewModel) {
|
fun NotificationScreen(accountViewModel: AccountViewModel, navController: NavController) {
|
||||||
val account by accountViewModel.accountLiveData.observeAsState()
|
val account by accountViewModel.accountLiveData.observeAsState()
|
||||||
|
|
||||||
if (account != null) {
|
if (account != null) {
|
||||||
@@ -26,7 +27,7 @@ fun NotificationScreen(accountViewModel: AccountViewModel) {
|
|||||||
Column(
|
Column(
|
||||||
modifier = Modifier.padding(vertical = 0.dp)
|
modifier = Modifier.padding(vertical = 0.dp)
|
||||||
) {
|
) {
|
||||||
CardFeedView(feedViewModel, accountViewModel = accountViewModel)
|
CardFeedView(feedViewModel, accountViewModel = accountViewModel, navController)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -8,19 +8,20 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.compose.ExperimentalLifecycleComposeApi
|
import androidx.lifecycle.compose.ExperimentalLifecycleComposeApi
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import androidx.navigation.NavController
|
||||||
import com.vitorpamplona.amethyst.service.NostrGlobalDataSource
|
import com.vitorpamplona.amethyst.service.NostrGlobalDataSource
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
|
|
||||||
@OptIn(ExperimentalLifecycleComposeApi::class)
|
@OptIn(ExperimentalLifecycleComposeApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun SearchScreen(accountViewModel: AccountViewModel) {
|
fun SearchScreen(accountViewModel: AccountViewModel, navController: NavController) {
|
||||||
val feedViewModel: FeedViewModel = viewModel { FeedViewModel( NostrGlobalDataSource ) }
|
val feedViewModel: FeedViewModel = viewModel { FeedViewModel( NostrGlobalDataSource ) }
|
||||||
|
|
||||||
Column(Modifier.fillMaxHeight()) {
|
Column(Modifier.fillMaxHeight()) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.padding(vertical = 0.dp)
|
modifier = Modifier.padding(vertical = 0.dp)
|
||||||
) {
|
) {
|
||||||
FeedView(feedViewModel, accountViewModel)
|
FeedView(feedViewModel, accountViewModel, navController)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,33 @@
|
|||||||
|
package com.vitorpamplona.amethyst.ui.screen
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.livedata.observeAsState
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import androidx.navigation.NavController
|
||||||
|
import com.vitorpamplona.amethyst.service.NostrThreadDataSource
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ThreadScreen(noteId: String?, accountViewModel: AccountViewModel, navController: NavController) {
|
||||||
|
val account by accountViewModel.accountLiveData.observeAsState()
|
||||||
|
|
||||||
|
if (account != null && noteId != null) {
|
||||||
|
NostrThreadDataSource.loadThread(noteId)
|
||||||
|
|
||||||
|
val feedViewModel: FeedViewModel = viewModel { FeedViewModel( NostrThreadDataSource ) }
|
||||||
|
|
||||||
|
Column(Modifier.fillMaxHeight()) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.padding(vertical = 0.dp)
|
||||||
|
) {
|
||||||
|
ThreadFeedView(noteId, feedViewModel, accountViewModel, navController)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user