Allows following of Geohashes
This commit is contained in:
@@ -17,6 +17,7 @@ import com.vitorpamplona.amethyst.service.NostrChatroomDataSource
|
|||||||
import com.vitorpamplona.amethyst.service.NostrChatroomListDataSource
|
import com.vitorpamplona.amethyst.service.NostrChatroomListDataSource
|
||||||
import com.vitorpamplona.amethyst.service.NostrCommunityDataSource
|
import com.vitorpamplona.amethyst.service.NostrCommunityDataSource
|
||||||
import com.vitorpamplona.amethyst.service.NostrDiscoveryDataSource
|
import com.vitorpamplona.amethyst.service.NostrDiscoveryDataSource
|
||||||
|
import com.vitorpamplona.amethyst.service.NostrGeohashDataSource
|
||||||
import com.vitorpamplona.amethyst.service.NostrHashtagDataSource
|
import com.vitorpamplona.amethyst.service.NostrHashtagDataSource
|
||||||
import com.vitorpamplona.amethyst.service.NostrHomeDataSource
|
import com.vitorpamplona.amethyst.service.NostrHomeDataSource
|
||||||
import com.vitorpamplona.amethyst.service.NostrSearchEventOrUserDataSource
|
import com.vitorpamplona.amethyst.service.NostrSearchEventOrUserDataSource
|
||||||
@@ -97,6 +98,7 @@ object ServiceManager {
|
|||||||
|
|
||||||
NostrCommunityDataSource.stop()
|
NostrCommunityDataSource.stop()
|
||||||
NostrHashtagDataSource.stop()
|
NostrHashtagDataSource.stop()
|
||||||
|
NostrGeohashDataSource.stop()
|
||||||
NostrSearchEventOrUserDataSource.stop()
|
NostrSearchEventOrUserDataSource.stop()
|
||||||
NostrSingleChannelDataSource.stop()
|
NostrSingleChannelDataSource.stop()
|
||||||
NostrSingleEventDataSource.stop()
|
NostrSingleEventDataSource.stop()
|
||||||
|
|||||||
@@ -174,6 +174,7 @@ class Account(
|
|||||||
val event = ContactListEvent.createFromScratch(
|
val event = ContactListEvent.createFromScratch(
|
||||||
followUsers = listOf(),
|
followUsers = listOf(),
|
||||||
followTags = listOf(),
|
followTags = listOf(),
|
||||||
|
followGeohashes = listOf(),
|
||||||
followCommunities = listOf(),
|
followCommunities = listOf(),
|
||||||
followEvents = DefaultChannels.toList(),
|
followEvents = DefaultChannels.toList(),
|
||||||
relayUse = relays,
|
relayUse = relays,
|
||||||
@@ -453,6 +454,7 @@ class Account(
|
|||||||
ContactListEvent.createFromScratch(
|
ContactListEvent.createFromScratch(
|
||||||
followUsers = listOf(Contact(user.pubkeyHex, null)),
|
followUsers = listOf(Contact(user.pubkeyHex, null)),
|
||||||
followTags = emptyList(),
|
followTags = emptyList(),
|
||||||
|
followGeohashes = emptyList(),
|
||||||
followCommunities = emptyList(),
|
followCommunities = emptyList(),
|
||||||
followEvents = DefaultChannels.toList(),
|
followEvents = DefaultChannels.toList(),
|
||||||
relayUse = Constants.defaultRelays.associate { it.url to ContactListEvent.ReadWrite(it.read, it.write) },
|
relayUse = Constants.defaultRelays.associate { it.url to ContactListEvent.ReadWrite(it.read, it.write) },
|
||||||
@@ -475,6 +477,7 @@ class Account(
|
|||||||
ContactListEvent.createFromScratch(
|
ContactListEvent.createFromScratch(
|
||||||
followUsers = emptyList(),
|
followUsers = emptyList(),
|
||||||
followTags = emptyList(),
|
followTags = emptyList(),
|
||||||
|
followGeohashes = emptyList(),
|
||||||
followCommunities = emptyList(),
|
followCommunities = emptyList(),
|
||||||
followEvents = DefaultChannels.toList().plus(channel.idHex),
|
followEvents = DefaultChannels.toList().plus(channel.idHex),
|
||||||
relayUse = Constants.defaultRelays.associate { it.url to ContactListEvent.ReadWrite(it.read, it.write) },
|
relayUse = Constants.defaultRelays.associate { it.url to ContactListEvent.ReadWrite(it.read, it.write) },
|
||||||
@@ -498,6 +501,7 @@ class Account(
|
|||||||
ContactListEvent.createFromScratch(
|
ContactListEvent.createFromScratch(
|
||||||
followUsers = emptyList(),
|
followUsers = emptyList(),
|
||||||
followTags = emptyList(),
|
followTags = emptyList(),
|
||||||
|
followGeohashes = emptyList(),
|
||||||
followCommunities = listOf(community.address),
|
followCommunities = listOf(community.address),
|
||||||
followEvents = DefaultChannels.toList(),
|
followEvents = DefaultChannels.toList(),
|
||||||
relayUse = relays,
|
relayUse = relays,
|
||||||
@@ -509,7 +513,7 @@ class Account(
|
|||||||
LocalCache.consume(event)
|
LocalCache.consume(event)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun follow(tag: String) {
|
fun followHashtag(tag: String) {
|
||||||
if (!isWriteable()) return
|
if (!isWriteable()) return
|
||||||
|
|
||||||
val contactList = migrateCommunitiesAndChannelsIfNeeded(userProfile().latestContactList)
|
val contactList = migrateCommunitiesAndChannelsIfNeeded(userProfile().latestContactList)
|
||||||
@@ -524,6 +528,34 @@ class Account(
|
|||||||
ContactListEvent.createFromScratch(
|
ContactListEvent.createFromScratch(
|
||||||
followUsers = emptyList(),
|
followUsers = emptyList(),
|
||||||
followTags = listOf(tag),
|
followTags = listOf(tag),
|
||||||
|
followGeohashes = emptyList(),
|
||||||
|
followCommunities = emptyList(),
|
||||||
|
followEvents = DefaultChannels.toList(),
|
||||||
|
relayUse = Constants.defaultRelays.associate { it.url to ContactListEvent.ReadWrite(it.read, it.write) },
|
||||||
|
privateKey = keyPair.privKey!!
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Client.send(event)
|
||||||
|
LocalCache.consume(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun followGeohash(geohash: String) {
|
||||||
|
if (!isWriteable()) return
|
||||||
|
|
||||||
|
val contactList = migrateCommunitiesAndChannelsIfNeeded(userProfile().latestContactList)
|
||||||
|
|
||||||
|
val event = if (contactList != null) {
|
||||||
|
ContactListEvent.followGeohash(
|
||||||
|
contactList,
|
||||||
|
geohash,
|
||||||
|
keyPair.privKey!!
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
ContactListEvent.createFromScratch(
|
||||||
|
followUsers = emptyList(),
|
||||||
|
followTags = emptyList(),
|
||||||
|
followGeohashes = listOf(geohash),
|
||||||
followCommunities = emptyList(),
|
followCommunities = emptyList(),
|
||||||
followEvents = DefaultChannels.toList(),
|
followEvents = DefaultChannels.toList(),
|
||||||
relayUse = Constants.defaultRelays.associate { it.url to ContactListEvent.ReadWrite(it.read, it.write) },
|
relayUse = Constants.defaultRelays.associate { it.url to ContactListEvent.ReadWrite(it.read, it.write) },
|
||||||
@@ -552,7 +584,7 @@ class Account(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun unfollow(tag: String) {
|
fun unfollowHashtag(tag: String) {
|
||||||
if (!isWriteable()) return
|
if (!isWriteable()) return
|
||||||
|
|
||||||
val contactList = migrateCommunitiesAndChannelsIfNeeded(userProfile().latestContactList)
|
val contactList = migrateCommunitiesAndChannelsIfNeeded(userProfile().latestContactList)
|
||||||
@@ -569,6 +601,23 @@ class Account(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun unfollowGeohash(geohash: String) {
|
||||||
|
if (!isWriteable()) return
|
||||||
|
|
||||||
|
val contactList = migrateCommunitiesAndChannelsIfNeeded(userProfile().latestContactList)
|
||||||
|
|
||||||
|
if (contactList != null && contactList.tags.isNotEmpty()) {
|
||||||
|
val event = ContactListEvent.unfollowGeohash(
|
||||||
|
contactList,
|
||||||
|
geohash,
|
||||||
|
keyPair.privKey!!
|
||||||
|
)
|
||||||
|
|
||||||
|
Client.send(event)
|
||||||
|
LocalCache.consume(event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun unfollow(channel: Channel) {
|
fun unfollow(channel: Channel) {
|
||||||
if (!isWriteable()) return
|
if (!isWriteable()) return
|
||||||
|
|
||||||
@@ -1222,6 +1271,30 @@ class Account(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun selectedGeohashesFollowList(listName: String?): Set<String>? {
|
||||||
|
if (listName == GLOBAL_FOLLOWS) return null
|
||||||
|
if (listName == KIND3_FOLLOWS) return userProfile().cachedFollowingGeohashSet()
|
||||||
|
|
||||||
|
val privKey = keyPair.privKey
|
||||||
|
|
||||||
|
return if (listName != null) {
|
||||||
|
val aTag = ATag(PeopleListEvent.kind, userProfile().pubkeyHex, listName, null).toTag()
|
||||||
|
val list = LocalCache.addressables[aTag]
|
||||||
|
if (list != null) {
|
||||||
|
val publicAddresses = list.event?.geohashes() ?: emptySet()
|
||||||
|
val privateAddresses = privKey?.let {
|
||||||
|
(list.event as? GeneralListEvent)?.privateGeohashes(it)
|
||||||
|
} ?: emptySet()
|
||||||
|
|
||||||
|
(publicAddresses + privateAddresses).toSet()
|
||||||
|
} else {
|
||||||
|
emptySet()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
emptySet()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun selectedCommunitiesFollowList(listName: String?): Set<String>? {
|
fun selectedCommunitiesFollowList(listName: String?): Set<String>? {
|
||||||
if (listName == GLOBAL_FOLLOWS) return null
|
if (listName == GLOBAL_FOLLOWS) return null
|
||||||
if (listName == KIND3_FOLLOWS) return userProfile().cachedFollowingCommunitiesSet()
|
if (listName == KIND3_FOLLOWS) return userProfile().cachedFollowingCommunitiesSet()
|
||||||
|
|||||||
@@ -249,6 +249,12 @@ class User(val pubkeyHex: String) {
|
|||||||
} ?: false
|
} ?: false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun isFollowingGeohashCached(geoTag: String): Boolean {
|
||||||
|
return latestContactList?.verifiedFollowGeohashSet?.let {
|
||||||
|
return geoTag.lowercase() 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
|
||||||
@@ -277,6 +283,10 @@ class User(val pubkeyHex: String) {
|
|||||||
return latestContactList?.verifiedFollowTagSet ?: emptySet()
|
return latestContactList?.verifiedFollowTagSet ?: emptySet()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun cachedFollowingGeohashSet(): Set<HexKey> {
|
||||||
|
return latestContactList?.verifiedFollowGeohashSet ?: emptySet()
|
||||||
|
}
|
||||||
|
|
||||||
fun cachedFollowingCommunitiesSet(): Set<HexKey> {
|
fun cachedFollowingCommunitiesSet(): Set<HexKey> {
|
||||||
return latestContactList?.verifiedFollowCommunitySet ?: emptySet()
|
return latestContactList?.verifiedFollowCommunitySet ?: emptySet()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -92,6 +92,26 @@ object NostrDiscoveryDataSource : NostrDataSource("DiscoveryFeed") {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun createLiveStreamGeohashesFilter(): TypedFilter? {
|
||||||
|
val hashToLoad = account.selectedGeohashesFollowList(account.defaultDiscoveryFollowList)
|
||||||
|
|
||||||
|
if (hashToLoad.isNullOrEmpty()) return null
|
||||||
|
|
||||||
|
return TypedFilter(
|
||||||
|
types = setOf(FeedType.GLOBAL),
|
||||||
|
filter = JsonFilter(
|
||||||
|
kinds = listOf(LiveActivitiesChatMessageEvent.kind, LiveActivitiesEvent.kind),
|
||||||
|
tags = mapOf(
|
||||||
|
"g" to hashToLoad.map {
|
||||||
|
listOf(it, it.lowercase(), it.uppercase(), it.capitalize())
|
||||||
|
}.flatten()
|
||||||
|
),
|
||||||
|
limit = 300,
|
||||||
|
since = latestEOSEs.users[account.userProfile()]?.followList?.get(account.defaultDiscoveryFollowList)?.relayList
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fun createPublicChatsTagsFilter(): TypedFilter? {
|
fun createPublicChatsTagsFilter(): TypedFilter? {
|
||||||
val hashToLoad = account.selectedTagsFollowList(account.defaultDiscoveryFollowList)
|
val hashToLoad = account.selectedTagsFollowList(account.defaultDiscoveryFollowList)
|
||||||
|
|
||||||
@@ -112,6 +132,26 @@ object NostrDiscoveryDataSource : NostrDataSource("DiscoveryFeed") {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun createPublicChatsGeohashesFilter(): TypedFilter? {
|
||||||
|
val hashToLoad = account.selectedGeohashesFollowList(account.defaultDiscoveryFollowList)
|
||||||
|
|
||||||
|
if (hashToLoad.isNullOrEmpty()) return null
|
||||||
|
|
||||||
|
return TypedFilter(
|
||||||
|
types = setOf(FeedType.PUBLIC_CHATS),
|
||||||
|
filter = JsonFilter(
|
||||||
|
kinds = listOf(ChannelCreateEvent.kind, ChannelMetadataEvent.kind, ChannelMessageEvent.kind),
|
||||||
|
tags = mapOf(
|
||||||
|
"g" to hashToLoad.map {
|
||||||
|
listOf(it, it.lowercase(), it.uppercase(), it.capitalize())
|
||||||
|
}.flatten()
|
||||||
|
),
|
||||||
|
limit = 300,
|
||||||
|
since = latestEOSEs.users[account.userProfile()]?.followList?.get(account.defaultDiscoveryFollowList)?.relayList
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fun createCommunitiesTagsFilter(): TypedFilter? {
|
fun createCommunitiesTagsFilter(): TypedFilter? {
|
||||||
val hashToLoad = account.selectedTagsFollowList(account.defaultDiscoveryFollowList)
|
val hashToLoad = account.selectedTagsFollowList(account.defaultDiscoveryFollowList)
|
||||||
|
|
||||||
@@ -132,6 +172,26 @@ object NostrDiscoveryDataSource : NostrDataSource("DiscoveryFeed") {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun createCommunitiesGeohashesFilter(): TypedFilter? {
|
||||||
|
val hashToLoad = account.selectedGeohashesFollowList(account.defaultDiscoveryFollowList)
|
||||||
|
|
||||||
|
if (hashToLoad.isNullOrEmpty()) return null
|
||||||
|
|
||||||
|
return TypedFilter(
|
||||||
|
types = setOf(FeedType.GLOBAL),
|
||||||
|
filter = JsonFilter(
|
||||||
|
kinds = listOf(CommunityDefinitionEvent.kind, CommunityPostApprovalEvent.kind),
|
||||||
|
tags = mapOf(
|
||||||
|
"g" to hashToLoad.map {
|
||||||
|
listOf(it, it.lowercase(), it.uppercase(), it.capitalize())
|
||||||
|
}.flatten()
|
||||||
|
),
|
||||||
|
limit = 300,
|
||||||
|
since = latestEOSEs.users[account.userProfile()]?.followList?.get(account.defaultDiscoveryFollowList)?.relayList
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
val discoveryFeedChannel = requestNewChannel() { time, relayUrl ->
|
val discoveryFeedChannel = requestNewChannel() { time, relayUrl ->
|
||||||
latestEOSEs.addOrUpdate(account.userProfile(), account.defaultDiscoveryFollowList, relayUrl, time)
|
latestEOSEs.addOrUpdate(account.userProfile(), account.defaultDiscoveryFollowList, relayUrl, time)
|
||||||
}
|
}
|
||||||
@@ -143,7 +203,10 @@ object NostrDiscoveryDataSource : NostrDataSource("DiscoveryFeed") {
|
|||||||
createCommunitiesFilter(),
|
createCommunitiesFilter(),
|
||||||
createLiveStreamTagsFilter(),
|
createLiveStreamTagsFilter(),
|
||||||
createPublicChatsTagsFilter(),
|
createPublicChatsTagsFilter(),
|
||||||
createCommunitiesTagsFilter()
|
createCommunitiesTagsFilter(),
|
||||||
|
createCommunitiesGeohashesFilter(),
|
||||||
|
createPublicChatsGeohashesFilter(),
|
||||||
|
createLiveStreamGeohashesFilter()
|
||||||
).ifEmpty { null }
|
).ifEmpty { null }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package com.vitorpamplona.amethyst.service
|
||||||
|
|
||||||
|
import com.vitorpamplona.amethyst.service.model.ChannelMessageEvent
|
||||||
|
import com.vitorpamplona.amethyst.service.model.LongTextNoteEvent
|
||||||
|
import com.vitorpamplona.amethyst.service.model.PollNoteEvent
|
||||||
|
import com.vitorpamplona.amethyst.service.model.TextNoteEvent
|
||||||
|
import com.vitorpamplona.amethyst.service.relays.COMMON_FEED_TYPES
|
||||||
|
import com.vitorpamplona.amethyst.service.relays.JsonFilter
|
||||||
|
import com.vitorpamplona.amethyst.service.relays.TypedFilter
|
||||||
|
|
||||||
|
object NostrGeohashDataSource : NostrDataSource("SingleGeoHashFeed") {
|
||||||
|
private var geohashToWatch: String? = null
|
||||||
|
|
||||||
|
fun createLoadHashtagFilter(): TypedFilter? {
|
||||||
|
val hashToLoad = geohashToWatch ?: return null
|
||||||
|
|
||||||
|
return TypedFilter(
|
||||||
|
types = COMMON_FEED_TYPES,
|
||||||
|
filter = JsonFilter(
|
||||||
|
tags = mapOf(
|
||||||
|
"g" to listOf(
|
||||||
|
hashToLoad
|
||||||
|
)
|
||||||
|
),
|
||||||
|
kinds = listOf(TextNoteEvent.kind, ChannelMessageEvent.kind, LongTextNoteEvent.kind, LongTextNoteEvent.kind, PollNoteEvent.kind),
|
||||||
|
limit = 200
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val loadGeohashChannel = requestNewChannel()
|
||||||
|
|
||||||
|
override fun updateChannelFilters() {
|
||||||
|
loadGeohashChannel.typedFilters = listOfNotNull(createLoadHashtagFilter()).ifEmpty { null }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun loadHashtag(tag: String?) {
|
||||||
|
geohashToWatch = tag
|
||||||
|
|
||||||
|
invalidateFilters()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -104,6 +104,26 @@ object NostrHomeDataSource : NostrDataSource("HomeFeed") {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun createFollowGeohashesFilter(): TypedFilter? {
|
||||||
|
val hashToLoad = account.selectedGeohashesFollowList(account.defaultHomeFollowList) ?: emptySet()
|
||||||
|
|
||||||
|
if (hashToLoad.isEmpty()) return null
|
||||||
|
|
||||||
|
return TypedFilter(
|
||||||
|
types = setOf(FeedType.FOLLOWS),
|
||||||
|
filter = JsonFilter(
|
||||||
|
kinds = listOf(TextNoteEvent.kind, LongTextNoteEvent.kind, ClassifiedsEvent.kind, HighlightEvent.kind, AudioTrackEvent.kind, PinListEvent.kind),
|
||||||
|
tags = mapOf(
|
||||||
|
"g" to hashToLoad.map {
|
||||||
|
listOf(it, it.lowercase(), it.uppercase(), it.capitalize())
|
||||||
|
}.flatten()
|
||||||
|
),
|
||||||
|
limit = 100,
|
||||||
|
since = latestEOSEs.users[account.userProfile()]?.followList?.get(account.defaultHomeFollowList)?.relayList
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fun createFollowCommunitiesFilter(): TypedFilter? {
|
fun createFollowCommunitiesFilter(): TypedFilter? {
|
||||||
val communitiesToLoad = account.selectedCommunitiesFollowList(account.defaultHomeFollowList) ?: emptySet()
|
val communitiesToLoad = account.selectedCommunitiesFollowList(account.defaultHomeFollowList) ?: emptySet()
|
||||||
|
|
||||||
@@ -135,6 +155,11 @@ object NostrHomeDataSource : NostrDataSource("HomeFeed") {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun updateChannelFilters() {
|
override fun updateChannelFilters() {
|
||||||
followAccountChannel.typedFilters = listOfNotNull(createFollowAccountsFilter(), createFollowCommunitiesFilter(), createFollowTagsFilter()).ifEmpty { null }
|
followAccountChannel.typedFilters = listOfNotNull(
|
||||||
|
createFollowAccountsFilter(),
|
||||||
|
createFollowCommunitiesFilter(),
|
||||||
|
createFollowTagsFilter(),
|
||||||
|
createFollowGeohashesFilter()
|
||||||
|
).ifEmpty { null }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,11 +51,35 @@ object NostrVideoDataSource : NostrDataSource("VideoFeed") {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun createFollowGeohashesFilter(): TypedFilter? {
|
||||||
|
val hashToLoad = account.selectedGeohashesFollowList(account.defaultStoriesFollowList)
|
||||||
|
|
||||||
|
if (hashToLoad.isNullOrEmpty()) return null
|
||||||
|
|
||||||
|
return TypedFilter(
|
||||||
|
types = setOf(FeedType.GLOBAL),
|
||||||
|
filter = JsonFilter(
|
||||||
|
kinds = listOf(FileHeaderEvent.kind, FileStorageHeaderEvent.kind),
|
||||||
|
tags = mapOf(
|
||||||
|
"g" to hashToLoad.map {
|
||||||
|
listOf(it, it.lowercase(), it.uppercase(), it.capitalize())
|
||||||
|
}.flatten()
|
||||||
|
),
|
||||||
|
limit = 100,
|
||||||
|
since = latestEOSEs.users[account.userProfile()]?.followList?.get(account.defaultStoriesFollowList)?.relayList
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
val videoFeedChannel = requestNewChannel() { time, relayUrl ->
|
val videoFeedChannel = requestNewChannel() { time, relayUrl ->
|
||||||
latestEOSEs.addOrUpdate(account.userProfile(), account.defaultStoriesFollowList, relayUrl, time)
|
latestEOSEs.addOrUpdate(account.userProfile(), account.defaultStoriesFollowList, relayUrl, time)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun updateChannelFilters() {
|
override fun updateChannelFilters() {
|
||||||
videoFeedChannel.typedFilters = listOfNotNull(createContextualFilter(), createFollowTagsFilter()).ifEmpty { null }
|
videoFeedChannel.typedFilters = listOfNotNull(
|
||||||
|
createContextualFilter(),
|
||||||
|
createFollowTagsFilter(),
|
||||||
|
createFollowGeohashesFilter()
|
||||||
|
).ifEmpty { null }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,6 +39,10 @@ class ContactListEvent(
|
|||||||
unverifiedFollowTagSet().map { it.lowercase() }.toSet()
|
unverifiedFollowTagSet().map { it.lowercase() }.toSet()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val verifiedFollowGeohashSet: Set<String> by lazy {
|
||||||
|
unverifiedFollowGeohashSet().map { it.lowercase() }.toSet()
|
||||||
|
}
|
||||||
|
|
||||||
val verifiedFollowCommunitySet: Set<String> by lazy {
|
val verifiedFollowCommunitySet: Set<String> by lazy {
|
||||||
unverifiedFollowAddressSet().toSet()
|
unverifiedFollowAddressSet().toSet()
|
||||||
}
|
}
|
||||||
@@ -49,6 +53,7 @@ class ContactListEvent(
|
|||||||
|
|
||||||
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 unverifiedFollowTagSet() = tags.filter { it[0] == "t" }.mapNotNull { it.getOrNull(1) }
|
||||||
|
fun unverifiedFollowGeohashSet() = tags.filter { it[0] == "g" }.mapNotNull { it.getOrNull(1) }
|
||||||
|
|
||||||
fun unverifiedFollowAddressSet() = tags.filter { it[0] == "a" }.mapNotNull { it.getOrNull(1) }
|
fun unverifiedFollowAddressSet() = tags.filter { it[0] == "a" }.mapNotNull { it.getOrNull(1) }
|
||||||
|
|
||||||
@@ -82,6 +87,7 @@ class ContactListEvent(
|
|||||||
fun createFromScratch(
|
fun createFromScratch(
|
||||||
followUsers: List<Contact>,
|
followUsers: List<Contact>,
|
||||||
followTags: List<String>,
|
followTags: List<String>,
|
||||||
|
followGeohashes: List<String>,
|
||||||
followCommunities: List<ATag>,
|
followCommunities: List<ATag>,
|
||||||
followEvents: List<String>,
|
followEvents: List<String>,
|
||||||
relayUse: Map<String, ReadWrite>?,
|
relayUse: Map<String, ReadWrite>?,
|
||||||
@@ -110,6 +116,8 @@ class ContactListEvent(
|
|||||||
} else {
|
} else {
|
||||||
listOf("a", it.toTag())
|
listOf("a", it.toTag())
|
||||||
}
|
}
|
||||||
|
} + followGeohashes.map {
|
||||||
|
listOf("g", it)
|
||||||
}
|
}
|
||||||
|
|
||||||
return create(
|
return create(
|
||||||
@@ -164,6 +172,28 @@ class ContactListEvent(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun followGeohash(earlierVersion: ContactListEvent, hashtag: String, privateKey: ByteArray, createdAt: Long = TimeUtils.now()): ContactListEvent {
|
||||||
|
if (earlierVersion.isTaggedGeoHash(hashtag)) return earlierVersion
|
||||||
|
|
||||||
|
return create(
|
||||||
|
content = earlierVersion.content,
|
||||||
|
tags = earlierVersion.tags.plus(element = listOf("g", hashtag)),
|
||||||
|
privateKey = privateKey,
|
||||||
|
createdAt = createdAt
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun unfollowGeohash(earlierVersion: ContactListEvent, hashtag: String, privateKey: ByteArray, createdAt: Long = TimeUtils.now()): ContactListEvent {
|
||||||
|
if (!earlierVersion.isTaggedGeoHash(hashtag)) return earlierVersion
|
||||||
|
|
||||||
|
return create(
|
||||||
|
content = earlierVersion.content,
|
||||||
|
tags = earlierVersion.tags.filter { it.size > 1 && it[1] != hashtag },
|
||||||
|
privateKey = privateKey,
|
||||||
|
createdAt = createdAt
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fun followEvent(earlierVersion: ContactListEvent, idHex: String, privateKey: ByteArray, createdAt: Long = TimeUtils.now()): ContactListEvent {
|
fun followEvent(earlierVersion: ContactListEvent, idHex: String, privateKey: ByteArray, createdAt: Long = TimeUtils.now()): ContactListEvent {
|
||||||
if (earlierVersion.isTaggedEvent(idHex)) return earlierVersion
|
if (earlierVersion.isTaggedEvent(idHex)) return earlierVersion
|
||||||
|
|
||||||
|
|||||||
@@ -69,6 +69,7 @@ open class Event(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun hashtags() = tags.filter { it.size > 1 && it[0] == "t" }.map { it[1] }
|
override fun hashtags() = tags.filter { it.size > 1 && it[0] == "t" }.map { it[1] }
|
||||||
|
override fun geohashes() = tags.filter { it.size > 1 && it[0] == "g" }.map { it[1] }
|
||||||
|
|
||||||
override fun matchTag1With(text: String) = tags.any { it.size > 1 && it[1].contains(text, true) }
|
override fun matchTag1With(text: String) = tags.any { it.size > 1 && it[1].contains(text, true) }
|
||||||
|
|
||||||
@@ -81,7 +82,10 @@ open class Event(
|
|||||||
override fun isTaggedAddressableNotes(idHexes: Set<String>) = tags.any { it.size > 1 && it[0] == "a" && it[1] in idHexes }
|
override fun isTaggedAddressableNotes(idHexes: Set<String>) = tags.any { it.size > 1 && it[0] == "a" && it[1] in idHexes }
|
||||||
|
|
||||||
override fun isTaggedHash(hashtag: String) = tags.any { it.size > 1 && it[0] == "t" && it[1].equals(hashtag, true) }
|
override fun isTaggedHash(hashtag: String) = tags.any { it.size > 1 && it[0] == "t" && it[1].equals(hashtag, true) }
|
||||||
|
|
||||||
|
override fun isTaggedGeoHash(hashtag: String) = tags.any { it.size > 1 && it[0] == "g" && it[1].startsWith(hashtag, true) }
|
||||||
override fun isTaggedHashes(hashtags: Set<String>) = tags.any { it.size > 1 && it[0] == "t" && it[1].lowercase() in hashtags }
|
override fun isTaggedHashes(hashtags: Set<String>) = tags.any { it.size > 1 && it[0] == "t" && it[1].lowercase() in hashtags }
|
||||||
|
override fun isTaggedGeoHashes(hashtags: Set<String>) = tags.any { it.size > 1 && it[0] == "g" && it[1].lowercase() in hashtags }
|
||||||
override fun firstIsTaggedHashes(hashtags: Set<String>) = tags.firstOrNull { it.size > 1 && it[0] == "t" && it[1].lowercase() in hashtags }?.getOrNull(1)
|
override fun firstIsTaggedHashes(hashtags: Set<String>) = tags.firstOrNull { it.size > 1 && it[0] == "t" && it[1].lowercase() in hashtags }?.getOrNull(1)
|
||||||
|
|
||||||
override fun firstIsTaggedAddressableNote(addressableNotes: Set<String>) = tags.firstOrNull { it.size > 1 && it[0] == "a" && it[1] in addressableNotes }?.getOrNull(1)
|
override fun firstIsTaggedAddressableNote(addressableNotes: Set<String>) = tags.firstOrNull { it.size > 1 && it[0] == "a" && it[1] in addressableNotes }?.getOrNull(1)
|
||||||
|
|||||||
@@ -34,7 +34,11 @@ interface EventInterface {
|
|||||||
fun isTaggedAddressableNotes(idHexes: Set<String>): Boolean
|
fun isTaggedAddressableNotes(idHexes: Set<String>): Boolean
|
||||||
|
|
||||||
fun isTaggedHash(hashtag: String): Boolean
|
fun isTaggedHash(hashtag: String): Boolean
|
||||||
|
fun isTaggedGeoHash(hashtag: String): Boolean
|
||||||
|
|
||||||
fun isTaggedHashes(hashtags: Set<String>): Boolean
|
fun isTaggedHashes(hashtags: Set<String>): Boolean
|
||||||
|
fun isTaggedGeoHashes(hashtags: Set<String>): Boolean
|
||||||
|
|
||||||
fun firstIsTaggedHashes(hashtags: Set<String>): String?
|
fun firstIsTaggedHashes(hashtags: Set<String>): String?
|
||||||
fun firstIsTaggedAddressableNote(addressableNotes: Set<String>): String?
|
fun firstIsTaggedAddressableNote(addressableNotes: Set<String>): String?
|
||||||
|
|
||||||
@@ -42,6 +46,7 @@ interface EventInterface {
|
|||||||
fun getTagOfAddressableKind(kind: Int): ATag?
|
fun getTagOfAddressableKind(kind: Int): ATag?
|
||||||
|
|
||||||
fun hashtags(): List<String>
|
fun hashtags(): List<String>
|
||||||
|
fun geohashes(): List<String>
|
||||||
|
|
||||||
fun getReward(): BigDecimal?
|
fun getReward(): BigDecimal?
|
||||||
fun getPoWRank(): Int
|
fun getPoWRank(): Int
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ abstract class GeneralListEvent(
|
|||||||
fun privateTaggedUsers(privKey: ByteArray) = privateTags(privKey)?.filter { it.size > 1 && it[0] == "p" }?.map { it[1] }
|
fun privateTaggedUsers(privKey: ByteArray) = privateTags(privKey)?.filter { it.size > 1 && it[0] == "p" }?.map { it[1] }
|
||||||
|
|
||||||
fun privateHashtags(privKey: ByteArray) = privateTags(privKey)?.filter { it.size > 1 && it[0] == "t" }?.map { it[1] }
|
fun privateHashtags(privKey: ByteArray) = privateTags(privKey)?.filter { it.size > 1 && it[0] == "t" }?.map { it[1] }
|
||||||
|
fun privateGeohashes(privKey: ByteArray) = privateTags(privKey)?.filter { it.size > 1 && it[0] == "g" }?.map { it[1] }
|
||||||
fun privateTaggedEvents(privKey: ByteArray) = privateTags(privKey)?.filter { it.size > 1 && it[0] == "e" }?.map { it[1] }
|
fun privateTaggedEvents(privKey: ByteArray) = privateTags(privKey)?.filter { it.size > 1 && it[0] == "e" }?.map { it[1] }
|
||||||
fun privateTaggedAddresses(privKey: ByteArray) = privateTags(privKey)?.filter { it.firstOrNull() == "a" }?.mapNotNull {
|
fun privateTaggedAddresses(privKey: ByteArray) = privateTags(privKey)?.filter { it.firstOrNull() == "a" }?.mapNotNull {
|
||||||
val aTagValue = it.getOrNull(1)
|
val aTagValue = it.getOrNull(1)
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ open class DiscoverChatFeedFilter(val account: Account) : AdditiveFeedFilter<Not
|
|||||||
|
|
||||||
val followingKeySet = account.selectedUsersFollowList(account.defaultDiscoveryFollowList) ?: emptySet()
|
val followingKeySet = account.selectedUsersFollowList(account.defaultDiscoveryFollowList) ?: emptySet()
|
||||||
val followingTagSet = account.selectedTagsFollowList(account.defaultDiscoveryFollowList) ?: emptySet()
|
val followingTagSet = account.selectedTagsFollowList(account.defaultDiscoveryFollowList) ?: emptySet()
|
||||||
|
val followingGeohashSet = account.selectedGeohashesFollowList(account.defaultDiscoveryFollowList) ?: emptySet()
|
||||||
|
|
||||||
val createEvents = collection.filter { it.event is ChannelCreateEvent }
|
val createEvents = collection.filter { it.event is ChannelCreateEvent }
|
||||||
val anyOtherChannelEvent = collection
|
val anyOtherChannelEvent = collection
|
||||||
@@ -48,7 +49,7 @@ open class DiscoverChatFeedFilter(val account: Account) : AdditiveFeedFilter<Not
|
|||||||
val activities = (createEvents + anyOtherChannelEvent)
|
val activities = (createEvents + anyOtherChannelEvent)
|
||||||
.asSequence()
|
.asSequence()
|
||||||
// .filter { it.event is ChannelCreateEvent } // Event heads might not be loaded yet.
|
// .filter { it.event is ChannelCreateEvent } // Event heads might not be loaded yet.
|
||||||
.filter { isGlobal || it.author?.pubkeyHex in followingKeySet || it.event?.isTaggedHashes(followingTagSet) == true }
|
.filter { isGlobal || it.author?.pubkeyHex in followingKeySet || it.event?.isTaggedHashes(followingTagSet) == true || it.event?.isTaggedGeoHashes(followingGeohashSet) == true }
|
||||||
.filter { isHiddenList || it.author?.let { !account.isHidden(it.pubkeyHex) } ?: true }
|
.filter { isHiddenList || it.author?.let { !account.isHidden(it.pubkeyHex) } ?: true }
|
||||||
.filter { (it.createdAt() ?: 0) <= now }
|
.filter { (it.createdAt() ?: 0) <= now }
|
||||||
.toSet()
|
.toSet()
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ open class DiscoverCommunityFeedFilter(val account: Account) : AdditiveFeedFilte
|
|||||||
|
|
||||||
val followingKeySet = account.selectedUsersFollowList(account.defaultDiscoveryFollowList) ?: emptySet()
|
val followingKeySet = account.selectedUsersFollowList(account.defaultDiscoveryFollowList) ?: emptySet()
|
||||||
val followingTagSet = account.selectedTagsFollowList(account.defaultDiscoveryFollowList) ?: emptySet()
|
val followingTagSet = account.selectedTagsFollowList(account.defaultDiscoveryFollowList) ?: emptySet()
|
||||||
|
val followingGeohashSet = account.selectedGeohashesFollowList(account.defaultDiscoveryFollowList) ?: emptySet()
|
||||||
|
|
||||||
val createEvents = collection.filter { it.event is CommunityDefinitionEvent }
|
val createEvents = collection.filter { it.event is CommunityDefinitionEvent }
|
||||||
val anyOtherCommunityEvent = collection
|
val anyOtherCommunityEvent = collection
|
||||||
@@ -48,7 +49,7 @@ open class DiscoverCommunityFeedFilter(val account: Account) : AdditiveFeedFilte
|
|||||||
val activities = (createEvents + anyOtherCommunityEvent)
|
val activities = (createEvents + anyOtherCommunityEvent)
|
||||||
.asSequence()
|
.asSequence()
|
||||||
.filter { it.event is CommunityDefinitionEvent }
|
.filter { it.event is CommunityDefinitionEvent }
|
||||||
.filter { isGlobal || it.author?.pubkeyHex in followingKeySet || it.event?.isTaggedHashes(followingTagSet) == true }
|
.filter { isGlobal || it.author?.pubkeyHex in followingKeySet || it.event?.isTaggedHashes(followingTagSet) == true || it.event?.isTaggedGeoHashes(followingGeohashSet) == true }
|
||||||
.filter { isHiddenList || it.author?.let { !account.isHidden(it.pubkeyHex) } ?: true }
|
.filter { isHiddenList || it.author?.let { !account.isHidden(it.pubkeyHex) } ?: true }
|
||||||
.filter { (it.createdAt() ?: 0) <= now }
|
.filter { (it.createdAt() ?: 0) <= now }
|
||||||
.toSet()
|
.toSet()
|
||||||
|
|||||||
@@ -39,10 +39,9 @@ open class DiscoverLiveFeedFilter(val account: Account) : AdditiveFeedFilter<Not
|
|||||||
val isGlobal = account.defaultDiscoveryFollowList == GLOBAL_FOLLOWS
|
val isGlobal = account.defaultDiscoveryFollowList == GLOBAL_FOLLOWS
|
||||||
val isHiddenList = showHiddenKey()
|
val isHiddenList = showHiddenKey()
|
||||||
|
|
||||||
val followingKeySet =
|
val followingKeySet = account.selectedUsersFollowList(account.defaultDiscoveryFollowList) ?: emptySet()
|
||||||
account.selectedUsersFollowList(account.defaultDiscoveryFollowList) ?: emptySet()
|
val followingTagSet = account.selectedTagsFollowList(account.defaultDiscoveryFollowList) ?: emptySet()
|
||||||
val followingTagSet =
|
val followingGeohashSet = account.selectedGeohashesFollowList(account.defaultDiscoveryFollowList) ?: emptySet()
|
||||||
account.selectedTagsFollowList(account.defaultDiscoveryFollowList) ?: emptySet()
|
|
||||||
|
|
||||||
val activities = collection
|
val activities = collection
|
||||||
.asSequence()
|
.asSequence()
|
||||||
@@ -50,6 +49,8 @@ open class DiscoverLiveFeedFilter(val account: Account) : AdditiveFeedFilter<Not
|
|||||||
.filter {
|
.filter {
|
||||||
isGlobal || it.author?.pubkeyHex in followingKeySet || it.event?.isTaggedHashes(
|
isGlobal || it.author?.pubkeyHex in followingKeySet || it.event?.isTaggedHashes(
|
||||||
followingTagSet
|
followingTagSet
|
||||||
|
) == true || it.event?.isTaggedGeoHashes(
|
||||||
|
followingGeohashSet
|
||||||
) == true
|
) == true
|
||||||
}
|
}
|
||||||
.filter { isHiddenList || it.author?.let { !account.isHidden(it.pubkeyHex) } ?: true }
|
.filter { isHiddenList || it.author?.let { !account.isHidden(it.pubkeyHex) } ?: true }
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
package com.vitorpamplona.amethyst.ui.dal
|
||||||
|
|
||||||
|
import com.vitorpamplona.amethyst.model.Account
|
||||||
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
|
import com.vitorpamplona.amethyst.service.model.ChannelMessageEvent
|
||||||
|
import com.vitorpamplona.amethyst.service.model.LongTextNoteEvent
|
||||||
|
import com.vitorpamplona.amethyst.service.model.PrivateDmEvent
|
||||||
|
import com.vitorpamplona.amethyst.service.model.TextNoteEvent
|
||||||
|
|
||||||
|
class GeoHashFeedFilter(val tag: String, val account: Account) : AdditiveFeedFilter<Note>() {
|
||||||
|
|
||||||
|
override fun feedKey(): String {
|
||||||
|
return account.userProfile().pubkeyHex + "-" + tag
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun feed(): List<Note> {
|
||||||
|
return sort(innerApplyFilter(LocalCache.notes.values))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun applyFilter(collection: Set<Note>): Set<Note> {
|
||||||
|
return innerApplyFilter(collection)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun innerApplyFilter(collection: Collection<Note>): Set<Note> {
|
||||||
|
val myTag = tag ?: return emptySet()
|
||||||
|
|
||||||
|
return collection
|
||||||
|
.asSequence()
|
||||||
|
.filter {
|
||||||
|
(
|
||||||
|
it.event is TextNoteEvent ||
|
||||||
|
it.event is LongTextNoteEvent ||
|
||||||
|
it.event is ChannelMessageEvent ||
|
||||||
|
it.event is PrivateDmEvent
|
||||||
|
) &&
|
||||||
|
it.event?.isTaggedGeoHash(myTag) == true
|
||||||
|
}
|
||||||
|
.filter { account.isAcceptable(it) }
|
||||||
|
.toSet()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun sort(collection: Set<Note>): List<Note> {
|
||||||
|
return collection.sortedWith(compareBy({ it.createdAt() }, { it.idHex })).reversed()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -35,6 +35,7 @@ class HomeConversationsFeedFilter(val account: Account) : AdditiveFeedFilter<Not
|
|||||||
|
|
||||||
val followingKeySet = account.selectedUsersFollowList(account.defaultHomeFollowList) ?: emptySet()
|
val followingKeySet = account.selectedUsersFollowList(account.defaultHomeFollowList) ?: emptySet()
|
||||||
val followingTagSet = account.selectedTagsFollowList(account.defaultHomeFollowList) ?: emptySet()
|
val followingTagSet = account.selectedTagsFollowList(account.defaultHomeFollowList) ?: emptySet()
|
||||||
|
val followingGeoHashSet = account.selectedGeohashesFollowList(account.defaultHomeFollowList) ?: emptySet()
|
||||||
|
|
||||||
val now = TimeUtils.now()
|
val now = TimeUtils.now()
|
||||||
|
|
||||||
@@ -42,7 +43,7 @@ class HomeConversationsFeedFilter(val account: Account) : AdditiveFeedFilter<Not
|
|||||||
.asSequence()
|
.asSequence()
|
||||||
.filter {
|
.filter {
|
||||||
(it.event is TextNoteEvent || it.event is PollNoteEvent || it.event is ChannelMessageEvent || it.event is LiveActivitiesChatMessageEvent) &&
|
(it.event is TextNoteEvent || it.event is PollNoteEvent || it.event is ChannelMessageEvent || it.event is LiveActivitiesChatMessageEvent) &&
|
||||||
(isGlobal || it.author?.pubkeyHex in followingKeySet || it.event?.isTaggedHashes(followingTagSet) ?: false) &&
|
(isGlobal || it.author?.pubkeyHex in followingKeySet || it.event?.isTaggedHashes(followingTagSet) ?: false || it.event?.isTaggedGeoHashes(followingGeoHashSet) ?: 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
|
||||||
(isHiddenList || it.author?.let { !account.isHidden(it) } ?: true) &&
|
(isHiddenList || it.author?.let { !account.isHidden(it) } ?: true) &&
|
||||||
((it.event?.createdAt() ?: 0) < now) &&
|
((it.event?.createdAt() ?: 0) < now) &&
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ class HomeNewThreadFeedFilter(val account: Account) : AdditiveFeedFilter<Note>()
|
|||||||
|
|
||||||
val followingKeySet = account.selectedUsersFollowList(account.defaultHomeFollowList) ?: emptySet()
|
val followingKeySet = account.selectedUsersFollowList(account.defaultHomeFollowList) ?: emptySet()
|
||||||
val followingTagSet = account.selectedTagsFollowList(account.defaultHomeFollowList) ?: emptySet()
|
val followingTagSet = account.selectedTagsFollowList(account.defaultHomeFollowList) ?: emptySet()
|
||||||
|
val followingGeoSet = account.selectedGeohashesFollowList(account.defaultHomeFollowList) ?: emptySet()
|
||||||
val followingCommunities = account.selectedCommunitiesFollowList(account.defaultHomeFollowList) ?: emptySet()
|
val followingCommunities = account.selectedCommunitiesFollowList(account.defaultHomeFollowList) ?: emptySet()
|
||||||
|
|
||||||
val oneMinuteInTheFuture = TimeUtils.now() + (1 * 60) // one minute in the future.
|
val oneMinuteInTheFuture = TimeUtils.now() + (1 * 60) // one minute in the future.
|
||||||
@@ -51,7 +52,7 @@ class HomeNewThreadFeedFilter(val account: Account) : AdditiveFeedFilter<Note>()
|
|||||||
.filter { it ->
|
.filter { it ->
|
||||||
val noteEvent = it.event
|
val noteEvent = it.event
|
||||||
(noteEvent is TextNoteEvent || noteEvent is ClassifiedsEvent || noteEvent is RepostEvent || noteEvent is GenericRepostEvent || noteEvent is LongTextNoteEvent || noteEvent is PollNoteEvent || noteEvent is HighlightEvent || noteEvent is AudioTrackEvent) &&
|
(noteEvent is TextNoteEvent || noteEvent is ClassifiedsEvent || noteEvent is RepostEvent || noteEvent is GenericRepostEvent || noteEvent is LongTextNoteEvent || noteEvent is PollNoteEvent || noteEvent is HighlightEvent || noteEvent is AudioTrackEvent) &&
|
||||||
(isGlobal || it.author?.pubkeyHex in followingKeySet || noteEvent.isTaggedHashes(followingTagSet) || noteEvent.isTaggedAddressableNotes(followingCommunities)) &&
|
(isGlobal || it.author?.pubkeyHex in followingKeySet || noteEvent.isTaggedHashes(followingTagSet) || noteEvent.isTaggedGeoHashes(followingGeoSet) || noteEvent.isTaggedAddressableNotes(followingCommunities)) &&
|
||||||
// && 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
|
||||||
(isHiddenList || it.author?.let { !account.isHidden(it.pubkeyHex) } ?: true) &&
|
(isHiddenList || it.author?.let { !account.isHidden(it.pubkeyHex) } ?: true) &&
|
||||||
((it.event?.createdAt() ?: 0) < oneMinuteInTheFuture) &&
|
((it.event?.createdAt() ?: 0) < oneMinuteInTheFuture) &&
|
||||||
|
|||||||
@@ -33,11 +33,12 @@ class VideoFeedFilter(val account: Account) : AdditiveFeedFilter<Note>() {
|
|||||||
|
|
||||||
val followingKeySet = account.selectedUsersFollowList(account.defaultStoriesFollowList) ?: emptySet()
|
val followingKeySet = account.selectedUsersFollowList(account.defaultStoriesFollowList) ?: emptySet()
|
||||||
val followingTagSet = account.selectedTagsFollowList(account.defaultStoriesFollowList) ?: emptySet()
|
val followingTagSet = account.selectedTagsFollowList(account.defaultStoriesFollowList) ?: emptySet()
|
||||||
|
val followingGeohashSet = account.selectedGeohashesFollowList(account.defaultStoriesFollowList) ?: emptySet()
|
||||||
|
|
||||||
return collection
|
return collection
|
||||||
.asSequence()
|
.asSequence()
|
||||||
.filter { it.event is FileHeaderEvent || it.event is FileStorageHeaderEvent }
|
.filter { it.event is FileHeaderEvent || it.event is FileStorageHeaderEvent }
|
||||||
.filter { isGlobal || it.author?.pubkeyHex in followingKeySet || (it.event?.isTaggedHashes(followingTagSet) ?: false) }
|
.filter { isGlobal || it.author?.pubkeyHex in followingKeySet || (it.event?.isTaggedHashes(followingTagSet) ?: false) || (it.event?.isTaggedGeoHashes(followingGeohashSet) ?: false) }
|
||||||
.filter { isHiddenList || account.isAcceptable(it) }
|
.filter { isHiddenList || account.isAcceptable(it) }
|
||||||
.filter { it.createdAt()!! <= now }
|
.filter { it.createdAt()!! <= now }
|
||||||
.toSet()
|
.toSet()
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.ChatroomListScreen
|
|||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.ChatroomScreen
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.ChatroomScreen
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.CommunityScreen
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.CommunityScreen
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.DiscoverScreen
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.DiscoverScreen
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.GeoHashScreen
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.HashtagScreen
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.HashtagScreen
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.HiddenUsersScreen
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.HiddenUsersScreen
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.HomeScreen
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.HomeScreen
|
||||||
@@ -178,6 +179,16 @@ fun AppNavigation(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Route.Geohash.let { route ->
|
||||||
|
composable(route.route, route.arguments, content = {
|
||||||
|
GeoHashScreen(
|
||||||
|
tag = it.arguments?.getString("id"),
|
||||||
|
accountViewModel = accountViewModel,
|
||||||
|
nav = nav
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
Route.Community.let { route ->
|
Route.Community.let { route ->
|
||||||
composable(route.route, route.arguments, content = {
|
composable(route.route, route.arguments, content = {
|
||||||
CommunityScreen(
|
CommunityScreen(
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ import com.vitorpamplona.amethyst.service.NostrChatroomDataSource
|
|||||||
import com.vitorpamplona.amethyst.service.NostrChatroomListDataSource
|
import com.vitorpamplona.amethyst.service.NostrChatroomListDataSource
|
||||||
import com.vitorpamplona.amethyst.service.NostrCommunityDataSource
|
import com.vitorpamplona.amethyst.service.NostrCommunityDataSource
|
||||||
import com.vitorpamplona.amethyst.service.NostrDiscoveryDataSource
|
import com.vitorpamplona.amethyst.service.NostrDiscoveryDataSource
|
||||||
|
import com.vitorpamplona.amethyst.service.NostrGeohashDataSource
|
||||||
import com.vitorpamplona.amethyst.service.NostrHashtagDataSource
|
import com.vitorpamplona.amethyst.service.NostrHashtagDataSource
|
||||||
import com.vitorpamplona.amethyst.service.NostrHomeDataSource
|
import com.vitorpamplona.amethyst.service.NostrHomeDataSource
|
||||||
import com.vitorpamplona.amethyst.service.NostrSearchEventOrUserDataSource
|
import com.vitorpamplona.amethyst.service.NostrSearchEventOrUserDataSource
|
||||||
@@ -124,6 +125,7 @@ private fun RenderTopRouteBar(
|
|||||||
Route.Room.base -> NoTopBar()
|
Route.Room.base -> NoTopBar()
|
||||||
Route.Community.base -> NoTopBar()
|
Route.Community.base -> NoTopBar()
|
||||||
Route.Hashtag.base -> NoTopBar()
|
Route.Hashtag.base -> NoTopBar()
|
||||||
|
Route.Geohash.base -> NoTopBar()
|
||||||
// Route.Profile.route -> TopBarWithBackButton(nav)
|
// Route.Profile.route -> TopBarWithBackButton(nav)
|
||||||
Route.Home.base -> HomeTopBar(followLists, scaffoldState, accountViewModel, nav)
|
Route.Home.base -> HomeTopBar(followLists, scaffoldState, accountViewModel, nav)
|
||||||
Route.Video.base -> StoriesTopBar(followLists, scaffoldState, accountViewModel, nav)
|
Route.Video.base -> StoriesTopBar(followLists, scaffoldState, accountViewModel, nav)
|
||||||
@@ -492,6 +494,7 @@ fun debugState(context: Context) {
|
|||||||
NostrCommunityDataSource.printCounter()
|
NostrCommunityDataSource.printCounter()
|
||||||
NostrDiscoveryDataSource.printCounter()
|
NostrDiscoveryDataSource.printCounter()
|
||||||
NostrHashtagDataSource.printCounter()
|
NostrHashtagDataSource.printCounter()
|
||||||
|
NostrGeohashDataSource.printCounter()
|
||||||
NostrHomeDataSource.printCounter()
|
NostrHomeDataSource.printCounter()
|
||||||
NostrSearchEventOrUserDataSource.printCounter()
|
NostrSearchEventOrUserDataSource.printCounter()
|
||||||
NostrSingleChannelDataSource.printCounter()
|
NostrSingleChannelDataSource.printCounter()
|
||||||
|
|||||||
@@ -107,6 +107,12 @@ sealed class Route(
|
|||||||
arguments = listOf(navArgument("id") { type = NavType.StringType }).toImmutableList()
|
arguments = listOf(navArgument("id") { type = NavType.StringType }).toImmutableList()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
object Geohash : Route(
|
||||||
|
route = "Geohash/{id}",
|
||||||
|
icon = R.drawable.ic_moments,
|
||||||
|
arguments = listOf(navArgument("id") { type = NavType.StringType }).toImmutableList()
|
||||||
|
)
|
||||||
|
|
||||||
object Community : Route(
|
object Community : Route(
|
||||||
route = "Community/{id}",
|
route = "Community/{id}",
|
||||||
icon = R.drawable.ic_moments,
|
icon = R.drawable.ic_moments,
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ import com.vitorpamplona.amethyst.ui.note.NIP05VerifiedIcon
|
|||||||
import com.vitorpamplona.amethyst.ui.theme.NIP05IconSize
|
import com.vitorpamplona.amethyst.ui.theme.NIP05IconSize
|
||||||
import com.vitorpamplona.amethyst.ui.theme.Size16Modifier
|
import com.vitorpamplona.amethyst.ui.theme.Size16Modifier
|
||||||
import com.vitorpamplona.amethyst.ui.theme.nip05
|
import com.vitorpamplona.amethyst.ui.theme.nip05
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@@ -115,6 +116,14 @@ fun ObserveDisplayNip05Status(baseUser: User, columnModifier: Modifier = Modifie
|
|||||||
Crossfade(targetState = nip05, modifier = columnModifier) {
|
Crossfade(targetState = nip05, modifier = columnModifier) {
|
||||||
if (it != null) {
|
if (it != null) {
|
||||||
DisplayNIP05Line(it, baseUser, columnModifier)
|
DisplayNIP05Line(it, baseUser, columnModifier)
|
||||||
|
} else {
|
||||||
|
Text(
|
||||||
|
text = baseUser.pubkeyDisplayHex(),
|
||||||
|
color = MaterialTheme.colors.placeholderText,
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
modifier = columnModifier
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2409,7 +2409,7 @@ fun SecondUserInfoRow(
|
|||||||
|
|
||||||
val geo = remember { noteEvent.getGeoHash() }
|
val geo = remember { noteEvent.getGeoHash() }
|
||||||
if (geo != null) {
|
if (geo != null) {
|
||||||
DisplayLocation(geo)
|
DisplayLocation(geo, nav)
|
||||||
}
|
}
|
||||||
|
|
||||||
val baseReward = remember { noteEvent.getReward()?.let { Reward(it) } }
|
val baseReward = remember { noteEvent.getReward()?.let { Reward(it) } }
|
||||||
@@ -2425,17 +2425,22 @@ fun SecondUserInfoRow(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun DisplayLocation(geohash: String) {
|
fun DisplayLocation(geohash: String, nav: (String) -> Unit) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val cityName = remember(geohash) {
|
val cityName = remember(geohash) {
|
||||||
ReverseGeoLocationUtil().execute(geohash.toGeoHash().toLocation(), context)
|
ReverseGeoLocationUtil().execute(geohash.toGeoHash().toLocation(), context)
|
||||||
}
|
}
|
||||||
|
|
||||||
Text(
|
ClickableText(
|
||||||
text = cityName ?: geohash,
|
text = AnnotatedString(cityName ?: geohash),
|
||||||
color = MaterialTheme.colors.lessImportantLink,
|
onClick = { nav("Geohash/$geohash") },
|
||||||
fontSize = Font14SP,
|
style = LocalTextStyle.current.copy(
|
||||||
fontWeight = FontWeight.Bold,
|
color = MaterialTheme.colors.primary.copy(
|
||||||
|
alpha = 0.52f
|
||||||
|
),
|
||||||
|
fontSize = Font14SP,
|
||||||
|
fontWeight = FontWeight.Bold
|
||||||
|
),
|
||||||
maxLines = 1
|
maxLines = 1
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ private fun UserNameDisplay(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun NPubDisplay(npubDisplay: String, modifier: Modifier) {
|
fun NPubDisplay(npubDisplay: String, modifier: Modifier) {
|
||||||
Text(
|
Text(
|
||||||
text = npubDisplay,
|
text = npubDisplay,
|
||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.Bold,
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import com.vitorpamplona.amethyst.ui.dal.DiscoverChatFeedFilter
|
|||||||
import com.vitorpamplona.amethyst.ui.dal.DiscoverCommunityFeedFilter
|
import com.vitorpamplona.amethyst.ui.dal.DiscoverCommunityFeedFilter
|
||||||
import com.vitorpamplona.amethyst.ui.dal.DiscoverLiveFeedFilter
|
import com.vitorpamplona.amethyst.ui.dal.DiscoverLiveFeedFilter
|
||||||
import com.vitorpamplona.amethyst.ui.dal.FeedFilter
|
import com.vitorpamplona.amethyst.ui.dal.FeedFilter
|
||||||
|
import com.vitorpamplona.amethyst.ui.dal.GeoHashFeedFilter
|
||||||
import com.vitorpamplona.amethyst.ui.dal.HashtagFeedFilter
|
import com.vitorpamplona.amethyst.ui.dal.HashtagFeedFilter
|
||||||
import com.vitorpamplona.amethyst.ui.dal.HomeConversationsFeedFilter
|
import com.vitorpamplona.amethyst.ui.dal.HomeConversationsFeedFilter
|
||||||
import com.vitorpamplona.amethyst.ui.dal.HomeNewThreadFeedFilter
|
import com.vitorpamplona.amethyst.ui.dal.HomeNewThreadFeedFilter
|
||||||
@@ -125,6 +126,14 @@ class NostrHashtagFeedViewModel(val hashtag: String, val account: Account) : Fee
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class NostrGeoHashFeedViewModel(val geohash: String, val account: Account) : FeedViewModel(GeoHashFeedFilter(geohash, account)) {
|
||||||
|
class Factory(val geohash: String, val account: Account) : ViewModelProvider.Factory {
|
||||||
|
override fun <NostrGeoHashFeedViewModel : ViewModel> create(modelClass: Class<NostrGeoHashFeedViewModel>): NostrGeoHashFeedViewModel {
|
||||||
|
return NostrGeoHashFeedViewModel(geohash, account) as NostrGeoHashFeedViewModel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class NostrCommunityFeedViewModel(val note: AddressableNote, val account: Account) : FeedViewModel(CommunityFeedFilter(note, account)) {
|
class NostrCommunityFeedViewModel(val note: AddressableNote, val account: Account) : FeedViewModel(CommunityFeedFilter(note, account)) {
|
||||||
class Factory(val note: AddressableNote, val account: Account) : ViewModelProvider.Factory {
|
class Factory(val note: AddressableNote, val account: Account) : ViewModelProvider.Factory {
|
||||||
override fun <NostrCommunityFeedViewModel : ViewModel> create(modelClass: Class<NostrCommunityFeedViewModel>): NostrCommunityFeedViewModel {
|
override fun <NostrCommunityFeedViewModel : ViewModel> create(modelClass: Class<NostrCommunityFeedViewModel>): NostrCommunityFeedViewModel {
|
||||||
|
|||||||
@@ -0,0 +1,203 @@
|
|||||||
|
package com.vitorpamplona.amethyst.ui.screen.loggedIn
|
||||||
|
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material.Divider
|
||||||
|
import androidx.compose.material.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.DisposableEffect
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.derivedStateOf
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.livedata.observeAsState
|
||||||
|
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.platform.LocalContext
|
||||||
|
import androidx.compose.ui.platform.LocalLifecycleOwner
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.lifecycle.Lifecycle
|
||||||
|
import androidx.lifecycle.LifecycleEventObserver
|
||||||
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import com.fonfon.kgeohash.toGeoHash
|
||||||
|
import com.vitorpamplona.amethyst.R
|
||||||
|
import com.vitorpamplona.amethyst.service.NostrGeohashDataSource
|
||||||
|
import com.vitorpamplona.amethyst.service.ReverseGeoLocationUtil
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.NostrGeoHashFeedViewModel
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.RefresheableFeedView
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.HalfPadding
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun GeoHashScreen(tag: String?, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
|
||||||
|
if (tag == null) return
|
||||||
|
|
||||||
|
PrepareViewModelsGeoHashScreen(tag, accountViewModel, nav)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun PrepareViewModelsGeoHashScreen(tag: String, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
|
||||||
|
val followsFeedViewModel: NostrGeoHashFeedViewModel = viewModel(
|
||||||
|
key = tag + "GeoHashFeedViewModel",
|
||||||
|
factory = NostrGeoHashFeedViewModel.Factory(
|
||||||
|
tag,
|
||||||
|
accountViewModel.account
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
GeoHashScreen(tag, followsFeedViewModel, accountViewModel, nav)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun GeoHashScreen(tag: String, feedViewModel: NostrGeoHashFeedViewModel, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
|
||||||
|
val lifeCycleOwner = LocalLifecycleOwner.current
|
||||||
|
|
||||||
|
NostrGeohashDataSource.loadHashtag(tag)
|
||||||
|
|
||||||
|
LaunchedEffect(tag) {
|
||||||
|
NostrGeohashDataSource.start()
|
||||||
|
feedViewModel.invalidateData()
|
||||||
|
}
|
||||||
|
|
||||||
|
DisposableEffect(accountViewModel) {
|
||||||
|
val observer = LifecycleEventObserver { _, event ->
|
||||||
|
if (event == Lifecycle.Event.ON_RESUME) {
|
||||||
|
println("Hashtag Start")
|
||||||
|
NostrGeohashDataSource.loadHashtag(tag)
|
||||||
|
NostrGeohashDataSource.start()
|
||||||
|
feedViewModel.invalidateData()
|
||||||
|
}
|
||||||
|
if (event == Lifecycle.Event.ON_PAUSE) {
|
||||||
|
println("Hashtag Stop")
|
||||||
|
NostrGeohashDataSource.loadHashtag(null)
|
||||||
|
NostrGeohashDataSource.stop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lifeCycleOwner.lifecycle.addObserver(observer)
|
||||||
|
onDispose {
|
||||||
|
lifeCycleOwner.lifecycle.removeObserver(observer)
|
||||||
|
NostrGeohashDataSource.loadHashtag(null)
|
||||||
|
NostrGeohashDataSource.stop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column(Modifier.fillMaxHeight()) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.padding(vertical = 0.dp)
|
||||||
|
) {
|
||||||
|
GeoHashHeader(tag, accountViewModel)
|
||||||
|
RefresheableFeedView(
|
||||||
|
feedViewModel,
|
||||||
|
null,
|
||||||
|
accountViewModel = accountViewModel,
|
||||||
|
nav = nav
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun GeoHashHeader(tag: String, account: AccountViewModel, onClick: () -> Unit = { }) {
|
||||||
|
Column(
|
||||||
|
Modifier.clickable { onClick() }
|
||||||
|
) {
|
||||||
|
Column(modifier = HalfPadding) {
|
||||||
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(start = 10.dp)
|
||||||
|
.weight(1f)
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.Center,
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
val cityName = remember(tag) {
|
||||||
|
ReverseGeoLocationUtil().execute(tag.toGeoHash().toLocation(), context)
|
||||||
|
}
|
||||||
|
|
||||||
|
Text(
|
||||||
|
"$cityName ($tag)",
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
)
|
||||||
|
|
||||||
|
HashtagActionOptions(tag, account)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Divider(
|
||||||
|
modifier = Modifier.padding(start = 12.dp, end = 12.dp),
|
||||||
|
thickness = 0.25.dp
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun HashtagActionOptions(
|
||||||
|
tag: String,
|
||||||
|
accountViewModel: AccountViewModel
|
||||||
|
) {
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
val context = LocalContext.current
|
||||||
|
|
||||||
|
val userState by accountViewModel.userProfile().live().follows.observeAsState()
|
||||||
|
val isFollowingTag by remember(userState) {
|
||||||
|
derivedStateOf {
|
||||||
|
userState?.user?.isFollowingGeohashCached(tag) ?: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isFollowingTag) {
|
||||||
|
UnfollowButton {
|
||||||
|
if (!accountViewModel.isWriteable()) {
|
||||||
|
scope.launch {
|
||||||
|
Toast
|
||||||
|
.makeText(
|
||||||
|
context,
|
||||||
|
context.getString(R.string.login_with_a_private_key_to_be_able_to_unfollow),
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
scope.launch(Dispatchers.IO) {
|
||||||
|
accountViewModel.account.unfollowGeohash(tag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
FollowButton {
|
||||||
|
if (!accountViewModel.isWriteable()) {
|
||||||
|
scope.launch {
|
||||||
|
Toast
|
||||||
|
.makeText(
|
||||||
|
context,
|
||||||
|
context.getString(R.string.login_with_a_private_key_to_be_able_to_follow),
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
scope.launch(Dispatchers.IO) {
|
||||||
|
accountViewModel.account.followGeohash(tag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,7 +18,6 @@ import androidx.compose.runtime.getValue
|
|||||||
import androidx.compose.runtime.livedata.observeAsState
|
import androidx.compose.runtime.livedata.observeAsState
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
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.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
@@ -170,7 +169,7 @@ private fun HashtagActionOptions(
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
scope.launch(Dispatchers.IO) {
|
scope.launch(Dispatchers.IO) {
|
||||||
accountViewModel.account.unfollow(tag)
|
accountViewModel.account.unfollowHashtag(tag)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -188,7 +187,7 @@ private fun HashtagActionOptions(
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
scope.launch(Dispatchers.IO) {
|
scope.launch(Dispatchers.IO) {
|
||||||
accountViewModel.account.follow(tag)
|
accountViewModel.account.followHashtag(tag)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user