Follow and unfollow logic

This commit is contained in:
Kgothatso Ngako
2026-04-24 00:51:54 +02:00
parent 5cd9cc09d6
commit 96806cfb78
6 changed files with 117 additions and 1 deletions

View File

@@ -2,6 +2,7 @@ package ac.cord.auxiliary.compose.database.dao
import ac.cord.auxiliary.compose.database.model.Connection
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query
import androidx.room.Upsert
@@ -23,4 +24,7 @@ interface ConnectionDao {
@Upsert
suspend fun upsert(connection: Connection)
@Delete
suspend fun delete(connection: Connection)
}

View File

@@ -9,6 +9,7 @@ import androidx.room.Query
import androidx.room.Upsert
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip01Core.core.Kind
import fr.acinq.bitcoin.PublicKey
import kotlinx.coroutines.flow.Flow
@Dao
@@ -43,6 +44,9 @@ interface NostrEventDao {
@Query("SELECT * FROM NostrEvent WHERE id = :id")
fun getNostrEventById(id: String): NostrEvent?
@Query("SELECT * FROM NostrEvent WHERE pubKey = :publicKey AND kind = :kind")
fun getNostrEventByPublicKeyAndKind(publicKey: HexKey, kind: Kind): NostrEvent?
@Upsert
suspend fun upsert(nostrEvent: NostrEvent)

View File

@@ -24,6 +24,7 @@ import ac.cord.auxiliary.compose.repository.NostrRepository
import ac.cord.auxiliary.compose.repository.RelayRepository
import co.touchlab.kermit.Logger
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip01Core.core.Kind
import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent
import com.vitorpamplona.quartz.nip02FollowList.ContactListEvent
import com.vitorpamplona.quartz.nip02FollowList.RelaySet
@@ -70,6 +71,10 @@ class DatabaseNostrRepository(
)
}
override suspend fun deleteConnection(connection: Connection) {
database.connectionDao().delete(connection)
}
override suspend fun observeUnsignedNostrEvents(publicKey: HexKey): Flow<UnsignedNostrEvent?> {
return database.unsignedNostrEventDao().observeUnsignedNostrEvents(publicKey)
}
@@ -239,6 +244,7 @@ class DatabaseNostrRepository(
}
override suspend fun saveUnsignedNostrEvent(unsignedNostrEvent: UnsignedNostrEvent): UnsignedNostrEvent? = try {
// TODO: We can actually check the unsignedNostrEvent.pubKey for support... if it's writeable
delay(2_100) // Just to make it look like we are doing serious work...
unsignedNostrEvent.copy(
id = database.unsignedNostrEventDao().upsert(unsignedNostrEvent)
@@ -328,6 +334,13 @@ class DatabaseNostrRepository(
)
}
override suspend fun getNostrEvent(publicKey: HexKey, kind: Kind): NostrEvent? {
return database.nostrEventDao().getNostrEventByPublicKeyAndKind(
publicKey = publicKey,
kind = kind
)
}
override suspend fun observeNostrFeed(): Flow<List<LocalNostrEvent>> {
return database.nostrEventDao().observeNostrEvents(
kinds = arrayOf(

View File

@@ -17,6 +17,7 @@ import ac.cord.auxiliary.compose.database.model.intermdiate.LocalProfileWithFoll
import ac.cord.auxiliary.compose.database.model.typealiases.SynchronizationFilterArray
import ac.cord.auxiliary.compose.database.model.types.SynchronizationFilter
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip01Core.core.Kind
import kotlinx.coroutines.flow.Flow
interface NostrRepository {
@@ -24,6 +25,8 @@ interface NostrRepository {
suspend fun observeRelation(publicKey: HexKey, activePublicKey: HexKey): Flow<List<Connection>>
suspend fun deleteConnection(connection: Connection)
suspend fun observeUnsignedNostrEvents(publicKey: HexKey): Flow<UnsignedNostrEvent?>
suspend fun observePendingBroadcastNostrEventRequests(): Flow<List<LocalBroadcastNostrEventRequest>>
@@ -76,6 +79,9 @@ interface NostrRepository {
suspend fun observeSynchronizeNostrEventRequestByPurposeAndStatusCount(purpose: String): Flow<Int>
suspend fun getNostrEvent(publicKey: HexKey, kind: Kind): NostrEvent?
suspend fun observeNostrFeed(): Flow<List<LocalNostrEvent>>
suspend fun observeNostrFeed(synchronizationFilter: SynchronizationFilter): Flow<List<LocalNostrEvent>>
@@ -109,6 +115,10 @@ interface NostrRepository {
TODO("Not yet implemented")
}
override suspend fun deleteConnection(connection: Connection) {
TODO("Not yet implemented")
}
override suspend fun observeUnsignedNostrEvents(publicKey: HexKey): Flow<UnsignedNostrEvent?> {
TODO("Not yet implemented")
}
@@ -186,6 +196,13 @@ interface NostrRepository {
TODO("Not yet implemented")
}
override suspend fun getNostrEvent(
publicKey: HexKey,
kind: Kind
): NostrEvent? {
TODO("Not yet implemented")
}
override suspend fun observeNostrFeed(): Flow<List<LocalNostrEvent>> {
TODO("Not yet implemented")
}

View File

@@ -206,7 +206,7 @@ fun MetadataEventDetail(
}
) {
Text(
text = if (metadataEventDetailUIState.followingActiveUserConnection == null) {
text = if (metadataEventDetailUIState.followingActiveUserConnection != null) {
"follow back"
} else {
"follow"

View File

@@ -1,6 +1,7 @@
package ac.cord.auxiliary.compose.ui.view.model
import ac.cord.auxiliary.compose.database.model.Connection
import ac.cord.auxiliary.compose.database.model.UnsignedNostrEvent
import ac.cord.auxiliary.compose.database.model.types.SynchronizationFilter
import ac.cord.auxiliary.compose.managers.SeedManager
import ac.cord.auxiliary.compose.repository.NostrRepository
@@ -17,12 +18,17 @@ import androidx.lifecycle.viewmodel.initializer
import androidx.lifecycle.viewmodel.viewModelFactory
import co.touchlab.kermit.Logger
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip01Core.core.TagArray
import com.vitorpamplona.quartz.nip01Core.core.toHexKey
import com.vitorpamplona.quartz.nip01Core.tags.people.isTaggedUser
import com.vitorpamplona.quartz.nip02FollowList.ContactListEvent
import com.vitorpamplona.quartz.nip02FollowList.RelaySet
import com.vitorpamplona.quartz.nip02FollowList.tags.ContactTag
import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent
import com.vitorpamplona.quartz.nip18Reposts.RepostEvent
import com.vitorpamplona.quartz.nip23LongContent.LongTextNoteEvent
import com.vitorpamplona.quartz.nip25Reactions.ReactionEvent
import com.vitorpamplona.quartz.nip31Alts.AltTag
import com.vitorpamplona.quartz.nip51Lists.bookmarkList.BookmarkListEvent
import com.vitorpamplona.quartz.nip51Lists.relayLists.RelayFeedsListEvent
import com.vitorpamplona.quartz.nip56Reports.ReportEvent
@@ -63,11 +69,17 @@ class MetadataEventDetailViewModel(
activePublicKey = activePublicKey
).collect { relations ->
logger.d("We found relations: $relations")
metadataEventDetailUIState = MetadataEventDetailUIState.Loaded(
isActiveUser = activePublicKey == publicKey,
followingActiveUserConnection = relations.find { connection -> connection.sourcePublicKey == publicKey && connection.destinationPublicKey == activePublicKey },
followedByActiveUserConnection = relations.find { connection -> connection.sourcePublicKey == activePublicKey && connection.destinationPublicKey == publicKey }
)
if (isActionPending.value) {
// This means this new relation was caused by user action...
isActionPending.value = false
}
}
}
}
@@ -210,7 +222,45 @@ class MetadataEventDetailViewModel(
val activePublicKey = SeedManager.activePublicKey().toHexKey()
viewModelScope.launch(Dispatchers.IO) {
val contactListEvent = nostrRepository.getNostrEvent(
publicKey = activePublicKey,
kind = ContactListEvent.KIND
)
val followUsers: TagArray = if (contactListEvent != null) {
if (contactListEvent.tags.isTaggedUser(publicKey)) {
// User already being followed
logger.d("User is already following $publicKey")
return@launch
}
contactListEvent.tags.plus(
ContactTag(
pubKey = publicKey
).toTagArray()
)
} else {
arrayOf(
ContactTag(activePublicKey, null, null).toTagArray(),
ContactTag(publicKey, null, null).toTagArray(),
)
}
val contactListTagList = listOf(AltTag.assemble(ContactListEvent.ALT)) +
followUsers.map { it }
nostrRepository.saveUnsignedNostrEvent(
UnsignedNostrEvent(
pubKey = activePublicKey,
kind = ContactListEvent.KIND,
tags = contactListTagList.toTypedArray(),
content = RelaySet.assemble(
emptyMap()
)
)
)
}
}
fun unfollow(connection: Connection) {
@@ -218,6 +268,34 @@ class MetadataEventDetailViewModel(
val activePublicKey = SeedManager.activePublicKey().toHexKey()
viewModelScope.launch(Dispatchers.IO) {
val contactListEvent = nostrRepository.getNostrEvent(
publicKey = activePublicKey,
kind = ContactListEvent.KIND
)
if (contactListEvent != null) {
if (!contactListEvent.tags.isTaggedUser(publicKey)) {
logger.d("User is not being followed so no need to unfollow: $publicKey")
} else {
nostrRepository.saveUnsignedNostrEvent(
UnsignedNostrEvent(
pubKey = activePublicKey,
kind = ContactListEvent.KIND,
tags = contactListEvent.tags.filter { it.size > 1 && it[1] != publicKey }.toTypedArray(),
content = RelaySet.assemble(
emptyMap()
)
)
)
}
nostrRepository.deleteConnection(
connection
)
}
}
}
companion object {
const val TAG = "MetadataEventDetailViewModel"