Support for Following Hashtags
This commit is contained in:
@@ -91,10 +91,12 @@ class Account(
|
|||||||
|
|
||||||
val contactList = userProfile().latestContactList
|
val contactList = userProfile().latestContactList
|
||||||
val follows = contactList?.follows() ?: emptyList()
|
val follows = contactList?.follows() ?: emptyList()
|
||||||
|
val followsTags = contactList?.unverifiedFollowTagSet() ?: emptyList()
|
||||||
|
|
||||||
if (contactList != null && follows.isNotEmpty()) {
|
if (contactList != null && follows.isNotEmpty()) {
|
||||||
val event = ContactListEvent.create(
|
val event = ContactListEvent.create(
|
||||||
follows,
|
follows,
|
||||||
|
followsTags,
|
||||||
relays,
|
relays,
|
||||||
loggedIn.privKey!!
|
loggedIn.privKey!!
|
||||||
)
|
)
|
||||||
@@ -102,7 +104,7 @@ class Account(
|
|||||||
Client.send(event)
|
Client.send(event)
|
||||||
LocalCache.consume(event)
|
LocalCache.consume(event)
|
||||||
} else {
|
} else {
|
||||||
val event = ContactListEvent.create(listOf(), relays, loggedIn.privKey!!)
|
val event = ContactListEvent.create(listOf(), listOf(), relays, loggedIn.privKey!!)
|
||||||
|
|
||||||
// Keep this local to avoid erasing a good contact list.
|
// Keep this local to avoid erasing a good contact list.
|
||||||
// Client.send(event)
|
// Client.send(event)
|
||||||
@@ -246,11 +248,13 @@ class Account(
|
|||||||
if (!isWriteable()) return
|
if (!isWriteable()) return
|
||||||
|
|
||||||
val contactList = userProfile().latestContactList
|
val contactList = userProfile().latestContactList
|
||||||
val follows = contactList?.follows() ?: emptyList()
|
val followingUsers = contactList?.follows() ?: emptyList()
|
||||||
|
val followingTags = contactList?.unverifiedFollowTagSet() ?: emptyList()
|
||||||
|
|
||||||
val event = if (contactList != null && follows.isNotEmpty()) {
|
val event = if (contactList != null) {
|
||||||
ContactListEvent.create(
|
ContactListEvent.create(
|
||||||
follows.plus(Contact(user.pubkeyHex, null)),
|
followingUsers.plus(Contact(user.pubkeyHex, null)),
|
||||||
|
followingTags,
|
||||||
contactList.relays(),
|
contactList.relays(),
|
||||||
loggedIn.privKey!!
|
loggedIn.privKey!!
|
||||||
)
|
)
|
||||||
@@ -258,6 +262,35 @@ class Account(
|
|||||||
val relays = Constants.defaultRelays.associate { it.url to ContactListEvent.ReadWrite(it.read, it.write) }
|
val relays = Constants.defaultRelays.associate { it.url to ContactListEvent.ReadWrite(it.read, it.write) }
|
||||||
ContactListEvent.create(
|
ContactListEvent.create(
|
||||||
listOf(Contact(user.pubkeyHex, null)),
|
listOf(Contact(user.pubkeyHex, null)),
|
||||||
|
followingTags,
|
||||||
|
relays,
|
||||||
|
loggedIn.privKey!!
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Client.send(event)
|
||||||
|
LocalCache.consume(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun follow(tag: String) {
|
||||||
|
if (!isWriteable()) return
|
||||||
|
|
||||||
|
val contactList = userProfile().latestContactList
|
||||||
|
val followingUsers = contactList?.follows() ?: emptyList()
|
||||||
|
val followingTags = contactList?.unverifiedFollowTagSet() ?: emptyList()
|
||||||
|
|
||||||
|
val event = if (contactList != null) {
|
||||||
|
ContactListEvent.create(
|
||||||
|
followingUsers,
|
||||||
|
followingTags.plus(tag),
|
||||||
|
contactList.relays(),
|
||||||
|
loggedIn.privKey!!
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
val relays = Constants.defaultRelays.associate { it.url to ContactListEvent.ReadWrite(it.read, it.write) }
|
||||||
|
ContactListEvent.create(
|
||||||
|
followingUsers,
|
||||||
|
followingTags.plus(tag),
|
||||||
relays,
|
relays,
|
||||||
loggedIn.privKey!!
|
loggedIn.privKey!!
|
||||||
)
|
)
|
||||||
@@ -271,11 +304,33 @@ class Account(
|
|||||||
if (!isWriteable()) return
|
if (!isWriteable()) return
|
||||||
|
|
||||||
val contactList = userProfile().latestContactList
|
val contactList = userProfile().latestContactList
|
||||||
val follows = contactList?.follows() ?: emptyList()
|
val followingUsers = contactList?.follows() ?: emptyList()
|
||||||
|
val followingTags = contactList?.unverifiedFollowTagSet() ?: emptyList()
|
||||||
|
|
||||||
if (contactList != null && follows.isNotEmpty()) {
|
if (contactList != null && (followingUsers.isNotEmpty() || followingTags.isNotEmpty())) {
|
||||||
val event = ContactListEvent.create(
|
val event = ContactListEvent.create(
|
||||||
follows.filter { it.pubKeyHex != user.pubkeyHex },
|
followingUsers.filter { it.pubKeyHex != user.pubkeyHex },
|
||||||
|
followingTags,
|
||||||
|
contactList.relays(),
|
||||||
|
loggedIn.privKey!!
|
||||||
|
)
|
||||||
|
|
||||||
|
Client.send(event)
|
||||||
|
LocalCache.consume(event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun unfollow(tag: String) {
|
||||||
|
if (!isWriteable()) return
|
||||||
|
|
||||||
|
val contactList = userProfile().latestContactList
|
||||||
|
val followingUsers = contactList?.follows() ?: emptyList()
|
||||||
|
val followingTags = contactList?.unverifiedFollowTagSet() ?: emptyList()
|
||||||
|
|
||||||
|
if (contactList != null && (followingUsers.isNotEmpty() || followingTags.isNotEmpty())) {
|
||||||
|
val event = ContactListEvent.create(
|
||||||
|
followingUsers,
|
||||||
|
followingTags.filter { it != tag },
|
||||||
contactList.relays(),
|
contactList.relays(),
|
||||||
loggedIn.privKey!!
|
loggedIn.privKey!!
|
||||||
)
|
)
|
||||||
@@ -503,7 +558,11 @@ class Account(
|
|||||||
fun isHidden(user: User) = user.pubkeyHex in hiddenUsers || user.pubkeyHex in transientHiddenUsers
|
fun isHidden(user: User) = user.pubkeyHex in hiddenUsers || user.pubkeyHex in transientHiddenUsers
|
||||||
|
|
||||||
fun followingKeySet(): Set<HexKey> {
|
fun followingKeySet(): Set<HexKey> {
|
||||||
return userProfile().latestContactList?.verifiedFollowKeySet ?: emptySet()
|
return userProfile().cachedFollowingKeySet() ?: emptySet()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun followingTagSet(): Set<HexKey> {
|
||||||
|
return userProfile().cachedFollowingTagSet() ?: emptySet()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isAcceptable(user: User): Boolean {
|
fun isAcceptable(user: User): Boolean {
|
||||||
|
|||||||
@@ -249,6 +249,12 @@ class User(val pubkeyHex: String) {
|
|||||||
} ?: false
|
} ?: false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun isFollowingHashtag(tag: String): Boolean {
|
||||||
|
return latestContactList?.unverifiedFollowTagSet()?.toSet()?.let {
|
||||||
|
return tag in it
|
||||||
|
} ?: false
|
||||||
|
}
|
||||||
|
|
||||||
fun isFollowingCached(user: User): Boolean {
|
fun isFollowingCached(user: User): Boolean {
|
||||||
return latestContactList?.verifiedFollowKeySet?.let {
|
return latestContactList?.verifiedFollowKeySet?.let {
|
||||||
return user.pubkeyHex in it
|
return user.pubkeyHex in it
|
||||||
@@ -267,6 +273,10 @@ class User(val pubkeyHex: String) {
|
|||||||
return latestContactList?.verifiedFollowKeySet ?: emptySet()
|
return latestContactList?.verifiedFollowKeySet ?: emptySet()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun cachedFollowingTagSet(): Set<HexKey> {
|
||||||
|
return latestContactList?.verifiedFollowTagSet ?: emptySet()
|
||||||
|
}
|
||||||
|
|
||||||
fun cachedFollowCount(): Int? {
|
fun cachedFollowCount(): Int? {
|
||||||
return latestContactList?.verifiedFollowKeySet?.size
|
return latestContactList?.verifiedFollowKeySet?.size
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,9 +58,28 @@ object NostrHomeDataSource : NostrDataSource("HomeFeed") {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun createFollowTagsFilter(): TypedFilter? {
|
||||||
|
val hashToLoad = account.followingTagSet()
|
||||||
|
|
||||||
|
if (hashToLoad.isEmpty()) return null
|
||||||
|
|
||||||
|
return TypedFilter(
|
||||||
|
types = setOf(FeedType.FOLLOWS),
|
||||||
|
filter = JsonFilter(
|
||||||
|
kinds = listOf(TextNoteEvent.kind, LongTextNoteEvent.kind),
|
||||||
|
tags = mapOf(
|
||||||
|
"t" to hashToLoad.map {
|
||||||
|
listOf(it, it.lowercase(), it.uppercase(), it.capitalize())
|
||||||
|
}.flatten()
|
||||||
|
),
|
||||||
|
limit = 100
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
val followAccountChannel = requestNewChannel()
|
val followAccountChannel = requestNewChannel()
|
||||||
|
|
||||||
override fun updateChannelFilters() {
|
override fun updateChannelFilters() {
|
||||||
followAccountChannel.typedFilters = listOf(createFollowAccountsFilter()).ifEmpty { null }
|
followAccountChannel.typedFilters = listOfNotNull(createFollowAccountsFilter(), createFollowTagsFilter()).ifEmpty { null }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,11 +33,16 @@ class ContactListEvent(
|
|||||||
}.toSet()
|
}.toSet()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val verifiedFollowTagSet: Set<String> by lazy {
|
||||||
|
unverifiedFollowTagSet().map { it.lowercase() }.toSet()
|
||||||
|
}
|
||||||
|
|
||||||
val verifiedFollowKeySetAndMe: Set<HexKey> by lazy {
|
val verifiedFollowKeySetAndMe: Set<HexKey> by lazy {
|
||||||
verifiedFollowKeySet + pubKey
|
verifiedFollowKeySet + pubKey
|
||||||
}
|
}
|
||||||
|
|
||||||
fun unverifiedFollowKeySet() = tags.filter { it[0] == "p" }.mapNotNull { it.getOrNull(1) }
|
fun unverifiedFollowKeySet() = tags.filter { it[0] == "p" }.mapNotNull { it.getOrNull(1) }
|
||||||
|
fun unverifiedFollowTagSet() = tags.filter { it[0] == "t" }.mapNotNull { it.getOrNull(1) }
|
||||||
|
|
||||||
fun follows() = tags.filter { it[0] == "p" }.mapNotNull {
|
fun follows() = tags.filter { it[0] == "p" }.mapNotNull {
|
||||||
try {
|
try {
|
||||||
@@ -48,6 +53,10 @@ class ContactListEvent(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun followsTags() = tags.filter { it[0] == "t" }.mapNotNull {
|
||||||
|
it.getOrNull(2)
|
||||||
|
}
|
||||||
|
|
||||||
fun relays(): Map<String, ReadWrite>? = try {
|
fun relays(): Map<String, ReadWrite>? = try {
|
||||||
if (content.isNotEmpty()) {
|
if (content.isNotEmpty()) {
|
||||||
gson.fromJson(content, object : TypeToken<Map<String, ReadWrite>>() {}.type) as Map<String, ReadWrite>
|
gson.fromJson(content, object : TypeToken<Map<String, ReadWrite>>() {}.type) as Map<String, ReadWrite>
|
||||||
@@ -62,7 +71,7 @@ class ContactListEvent(
|
|||||||
companion object {
|
companion object {
|
||||||
const val kind = 3
|
const val kind = 3
|
||||||
|
|
||||||
fun create(follows: List<Contact>, relayUse: Map<String, ReadWrite>?, privateKey: ByteArray, createdAt: Long = Date().time / 1000): ContactListEvent {
|
fun create(follows: List<Contact>, followTags: List<String>, relayUse: Map<String, ReadWrite>?, privateKey: ByteArray, createdAt: Long = Date().time / 1000): ContactListEvent {
|
||||||
val content = if (relayUse != null) {
|
val content = if (relayUse != null) {
|
||||||
gson.toJson(relayUse)
|
gson.toJson(relayUse)
|
||||||
} else {
|
} else {
|
||||||
@@ -75,7 +84,10 @@ class ContactListEvent(
|
|||||||
} else {
|
} else {
|
||||||
listOf("p", it.pubKeyHex)
|
listOf("p", it.pubKeyHex)
|
||||||
}
|
}
|
||||||
|
} + followTags.map {
|
||||||
|
listOf("t", it)
|
||||||
}
|
}
|
||||||
|
|
||||||
val id = generateId(pubKey, createdAt, kind, tags, content)
|
val id = generateId(pubKey, createdAt, kind, tags, content)
|
||||||
val sig = Utils.sign(id, privateKey)
|
val sig = Utils.sign(id, privateKey)
|
||||||
return ContactListEvent(id.toHexKey(), pubKey, createdAt, tags, content, sig.toHexKey())
|
return ContactListEvent(id.toHexKey(), pubKey, createdAt, tags, content, sig.toHexKey())
|
||||||
|
|||||||
@@ -44,6 +44,8 @@ open class Event(
|
|||||||
override fun isTaggedUser(idHex: String) = tags.any { it.getOrNull(0) == "p" && it.getOrNull(1) == idHex }
|
override fun isTaggedUser(idHex: String) = tags.any { it.getOrNull(0) == "p" && it.getOrNull(1) == idHex }
|
||||||
|
|
||||||
override fun isTaggedHash(hashtag: String) = tags.any { it.getOrNull(0) == "t" && it.getOrNull(1).equals(hashtag, true) }
|
override fun isTaggedHash(hashtag: String) = tags.any { it.getOrNull(0) == "t" && it.getOrNull(1).equals(hashtag, true) }
|
||||||
|
override fun isTaggedHashes(hashtags: Set<String>) = tags.any { it.getOrNull(0) == "t" && it.getOrNull(1)?.lowercase() in hashtags }
|
||||||
|
override fun firstIsTaggedHashes(hashtags: Set<String>) = tags.firstOrNull { it.getOrNull(0) == "t" && it.getOrNull(1)?.lowercase() in hashtags }?.getOrNull(1)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the ID is correct and then if the pubKey's secret key signed the event.
|
* Checks if the ID is correct and then if the pubKey's secret key signed the event.
|
||||||
|
|||||||
@@ -26,4 +26,6 @@ interface EventInterface {
|
|||||||
fun isTaggedUser(loggedInUser: String): Boolean
|
fun isTaggedUser(loggedInUser: String): Boolean
|
||||||
|
|
||||||
fun isTaggedHash(hashtag: String): Boolean
|
fun isTaggedHash(hashtag: String): Boolean
|
||||||
|
fun isTaggedHashes(hashtag: Set<String>): Boolean
|
||||||
|
fun firstIsTaggedHashes(hashtag: Set<String>): String?
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,11 +12,12 @@ object HomeConversationsFeedFilter : FeedFilter<Note>() {
|
|||||||
override fun feed(): List<Note> {
|
override fun feed(): List<Note> {
|
||||||
val user = account.userProfile()
|
val user = account.userProfile()
|
||||||
val followingKeySet = user.cachedFollowingKeySet()
|
val followingKeySet = user.cachedFollowingKeySet()
|
||||||
|
val followingTagSet = user.cachedFollowingTagSet()
|
||||||
|
|
||||||
return LocalCache.notes.values
|
return LocalCache.notes.values
|
||||||
.filter {
|
.filter {
|
||||||
(it.event is TextNoteEvent || it.event is RepostEvent) &&
|
(it.event is TextNoteEvent || it.event is RepostEvent) &&
|
||||||
it.author?.pubkeyHex in followingKeySet &&
|
(it.author?.pubkeyHex in followingKeySet || (it.event?.isTaggedHashes(followingTagSet) ?: false)) &&
|
||||||
// && account.isAcceptable(it) // This filter follows only. No need to check if acceptable
|
// && account.isAcceptable(it) // This filter follows only. No need to check if acceptable
|
||||||
it.author?.let { !account.isHidden(it) } ?: true &&
|
it.author?.let { !account.isHidden(it) } ?: true &&
|
||||||
!it.isNewThread()
|
!it.isNewThread()
|
||||||
|
|||||||
@@ -13,11 +13,12 @@ object HomeNewThreadFeedFilter : FeedFilter<Note>() {
|
|||||||
override fun feed(): List<Note> {
|
override fun feed(): List<Note> {
|
||||||
val user = account.userProfile()
|
val user = account.userProfile()
|
||||||
val followingKeySet = user.cachedFollowingKeySet()
|
val followingKeySet = user.cachedFollowingKeySet()
|
||||||
|
val followingTagSet = user.cachedFollowingTagSet()
|
||||||
|
|
||||||
val notes = LocalCache.notes.values
|
val notes = LocalCache.notes.values
|
||||||
.filter { it ->
|
.filter { it ->
|
||||||
(it.event is TextNoteEvent || it.event is RepostEvent || it.event is LongTextNoteEvent) &&
|
(it.event is TextNoteEvent || it.event is RepostEvent || it.event is LongTextNoteEvent) &&
|
||||||
it.author?.pubkeyHex in followingKeySet &&
|
(it.author?.pubkeyHex in followingKeySet || (it.event?.isTaggedHashes(followingTagSet) ?: false)) &&
|
||||||
// && account.isAcceptable(it) // This filter follows only. No need to check if acceptable
|
// && account.isAcceptable(it) // This filter follows only. No need to check if acceptable
|
||||||
it.author?.let { !account.isHidden(it) } ?: true &&
|
it.author?.let { !account.isHidden(it) } ?: true &&
|
||||||
it.isNewThread()
|
it.isNewThread()
|
||||||
@@ -26,7 +27,7 @@ object HomeNewThreadFeedFilter : FeedFilter<Note>() {
|
|||||||
val longFormNotes = LocalCache.addressables.values
|
val longFormNotes = LocalCache.addressables.values
|
||||||
.filter { it ->
|
.filter { it ->
|
||||||
(it.event is TextNoteEvent || it.event is RepostEvent || it.event is LongTextNoteEvent) &&
|
(it.event is TextNoteEvent || it.event is RepostEvent || it.event is LongTextNoteEvent) &&
|
||||||
it.author?.pubkeyHex in followingKeySet &&
|
(it.author?.pubkeyHex in followingKeySet || (it.event?.isTaggedHashes(followingTagSet) ?: false)) &&
|
||||||
// && account.isAcceptable(it) // This filter follows only. No need to check if acceptable
|
// && account.isAcceptable(it) // This filter follows only. No need to check if acceptable
|
||||||
it.author?.let { !account.isHidden(it) } ?: true &&
|
it.author?.let { !account.isHidden(it) } ?: true &&
|
||||||
it.isNewThread()
|
it.isNewThread()
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import androidx.compose.foundation.layout.*
|
|||||||
import androidx.compose.foundation.shape.CircleShape
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
import androidx.compose.foundation.shape.CutCornerShape
|
import androidx.compose.foundation.shape.CutCornerShape
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.foundation.text.ClickableText
|
||||||
import androidx.compose.material.*
|
import androidx.compose.material.*
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.ExpandMore
|
import androidx.compose.material.icons.filled.ExpandMore
|
||||||
@@ -268,6 +269,15 @@ fun NoteCompose(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val firstTag = noteEvent.firstIsTaggedHashes(account.followingTagSet())
|
||||||
|
if (firstTag != null) {
|
||||||
|
ClickableText(
|
||||||
|
text = AnnotatedString(" #$firstTag"),
|
||||||
|
onClick = { navController.navigate("Hashtag/$firstTag") },
|
||||||
|
style = LocalTextStyle.current.copy(color = MaterialTheme.colors.primary.copy(alpha = 0.52f))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
timeAgo(note.createdAt(), context = context),
|
timeAgo(note.createdAt(), context = context),
|
||||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
|
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import androidx.compose.runtime.DisposableEffect
|
|||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
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.rememberCoroutineScope
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalLifecycleOwner
|
import androidx.compose.ui.platform.LocalLifecycleOwner
|
||||||
@@ -22,10 +23,13 @@ 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 androidx.navigation.NavController
|
||||||
|
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
|
||||||
import com.vitorpamplona.amethyst.ui.screen.FeedView
|
import com.vitorpamplona.amethyst.ui.screen.FeedView
|
||||||
import com.vitorpamplona.amethyst.ui.screen.NostrHashtagFeedViewModel
|
import com.vitorpamplona.amethyst.ui.screen.NostrHashtagFeedViewModel
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun HashtagScreen(tag: String?, accountViewModel: AccountViewModel, navController: NavController) {
|
fun HashtagScreen(tag: String?, accountViewModel: AccountViewModel, navController: NavController) {
|
||||||
@@ -70,7 +74,7 @@ fun HashtagScreen(tag: String?, accountViewModel: AccountViewModel, navControlle
|
|||||||
Column(
|
Column(
|
||||||
modifier = Modifier.padding(vertical = 0.dp)
|
modifier = Modifier.padding(vertical = 0.dp)
|
||||||
) {
|
) {
|
||||||
HashtagHeader(tag)
|
HashtagHeader(tag, account)
|
||||||
FeedView(feedViewModel, accountViewModel, navController, null)
|
FeedView(feedViewModel, accountViewModel, navController, null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -78,7 +82,12 @@ fun HashtagScreen(tag: String?, accountViewModel: AccountViewModel, navControlle
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun HashtagHeader(tag: String) {
|
fun HashtagHeader(tag: String, account: Account) {
|
||||||
|
val userState by account.userProfile().live().follows.observeAsState()
|
||||||
|
val userFollows = userState?.user ?: return
|
||||||
|
|
||||||
|
val coroutineScope = rememberCoroutineScope()
|
||||||
|
|
||||||
Column() {
|
Column() {
|
||||||
Column(modifier = Modifier.padding(12.dp)) {
|
Column(modifier = Modifier.padding(12.dp)) {
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
@@ -94,8 +103,15 @@ fun HashtagHeader(tag: String) {
|
|||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
"#$tag",
|
"#$tag",
|
||||||
fontWeight = FontWeight.Bold
|
fontWeight = FontWeight.Bold,
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (userFollows.isFollowingHashtag(tag)) {
|
||||||
|
UnfollowButton { coroutineScope.launch(Dispatchers.IO) { account.unfollow(tag) } }
|
||||||
|
} else {
|
||||||
|
FollowButton { coroutineScope.launch(Dispatchers.IO) { account.follow(tag) } }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user