Setup UI for follow/unfollow logic
This commit is contained in:
@@ -5,6 +5,8 @@ import androidx.room.Dao
|
||||
import androidx.room.Insert
|
||||
import androidx.room.Query
|
||||
import androidx.room.Upsert
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@Dao
|
||||
interface ConnectionDao {
|
||||
@@ -12,6 +14,9 @@ interface ConnectionDao {
|
||||
fun getAllConnections(limit: Int = 21): List<Connection>
|
||||
|
||||
|
||||
@Query("SELECT * FROM Connection WHERE (sourcePublicKey = :alicePublicKey AND destinationPublicKey = :bobPublicKey) OR (sourcePublicKey = :bobPublicKey AND destinationPublicKey = :alicePublicKey)")
|
||||
fun observeRelation(alicePublicKey: HexKey, bobPublicKey: HexKey): Flow<List<Connection>>
|
||||
|
||||
@Insert
|
||||
suspend fun insertPlaceholderProfile(connection: Connection)
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import ac.cord.auxiliary.compose.database.AuxDatabase
|
||||
import ac.cord.auxiliary.compose.database.GENESIS_AT
|
||||
import ac.cord.auxiliary.compose.database.model.BroadcastNostrEventReceipt
|
||||
import ac.cord.auxiliary.compose.database.model.BroadcastNostrEventRequest
|
||||
import ac.cord.auxiliary.compose.database.model.Connection
|
||||
import ac.cord.auxiliary.compose.database.model.NostrEvent
|
||||
import ac.cord.auxiliary.compose.database.model.Profile
|
||||
import ac.cord.auxiliary.compose.database.model.RecentSearch
|
||||
@@ -59,6 +60,16 @@ class DatabaseNostrRepository(
|
||||
return database.unsignedNostrEventDao().observeProfile(publicKey)
|
||||
}
|
||||
|
||||
override suspend fun observeRelation(
|
||||
publicKey: HexKey,
|
||||
activePublicKey: HexKey
|
||||
): Flow<List<Connection>> {
|
||||
return database.connectionDao().observeRelation(
|
||||
alicePublicKey = publicKey,
|
||||
bobPublicKey = activePublicKey
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun observeUnsignedNostrEvents(publicKey: HexKey): Flow<UnsignedNostrEvent?> {
|
||||
return database.unsignedNostrEventDao().observeUnsignedNostrEvents(publicKey)
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package ac.cord.auxiliary.compose.repository
|
||||
|
||||
import ac.cord.auxiliary.compose.database.model.BroadcastNostrEventReceipt
|
||||
import ac.cord.auxiliary.compose.database.model.BroadcastNostrEventRequest
|
||||
import ac.cord.auxiliary.compose.database.model.Connection
|
||||
import ac.cord.auxiliary.compose.database.model.NostrEvent
|
||||
import ac.cord.auxiliary.compose.database.model.Profile
|
||||
import ac.cord.auxiliary.compose.database.model.RecentSearch
|
||||
@@ -21,6 +22,8 @@ import kotlinx.coroutines.flow.Flow
|
||||
interface NostrRepository {
|
||||
suspend fun observeProfile(publicKey: HexKey): Flow<LocalAccount?>
|
||||
|
||||
suspend fun observeRelation(publicKey: HexKey, activePublicKey: HexKey): Flow<List<Connection>>
|
||||
|
||||
suspend fun observeUnsignedNostrEvents(publicKey: HexKey): Flow<UnsignedNostrEvent?>
|
||||
|
||||
suspend fun observePendingBroadcastNostrEventRequests(): Flow<List<LocalBroadcastNostrEventRequest>>
|
||||
@@ -99,6 +102,13 @@ interface NostrRepository {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override suspend fun observeRelation(
|
||||
publicKey: HexKey,
|
||||
activePublicKey: HexKey
|
||||
): Flow<List<Connection>> {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override suspend fun observeUnsignedNostrEvents(publicKey: HexKey): Flow<UnsignedNostrEvent?> {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@ fun NostrEventDetailScreen(
|
||||
nostrRepository: NostrRepository,
|
||||
onNavigateBack: () -> Unit,
|
||||
onNavigateToEvent: (Route) -> Unit,
|
||||
onNavigateToEditProfile: () -> Unit,
|
||||
onNavigateToWriteAReply: (String) -> Unit
|
||||
) {
|
||||
val nostrEventDetailViewModel: NostrEventDetailViewModel = viewModel(
|
||||
@@ -67,6 +68,7 @@ fun NostrEventDetailScreen(
|
||||
localNostrEvent = feedListUIState.localNostrEvent,
|
||||
onNavigateBack = onNavigateBack,
|
||||
onNavigateToEvent = onNavigateToEvent,
|
||||
onNavigateToEditProfile = onNavigateToEditProfile,
|
||||
nostrRepository = nostrRepository
|
||||
)
|
||||
}
|
||||
@@ -160,7 +162,8 @@ It has survived not only five centuries, but also the leap into electronic types
|
||||
nostrRepository = NostrRepository.NO_OP_NOSTR_REPOSITORY,
|
||||
onNavigateBack = {},
|
||||
onNavigateToEvent = {},
|
||||
onNavigateToWriteAReply = {}
|
||||
onNavigateToWriteAReply = {},
|
||||
onNavigateToEditProfile = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -387,6 +387,11 @@ fun AuxNavHost(
|
||||
inReplyToEventId = nostrEventId
|
||||
)
|
||||
)
|
||||
},
|
||||
onNavigateToEditProfile = {
|
||||
navController.navigate(
|
||||
route = ImplementationPendingRoute("Edit Profile")
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import ac.cord.auxiliary.compose.ui.view.model.MetadataEventDetailViewModel
|
||||
import ac.cord.auxiliary.compose.ui.view.state.FeedListUIState
|
||||
import ac.cord.auxiliary.compose.ui.view.state.FollowersListUIState
|
||||
import ac.cord.auxiliary.compose.ui.view.state.FollowingListUIState
|
||||
import ac.cord.auxiliary.compose.ui.view.state.MetadataEventDetailUIState
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
@@ -30,13 +31,14 @@ import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ArrowBackIosNew
|
||||
import androidx.compose.material.icons.filled.Bolt
|
||||
import androidx.compose.material.icons.filled.Person
|
||||
import androidx.compose.material.icons.filled.Error
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.LargeTopAppBar
|
||||
import androidx.compose.material3.LoadingIndicator
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.PrimaryScrollableTabRow
|
||||
import androidx.compose.material3.Scaffold
|
||||
@@ -66,6 +68,7 @@ fun MetadataEventDetail(
|
||||
localNostrEvent: LocalNostrEvent,
|
||||
onNavigateBack: () -> Unit,
|
||||
onNavigateToEvent: (Route) -> Unit,
|
||||
onNavigateToEditProfile: () -> Unit,
|
||||
nostrRepository: NostrRepository
|
||||
) {
|
||||
val scope = rememberCoroutineScope()
|
||||
@@ -160,14 +163,64 @@ fun MetadataEventDetail(
|
||||
)
|
||||
|
||||
|
||||
Button(
|
||||
onClick = {
|
||||
|
||||
when(val metadataEventDetailUIState = metadataEventDetailViewModel.metadataEventDetailUIState) {
|
||||
MetadataEventDetailUIState.Error -> {
|
||||
Icon(
|
||||
Icons.Default.Error,
|
||||
contentDescription = "Something went wrong"
|
||||
)
|
||||
}
|
||||
is MetadataEventDetailUIState.Loaded -> {
|
||||
|
||||
if (metadataEventDetailViewModel.isActionPending.value) {
|
||||
LoadingIndicator()
|
||||
} else {
|
||||
if (metadataEventDetailUIState.isActiveUser) {
|
||||
Button(
|
||||
onClick = onNavigateToEditProfile
|
||||
) {
|
||||
Text(
|
||||
"Edit Profile"
|
||||
)
|
||||
}
|
||||
|
||||
} else if (metadataEventDetailUIState.followedByActiveUserConnection != null) {
|
||||
Button(
|
||||
onClick = {
|
||||
// Unfollow
|
||||
metadataEventDetailViewModel.unfollow(
|
||||
metadataEventDetailUIState.followedByActiveUserConnection
|
||||
)
|
||||
}
|
||||
) {
|
||||
Text(
|
||||
"Unfollow"
|
||||
)
|
||||
}
|
||||
} else {
|
||||
// flow
|
||||
Button(
|
||||
onClick = {
|
||||
metadataEventDetailViewModel.follow()
|
||||
}
|
||||
) {
|
||||
Text(
|
||||
text = if (metadataEventDetailUIState.followingActiveUserConnection == null) {
|
||||
"follow back"
|
||||
} else {
|
||||
"follow"
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
) {
|
||||
Text(
|
||||
"Follow"
|
||||
)
|
||||
MetadataEventDetailUIState.Loading -> {
|
||||
LoadingIndicator()
|
||||
}
|
||||
}
|
||||
}
|
||||
Text(
|
||||
@@ -182,7 +235,7 @@ fun MetadataEventDetail(
|
||||
}
|
||||
}
|
||||
|
||||
item {
|
||||
stickyHeader {
|
||||
PrimaryScrollableTabRow(
|
||||
modifier = Modifier.padding(10.dp),
|
||||
edgePadding = 10.dp,
|
||||
@@ -206,7 +259,9 @@ fun MetadataEventDetail(
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
item {
|
||||
Column(
|
||||
modifier = Modifier.fillParentMaxSize()
|
||||
) {
|
||||
@@ -289,6 +344,10 @@ fun MetadataEventDetail(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
key(true) {
|
||||
metadataEventDetailViewModel.initiate()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -321,6 +380,7 @@ private fun MetadataEventEventDetailPreview() {
|
||||
),
|
||||
onNavigateBack = {},
|
||||
onNavigateToEvent = {},
|
||||
onNavigateToEditProfile = {},
|
||||
nostrRepository = NostrRepository.NO_OP_NOSTR_REPOSITORY
|
||||
)
|
||||
}
|
||||
|
||||
@@ -73,9 +73,10 @@ class CreateProfileViewModel(
|
||||
isActionPending.value = true
|
||||
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
val publicKey = SeedManager.activePublicKey().toHexKey()
|
||||
|
||||
nostrRepository.createNewProfile(
|
||||
SeedManager.activePublicKey().toHexKey(),
|
||||
publicKey,
|
||||
name = createProfileFormState.nameField.text.value,
|
||||
biography = createProfileFormState.biographyField.text.value,
|
||||
onError = {
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
package ac.cord.auxiliary.compose.ui.view.model
|
||||
|
||||
import ac.cord.auxiliary.compose.database.model.SynchronizeNostrEventRequest
|
||||
import ac.cord.auxiliary.compose.database.model.Connection
|
||||
import ac.cord.auxiliary.compose.database.model.types.SynchronizationFilter
|
||||
import ac.cord.auxiliary.compose.nostr.Relays
|
||||
import ac.cord.auxiliary.compose.managers.SeedManager
|
||||
import ac.cord.auxiliary.compose.repository.NostrRepository
|
||||
import ac.cord.auxiliary.compose.ui.view.state.FeedListUIState
|
||||
import ac.cord.auxiliary.compose.ui.view.state.MetadataEventDetailUIState
|
||||
import androidx.compose.foundation.pager.PagerState
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
@@ -16,6 +17,7 @@ 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.toHexKey
|
||||
import com.vitorpamplona.quartz.nip02FollowList.ContactListEvent
|
||||
import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent
|
||||
import com.vitorpamplona.quartz.nip18Reposts.RepostEvent
|
||||
@@ -27,7 +29,6 @@ import com.vitorpamplona.quartz.nip56Reports.ReportEvent
|
||||
import com.vitorpamplona.quartz.nip57Zaps.LnZapEvent
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.IO
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
enum class MetadataEventDetailType {
|
||||
@@ -35,11 +36,41 @@ enum class MetadataEventDetailType {
|
||||
}
|
||||
|
||||
class MetadataEventDetailViewModel(
|
||||
initialMetadataEventDetailUIState: MetadataEventDetailUIState = MetadataEventDetailUIState.Loading,
|
||||
val publicKey: HexKey,
|
||||
val nostrRepository: NostrRepository,
|
||||
val pagerState: PagerState
|
||||
): ViewModel() {
|
||||
val logger = Logger.withTag(TAG)
|
||||
|
||||
val isActionPending: MutableState<Boolean> = mutableStateOf(false)
|
||||
|
||||
var metadataEventDetailUIState: MetadataEventDetailUIState by mutableStateOf(initialMetadataEventDetailUIState)
|
||||
private set
|
||||
|
||||
val logger = Logger.withTag("MetadataEventDetailViewModel")
|
||||
|
||||
fun initiate() {
|
||||
observeMetadataEventRelationWithActiveUser()
|
||||
}
|
||||
|
||||
fun observeMetadataEventRelationWithActiveUser() {
|
||||
logger.d("observeLocalNostrFeed")
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
val activePublicKey = SeedManager.activePublicKey().toHexKey()
|
||||
|
||||
nostrRepository.observeRelation(
|
||||
publicKey,
|
||||
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 }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getSynchronizationFilter(
|
||||
metadataEventDetailType: MetadataEventDetailType
|
||||
@@ -174,7 +205,20 @@ class MetadataEventDetailViewModel(
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Queue profile synchronization request...
|
||||
fun follow() {
|
||||
isActionPending.value = true
|
||||
|
||||
val activePublicKey = SeedManager.activePublicKey().toHexKey()
|
||||
|
||||
|
||||
}
|
||||
|
||||
fun unfollow(connection: Connection) {
|
||||
isActionPending.value = true
|
||||
|
||||
val activePublicKey = SeedManager.activePublicKey().toHexKey()
|
||||
|
||||
}
|
||||
companion object {
|
||||
const val TAG = "MetadataEventDetailViewModel"
|
||||
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
package ac.cord.auxiliary.compose.ui.view.state
|
||||
|
||||
import ac.cord.auxiliary.compose.database.model.Connection
|
||||
|
||||
|
||||
sealed interface MetadataEventDetailUIState {
|
||||
data class Loaded(
|
||||
val isActiveUser: Boolean,
|
||||
val followingActiveUserConnection: Connection?,
|
||||
val followedByActiveUserConnection: Connection?,
|
||||
): MetadataEventDetailUIState
|
||||
|
||||
data object Error: MetadataEventDetailUIState
|
||||
data object Loading: MetadataEventDetailUIState
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user