-
Notifications
You must be signed in to change notification settings - Fork 1
/
TopicFollower.kt
114 lines (102 loc) · 4.25 KB
/
TopicFollower.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
package com.dluvian.voyage.data.interactor
import android.content.Context
import android.util.Log
import androidx.compose.material3.SnackbarHostState
import com.dluvian.nostr_kt.getHashtags
import com.dluvian.nostr_kt.secs
import com.dluvian.voyage.R
import com.dluvian.voyage.core.FollowTopic
import com.dluvian.voyage.core.LIST_CHANGE_DEBOUNCE
import com.dluvian.voyage.core.SignerLauncher
import com.dluvian.voyage.core.Topic
import com.dluvian.voyage.core.TopicEvent
import com.dluvian.voyage.core.UnfollowTopic
import com.dluvian.voyage.core.launchIO
import com.dluvian.voyage.core.showToast
import com.dluvian.voyage.data.event.ValidatedTopicList
import com.dluvian.voyage.data.nostr.NostrService
import com.dluvian.voyage.data.provider.RelayProvider
import com.dluvian.voyage.data.room.dao.TopicDao
import com.dluvian.voyage.data.room.dao.tx.TopicUpsertDao
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
private const val TAG = "TopicFollower"
class TopicFollower(
private val nostrService: NostrService,
private val relayProvider: RelayProvider,
private val topicUpsertDao: TopicUpsertDao,
private val topicDao: TopicDao,
private val snackbar: SnackbarHostState,
private val context: Context,
private val forcedFollowStates: MutableStateFlow<Map<Topic, Boolean>>
) {
private val scope = CoroutineScope(Dispatchers.IO)
fun handle(action: TopicEvent) {
when (action) {
is FollowTopic -> handleAction(
topic = action.topic,
isFollowed = true,
signerLauncher = action.signerLauncher
)
is UnfollowTopic -> handleAction(
topic = action.topic,
isFollowed = false,
signerLauncher = action.signerLauncher
)
}
}
private var signerLauncher: SignerLauncher? = null
private fun handleAction(topic: Topic, isFollowed: Boolean, signerLauncher: SignerLauncher) {
updateForcedState(topic = topic, isFollowed = isFollowed)
this.signerLauncher = signerLauncher
handleFollowsInBackground()
}
private fun updateForcedState(topic: Topic, isFollowed: Boolean) {
synchronized(forcedFollowStates) {
val mutable = forcedFollowStates.value.toMutableMap()
mutable[topic] = isFollowed
forcedFollowStates.value = mutable
}
}
private var job: Job? = null
private fun handleFollowsInBackground() {
val nonNullSignerLauncher = signerLauncher ?: return
if (job?.isActive == true) return
job = scope.launchIO {
delay(LIST_CHANGE_DEBOUNCE)
val toHandle: Map<Topic, Boolean>
synchronized(forcedFollowStates) {
toHandle = forcedFollowStates.value.toMap()
}
val topicsBefore = topicDao.getMyTopics().toSet()
val topicsAdjusted = topicsBefore.toMutableSet()
val toAdd = toHandle.filter { (_, bool) -> bool }.map { (topic, _) -> topic }
topicsAdjusted.addAll(toAdd)
val toRemove = toHandle.filter { (_, bool) -> !bool }.map { (topic, _) -> topic }
topicsAdjusted.removeAll(toRemove.toSet())
if (topicsAdjusted == topicsBefore) return@launchIO
nostrService.publishTopicList(
topics = topicsAdjusted.toList(),
relayUrls = relayProvider.getPublishRelays(),
signerLauncher = nonNullSignerLauncher,
).onSuccess { event ->
val topicList = ValidatedTopicList(
myPubkey = event.author().toHex(),
topics = event.getHashtags().toSet(),
createdAt = event.createdAt().secs()
)
topicUpsertDao.upsertTopics(validatedTopicList = topicList)
}
.onFailure {
Log.w(TAG, "Failed to publish topic list: ${it.message}", it)
snackbar.showToast(
scope = scope,
msg = context.getString(R.string.failed_to_sign_topic_list)
)
}
}
}
}