mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2025-03-26 17:52:29 +01:00
Relay Management (View/Edit)
This commit is contained in:
parent
c33f7f615f
commit
a47aaab83c
@ -0,0 +1,71 @@
|
||||
package com.vitorpamplona.amethyst
|
||||
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.service.Constants
|
||||
import com.vitorpamplona.amethyst.service.NostrAccountDataSource
|
||||
import com.vitorpamplona.amethyst.service.NostrChannelDataSource
|
||||
import com.vitorpamplona.amethyst.service.NostrChatroomListDataSource
|
||||
import com.vitorpamplona.amethyst.service.NostrGlobalDataSource
|
||||
import com.vitorpamplona.amethyst.service.NostrHomeDataSource
|
||||
import com.vitorpamplona.amethyst.service.NostrNotificationDataSource
|
||||
import com.vitorpamplona.amethyst.service.NostrSingleEventDataSource
|
||||
import com.vitorpamplona.amethyst.service.NostrSingleUserDataSource
|
||||
import com.vitorpamplona.amethyst.service.NostrThreadDataSource
|
||||
import com.vitorpamplona.amethyst.service.NostrUserProfileDataSource
|
||||
import com.vitorpamplona.amethyst.service.NostrUserProfileFollowersDataSource
|
||||
import com.vitorpamplona.amethyst.service.NostrUserProfileFollowsDataSource
|
||||
import com.vitorpamplona.amethyst.service.relays.Client
|
||||
|
||||
object ServiceManager {
|
||||
private var account: Account? = null
|
||||
|
||||
fun start(account: Account) {
|
||||
this.account = account
|
||||
start()
|
||||
}
|
||||
|
||||
fun start() {
|
||||
val myAccount = account
|
||||
|
||||
if (myAccount != null) {
|
||||
Client.connect(myAccount.activeRelays() ?: Constants.defaultRelays)
|
||||
|
||||
// start services
|
||||
NostrAccountDataSource.account = myAccount
|
||||
NostrHomeDataSource.account = myAccount
|
||||
NostrNotificationDataSource.account = myAccount
|
||||
NostrChatroomListDataSource.account = myAccount
|
||||
|
||||
NostrAccountDataSource.start()
|
||||
NostrGlobalDataSource.start()
|
||||
NostrHomeDataSource.start()
|
||||
NostrNotificationDataSource.start()
|
||||
NostrSingleEventDataSource.start()
|
||||
NostrSingleUserDataSource.start()
|
||||
NostrThreadDataSource.start()
|
||||
NostrChatroomListDataSource.start()
|
||||
} else {
|
||||
// if not logged in yet, start a basic service wit default relays
|
||||
Client.connect()
|
||||
}
|
||||
}
|
||||
|
||||
fun pause() {
|
||||
NostrAccountDataSource.stop()
|
||||
NostrHomeDataSource.stop()
|
||||
NostrChannelDataSource.stop()
|
||||
NostrChatroomListDataSource.stop()
|
||||
NostrUserProfileDataSource.stop()
|
||||
NostrUserProfileFollowersDataSource.stop()
|
||||
NostrUserProfileFollowsDataSource.stop()
|
||||
|
||||
NostrGlobalDataSource.stop()
|
||||
NostrNotificationDataSource.stop()
|
||||
NostrSingleEventDataSource.stop()
|
||||
NostrSingleUserDataSource.stop()
|
||||
NostrThreadDataSource.stop()
|
||||
|
||||
Client.disconnect()
|
||||
}
|
||||
|
||||
}
|
@ -8,6 +8,8 @@ import com.vitorpamplona.amethyst.service.model.ChannelMetadataEvent
|
||||
import com.vitorpamplona.amethyst.service.model.ReactionEvent
|
||||
import com.vitorpamplona.amethyst.service.model.RepostEvent
|
||||
import com.vitorpamplona.amethyst.service.relays.Client
|
||||
import com.vitorpamplona.amethyst.service.relays.Relay
|
||||
import com.vitorpamplona.amethyst.service.relays.RelayPool
|
||||
import com.vitorpamplona.amethyst.ui.screen.AccountStateViewModel
|
||||
import java.util.Date
|
||||
import nostr.postr.Contact
|
||||
@ -40,6 +42,23 @@ class Account(val loggedIn: Persona, val followingChannels: MutableSet<String> =
|
||||
return loggedIn.privKey != null
|
||||
}
|
||||
|
||||
fun sendNewRelayList(relays: Map<String, ContactListEvent.ReadWrite>) {
|
||||
if (!isWriteable()) return
|
||||
|
||||
val lastestContactList = userProfile().latestContactList
|
||||
val event = if (lastestContactList != null) {
|
||||
ContactListEvent.create(
|
||||
lastestContactList.follows,
|
||||
relays,
|
||||
loggedIn.privKey!!)
|
||||
} else {
|
||||
ContactListEvent.create(listOf(), relays, loggedIn.privKey!!)
|
||||
}
|
||||
|
||||
Client.send(event)
|
||||
LocalCache.consume(event)
|
||||
}
|
||||
|
||||
fun sendNewUserMetadata(toString: String) {
|
||||
if (!isWriteable()) return
|
||||
|
||||
@ -234,6 +253,21 @@ class Account(val loggedIn: Persona, val followingChannels: MutableSet<String> =
|
||||
}
|
||||
}
|
||||
|
||||
fun activeRelays(): Array<Relay>? {
|
||||
return userProfile().relays?.map { Relay(it.key, it.value.read, it.value.write) }?.toTypedArray()
|
||||
}
|
||||
|
||||
init {
|
||||
userProfile().subscribe(object: User.Listener() {
|
||||
override fun onRelayChange() {
|
||||
println("Updating Relays AAA Account")
|
||||
Client.disconnect()
|
||||
Client.connect(activeRelays() ?: Constants.defaultRelays)
|
||||
RelayPool.requestAndWatch()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Observers line up here.
|
||||
val live: AccountLiveData = AccountLiveData(this)
|
||||
|
||||
|
@ -4,6 +4,7 @@ import android.util.Log
|
||||
import androidx.lifecycle.LiveData
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature
|
||||
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import com.vitorpamplona.amethyst.service.model.ChannelCreateEvent
|
||||
import com.vitorpamplona.amethyst.service.model.ChannelHideMessageEvent
|
||||
import com.vitorpamplona.amethyst.service.model.ChannelMessageEvent
|
||||
@ -19,6 +20,7 @@ import java.util.Collections
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import nostr.postr.events.ContactListEvent
|
||||
import nostr.postr.events.DeletionEvent
|
||||
import nostr.postr.events.Event
|
||||
import nostr.postr.events.MetadataEvent
|
||||
import nostr.postr.events.PrivateDmEvent
|
||||
import nostr.postr.events.RecommendRelayEvent
|
||||
@ -110,10 +112,10 @@ object LocalCache {
|
||||
|
||||
// Adds notifications to users.
|
||||
mentions.forEach {
|
||||
it.taggedPosts.add(note)
|
||||
it.addTaggedPost(note)
|
||||
}
|
||||
replyTo.forEach {
|
||||
it.author?.taggedPosts?.add(note)
|
||||
it.author?.addTaggedPost(note)
|
||||
}
|
||||
|
||||
// Counts the replies
|
||||
@ -148,6 +150,26 @@ object LocalCache {
|
||||
event.createdAt
|
||||
)
|
||||
|
||||
if (user.pubkeyHex == "460c25e682fda7832b52d1f22d3d22b3176d972f60dcdc3212ed8c92ef85065c")
|
||||
println("Updating Relays AAA ${user.toBestDisplayName()} ${event.content} ${formattedDateTime(event.createdAt)}")
|
||||
|
||||
try {
|
||||
if (event.content.isNotEmpty()) {
|
||||
if (user.pubkeyHex == "460c25e682fda7832b52d1f22d3d22b3176d972f60dcdc3212ed8c92ef85065c")
|
||||
println("Updating Relays AAA 1 ${user.toBestDisplayName()} ${event.content}")
|
||||
val relays: Map<String, ContactListEvent.ReadWrite> =
|
||||
Event.gson.fromJson(
|
||||
event.content,
|
||||
object : TypeToken<Map<String, ContactListEvent.ReadWrite>>() {}.type
|
||||
)
|
||||
user.updateRelays(relays)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
if (user.pubkeyHex == "460c25e682fda7832b52d1f22d3d22b3176d972f60dcdc3212ed8c92ef85065c")
|
||||
println("Updating Relays AAA 2 for ${user.bestUsername()} ${e.message}")
|
||||
e.printStackTrace()
|
||||
}
|
||||
|
||||
user.latestContactList = event
|
||||
}
|
||||
|
||||
@ -201,10 +223,10 @@ object LocalCache {
|
||||
|
||||
// Adds notifications to users.
|
||||
mentions.forEach {
|
||||
it.taggedPosts.add(note)
|
||||
it.addTaggedPost(note)
|
||||
}
|
||||
repliesTo.forEach {
|
||||
it.author?.taggedPosts?.add(note)
|
||||
it.author?.addTaggedPost(note)
|
||||
}
|
||||
|
||||
// Counts the replies
|
||||
@ -231,10 +253,10 @@ object LocalCache {
|
||||
|
||||
// Adds notifications to users.
|
||||
mentions.forEach {
|
||||
it.taggedPosts.add(note)
|
||||
it.addTaggedPost(note)
|
||||
}
|
||||
repliesTo.forEach {
|
||||
it.author?.taggedPosts?.add(note)
|
||||
it.author?.addTaggedPost(note)
|
||||
}
|
||||
|
||||
if (event.content == "" || event.content == "+" || event.content == "\uD83E\uDD19") {
|
||||
@ -312,10 +334,10 @@ object LocalCache {
|
||||
|
||||
// Adds notifications to users.
|
||||
mentions.forEach {
|
||||
it.taggedPosts.add(note)
|
||||
it.addTaggedPost(note)
|
||||
}
|
||||
replyTo.forEach {
|
||||
it.author?.taggedPosts?.add(note)
|
||||
it.author?.addTaggedPost(note)
|
||||
}
|
||||
|
||||
// Counts the replies
|
||||
|
@ -2,6 +2,8 @@ package com.vitorpamplona.amethyst.model
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import com.vitorpamplona.amethyst.service.NostrSingleUserDataSource
|
||||
import com.vitorpamplona.amethyst.service.relays.Client
|
||||
import com.vitorpamplona.amethyst.service.relays.Relay
|
||||
import com.vitorpamplona.amethyst.ui.note.toShortenHex
|
||||
import java.util.Collections
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
@ -11,6 +13,7 @@ import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import nostr.postr.events.ContactListEvent
|
||||
import nostr.postr.events.Event
|
||||
import nostr.postr.events.MetadataEvent
|
||||
|
||||
class User(val pubkey: ByteArray) {
|
||||
@ -27,8 +30,11 @@ class User(val pubkey: ByteArray) {
|
||||
|
||||
val notes = Collections.synchronizedSet(mutableSetOf<Note>())
|
||||
val follows = Collections.synchronizedSet(mutableSetOf<User>())
|
||||
|
||||
val taggedPosts = Collections.synchronizedSet(mutableSetOf<Note>())
|
||||
|
||||
var relays: Map<String, ContactListEvent.ReadWrite>? = null
|
||||
|
||||
val followers = Collections.synchronizedSet(mutableSetOf<User>())
|
||||
val messages = ConcurrentHashMap<User, MutableSet<Note>>()
|
||||
|
||||
@ -55,6 +61,10 @@ class User(val pubkey: ByteArray) {
|
||||
|
||||
invalidateData()
|
||||
user.invalidateData()
|
||||
|
||||
listeners.forEach {
|
||||
it.onFollowsChange()
|
||||
}
|
||||
}
|
||||
fun unfollow(user: User) {
|
||||
follows.remove(user)
|
||||
@ -62,6 +72,15 @@ class User(val pubkey: ByteArray) {
|
||||
|
||||
invalidateData()
|
||||
user.invalidateData()
|
||||
|
||||
updateSubscribers {
|
||||
it.onFollowsChange()
|
||||
}
|
||||
}
|
||||
|
||||
fun addTaggedPost(note: Note) {
|
||||
taggedPosts.add(note)
|
||||
updateSubscribers { it.onNewPosts() }
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
@ -76,6 +95,7 @@ class User(val pubkey: ByteArray) {
|
||||
fun addMessage(user: User, msg: Note) {
|
||||
getOrCreateChannel(user).add(msg)
|
||||
live.refresh()
|
||||
updateSubscribers { it.onNewMessage() }
|
||||
}
|
||||
|
||||
fun updateFollows(newFollows: Set<User>, updateAt: Long) {
|
||||
@ -95,6 +115,15 @@ class User(val pubkey: ByteArray) {
|
||||
updatedFollowsAt = updateAt
|
||||
}
|
||||
|
||||
fun updateRelays(relayUse: Map<String, ContactListEvent.ReadWrite>) {
|
||||
if (relays != relayUse) {
|
||||
relays = relayUse
|
||||
listeners.forEach {
|
||||
it.onRelayChange()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun updateUserInfo(newUserInfo: UserMetadata, updateAt: Long) {
|
||||
info = newUserInfo
|
||||
updatedMetadataAt = updateAt
|
||||
@ -108,7 +137,42 @@ class User(val pubkey: ByteArray) {
|
||||
}
|
||||
}
|
||||
|
||||
// Observers line up here.
|
||||
// Model Observers
|
||||
private var listeners = setOf<Listener>()
|
||||
|
||||
fun subscribe(listener: Listener) {
|
||||
listeners = listeners.plus(listener)
|
||||
}
|
||||
|
||||
fun unsubscribe(listener: Listener) {
|
||||
listeners = listeners.minus(listener)
|
||||
}
|
||||
|
||||
abstract class Listener {
|
||||
open fun onRelayChange() = Unit
|
||||
open fun onFollowsChange() = Unit
|
||||
open fun onNewPosts() = Unit
|
||||
open fun onNewMessage() = Unit
|
||||
}
|
||||
|
||||
// Refreshes observers in batches.
|
||||
var modelHandlerWaiting = false
|
||||
@Synchronized
|
||||
fun updateSubscribers(on: (Listener) -> Unit) {
|
||||
if (modelHandlerWaiting) return
|
||||
|
||||
modelHandlerWaiting = true
|
||||
val scope = CoroutineScope(Job() + Dispatchers.Main)
|
||||
scope.launch {
|
||||
delay(100)
|
||||
listeners.forEach {
|
||||
on(it)
|
||||
}
|
||||
modelHandlerWaiting = false
|
||||
}
|
||||
}
|
||||
|
||||
// UI Observers line up here.
|
||||
val live: UserLiveData = UserLiveData(this)
|
||||
|
||||
// Refreshes observers in batches.
|
||||
|
@ -13,27 +13,11 @@ import nostr.postr.events.TextNoteEvent
|
||||
object NostrAccountDataSource: NostrDataSource<Note>("AccountData") {
|
||||
lateinit var account: Account
|
||||
|
||||
private val cacheListener: (UserState) -> Unit = {
|
||||
resetFilters()
|
||||
}
|
||||
|
||||
override fun start() {
|
||||
if (this::account.isInitialized)
|
||||
account.userProfile().live.observeForever(cacheListener)
|
||||
super.start()
|
||||
}
|
||||
|
||||
override fun stop() {
|
||||
super.stop()
|
||||
if (this::account.isInitialized)
|
||||
account.userProfile().live.removeObserver(cacheListener)
|
||||
}
|
||||
|
||||
fun createAccountContactListFilter(): JsonFilter {
|
||||
return JsonFilter(
|
||||
kinds = listOf(ContactListEvent.kind),
|
||||
authors = listOf(account.userProfile().pubkeyHex),
|
||||
limit = 1
|
||||
limit = 5
|
||||
)
|
||||
}
|
||||
|
||||
@ -41,7 +25,7 @@ object NostrAccountDataSource: NostrDataSource<Note>("AccountData") {
|
||||
return JsonFilter(
|
||||
kinds = listOf(MetadataEvent.kind),
|
||||
authors = listOf(account.userProfile().pubkeyHex),
|
||||
limit = 3
|
||||
limit = 5
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -3,8 +3,12 @@ package com.vitorpamplona.amethyst.service
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.model.LocalCache
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.model.User
|
||||
import com.vitorpamplona.amethyst.model.UserState
|
||||
import com.vitorpamplona.amethyst.service.model.RepostEvent
|
||||
import com.vitorpamplona.amethyst.service.relays.Client
|
||||
import com.vitorpamplona.amethyst.service.relays.Relay
|
||||
import com.vitorpamplona.amethyst.service.relays.RelayPool
|
||||
import nostr.postr.JsonFilter
|
||||
import nostr.postr.events.TextNoteEvent
|
||||
import nostr.postr.toHex
|
||||
@ -12,20 +16,22 @@ import nostr.postr.toHex
|
||||
object NostrHomeDataSource: NostrDataSource<Note>("HomeFeed") {
|
||||
lateinit var account: Account
|
||||
|
||||
private val cacheListener: (UserState) -> Unit = {
|
||||
resetFilters()
|
||||
object cacheListener: User.Listener() {
|
||||
override fun onFollowsChange() {
|
||||
resetFilters()
|
||||
}
|
||||
}
|
||||
|
||||
override fun start() {
|
||||
if (this::account.isInitialized)
|
||||
account.userProfile().live.observeForever(cacheListener)
|
||||
account.userProfile().subscribe(cacheListener)
|
||||
super.start()
|
||||
}
|
||||
|
||||
override fun stop() {
|
||||
super.stop()
|
||||
if (this::account.isInitialized)
|
||||
account.userProfile().live.removeObserver(cacheListener)
|
||||
account.userProfile().unsubscribe(cacheListener)
|
||||
}
|
||||
|
||||
fun createFollowAccountsFilter(): JsonFilter {
|
||||
|
@ -2,6 +2,7 @@ package com.vitorpamplona.amethyst.service
|
||||
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.model.User
|
||||
import nostr.postr.JsonFilter
|
||||
|
||||
object NostrNotificationDataSource: NostrDataSource<Note>("NotificationFeed") {
|
||||
|
@ -27,10 +27,9 @@ object Client: RelayPool.Listener {
|
||||
private var relays = Constants.defaultRelays
|
||||
private val subscriptions = mutableMapOf<String, List<JsonFilter>>()
|
||||
|
||||
fun connect(
|
||||
relays: Array<Relay> = Constants.defaultRelays
|
||||
) {
|
||||
fun connect(relays: Array<Relay> = Constants.defaultRelays) {
|
||||
RelayPool.register(this)
|
||||
RelayPool.unloadRelays()
|
||||
RelayPool.loadRelays(relays.toList())
|
||||
this.relays = relays
|
||||
}
|
||||
|
@ -9,14 +9,18 @@ import okhttp3.WebSocket
|
||||
import okhttp3.WebSocketListener
|
||||
|
||||
class Relay(
|
||||
val url: String,
|
||||
var read: Boolean = true,
|
||||
var write: Boolean = true
|
||||
var url: String,
|
||||
var read: Boolean = true,
|
||||
var write: Boolean = true
|
||||
) {
|
||||
private val httpClient = OkHttpClient()
|
||||
private var listeners = setOf<Listener>()
|
||||
private var socket: WebSocket? = null
|
||||
|
||||
var eventDownloadCounter = 0
|
||||
var eventUploadCounter = 0
|
||||
var errorCounter = 0
|
||||
|
||||
fun register(listener: Listener) {
|
||||
listeners = listeners.plus(listener)
|
||||
}
|
||||
@ -49,6 +53,7 @@ class Relay(
|
||||
val channel = msg[1].asString
|
||||
when (type) {
|
||||
"EVENT" -> {
|
||||
eventDownloadCounter++
|
||||
val event = Event.fromJson(msg[2], Client.lenient)
|
||||
listeners.forEach { it.onEvent(this@Relay, channel, event) }
|
||||
}
|
||||
@ -88,6 +93,8 @@ class Relay(
|
||||
}
|
||||
|
||||
override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) {
|
||||
errorCounter++
|
||||
|
||||
socket?.close(1000, "Normal close")
|
||||
// Failures disconnect the relay.
|
||||
socket = null
|
||||
@ -98,6 +105,7 @@ class Relay(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
socket = httpClient.newWebSocket(request, listener)
|
||||
}
|
||||
|
||||
@ -108,14 +116,17 @@ class Relay(
|
||||
}
|
||||
|
||||
fun sendFilter(requestId: String) {
|
||||
if (socket == null) {
|
||||
requestAndWatch()
|
||||
} else {
|
||||
val filters = Client.getSubscriptionFilters(requestId)
|
||||
if (filters.isNotEmpty()) {
|
||||
val request = """["REQ","$requestId",${filters.joinToString(",") { it.toJson() }}]"""
|
||||
//println("FILTERSSENT " + """["REQ","$requestId",${filters.joinToString(",") { it.toJson() }}]""")
|
||||
socket?.send(request)
|
||||
if (read) {
|
||||
if (socket == null) {
|
||||
requestAndWatch()
|
||||
} else {
|
||||
val filters = Client.getSubscriptionFilters(requestId)
|
||||
if (filters.isNotEmpty()) {
|
||||
val request =
|
||||
"""["REQ","$requestId",${filters.joinToString(",") { it.toJson() }}]"""
|
||||
//println("FILTERSSENT " + """["REQ","$requestId",${filters.joinToString(",") { it.toJson() }}]""")
|
||||
socket?.send(request)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -127,8 +138,10 @@ class Relay(
|
||||
}
|
||||
|
||||
fun send(signedEvent: Event) {
|
||||
if (write)
|
||||
if (write) {
|
||||
socket?.send("""["EVENT",${signedEvent.toJson()}]""")
|
||||
eventUploadCounter++
|
||||
}
|
||||
}
|
||||
|
||||
fun close(subscriptionId: String){
|
||||
|
@ -24,6 +24,10 @@ object RelayPool: Relay.Listener {
|
||||
return relays.filter { it.isConnected() }.size
|
||||
}
|
||||
|
||||
fun getRelay(url: String): Relay? {
|
||||
return relays.firstOrNull() { it.url == url }
|
||||
}
|
||||
|
||||
fun loadRelays(relayList: List<Relay>? = null){
|
||||
if (!relayList.isNullOrEmpty()){
|
||||
relayList.forEach { addRelay(it) }
|
||||
|
@ -15,6 +15,8 @@ import coil.decode.GifDecoder
|
||||
import coil.decode.ImageDecoderDecoder
|
||||
import coil.decode.SvgDecoder
|
||||
import com.vitorpamplona.amethyst.KeyStorage
|
||||
import com.vitorpamplona.amethyst.ServiceManager
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.service.NostrAccountDataSource
|
||||
import com.vitorpamplona.amethyst.service.NostrChannelDataSource
|
||||
import com.vitorpamplona.amethyst.service.NostrChatroomListDataSource
|
||||
@ -68,24 +70,13 @@ class MainActivity : ComponentActivity() {
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
Client.connect()
|
||||
|
||||
ServiceManager.start()
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
NostrAccountDataSource.stop()
|
||||
NostrHomeDataSource.stop()
|
||||
NostrChannelDataSource.stop()
|
||||
NostrChatroomListDataSource.stop()
|
||||
NostrUserProfileDataSource.stop()
|
||||
NostrUserProfileFollowersDataSource.stop()
|
||||
NostrUserProfileFollowsDataSource.stop()
|
||||
ServiceManager.pause()
|
||||
|
||||
NostrGlobalDataSource.stop()
|
||||
NostrNotificationDataSource.stop()
|
||||
NostrSingleEventDataSource.stop()
|
||||
NostrSingleUserDataSource.stop()
|
||||
NostrThreadDataSource.stop()
|
||||
Client.disconnect()
|
||||
super.onPause()
|
||||
}
|
||||
}
|
@ -0,0 +1,369 @@
|
||||
package com.vitorpamplona.amethyst.ui.actions
|
||||
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material.Button
|
||||
import androidx.compose.material.ButtonDefaults
|
||||
import androidx.compose.material.Checkbox
|
||||
import androidx.compose.material.Colors
|
||||
import androidx.compose.material.Divider
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.IconButton
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.OutlinedTextField
|
||||
import androidx.compose.material.Surface
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.material.icons.filled.Cancel
|
||||
import androidx.compose.material.icons.filled.Delete
|
||||
import androidx.compose.material.icons.filled.Download
|
||||
import androidx.compose.material.icons.filled.DownloadDone
|
||||
import androidx.compose.material.icons.filled.SyncProblem
|
||||
import androidx.compose.material.icons.filled.Upload
|
||||
import androidx.compose.material.icons.outlined.BarChart
|
||||
import androidx.compose.material.icons.outlined.Delete
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.input.KeyboardCapitalization
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import androidx.compose.ui.window.DialogProperties
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import java.lang.Math.round
|
||||
|
||||
@OptIn(ExperimentalComposeUiApi::class)
|
||||
@Composable
|
||||
fun NewRelayListView(onClose: () -> Unit, account: Account) {
|
||||
val postViewModel: NewRelayListViewModel = viewModel()
|
||||
|
||||
val feedState by postViewModel.relays.collectAsState()
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
postViewModel.load(account)
|
||||
}
|
||||
|
||||
Dialog(
|
||||
onDismissRequest = { onClose() },
|
||||
properties = DialogProperties(
|
||||
usePlatformDefaultWidth = false,
|
||||
dismissOnClickOutside = false
|
||||
)
|
||||
) {
|
||||
Surface(
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(10.dp)
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
CloseButton(onCancel = {
|
||||
postViewModel.clear()
|
||||
onClose()
|
||||
})
|
||||
|
||||
PostButton(
|
||||
onPost = {
|
||||
postViewModel.create()
|
||||
onClose()
|
||||
},
|
||||
true
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(10.dp))
|
||||
|
||||
Row(modifier = Modifier.fillMaxWidth(1f), verticalAlignment = Alignment.CenterVertically) {
|
||||
LazyColumn(
|
||||
contentPadding = PaddingValues(
|
||||
top = 10.dp,
|
||||
bottom = 10.dp
|
||||
),
|
||||
) {
|
||||
itemsIndexed(feedState, key = { _, item -> item.url }) { index, item ->
|
||||
if (index == 0)
|
||||
ServerConfigHeader()
|
||||
ServerConfig(item,
|
||||
onToggleDownload = {
|
||||
postViewModel.toggleDownload(it)
|
||||
},
|
||||
onToggleUpload = {
|
||||
postViewModel.toggleUpload(it)
|
||||
},
|
||||
onDelete = {
|
||||
postViewModel.deleteRelay(it)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(10.dp))
|
||||
|
||||
EditableServerConfig() {
|
||||
postViewModel.addRelay(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ServerConfigHeader() {
|
||||
Column(Modifier.fillMaxWidth()) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Column(Modifier.weight(1f)) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Text(
|
||||
text = "Relay Address",
|
||||
modifier = Modifier.weight(1f),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Column(Modifier.weight(1f)) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Spacer(modifier = Modifier.size(20.dp))
|
||||
|
||||
Text(
|
||||
text = "Posts",
|
||||
maxLines = 1,
|
||||
fontSize = 14.sp,
|
||||
modifier = Modifier.weight(1f),
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.size(10.dp))
|
||||
|
||||
Text(
|
||||
text = "Posts",
|
||||
maxLines = 1,
|
||||
fontSize = 14.sp,
|
||||
modifier = Modifier.weight(1f),
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.size(10.dp))
|
||||
|
||||
Text(
|
||||
text = "Errors",
|
||||
maxLines = 1,
|
||||
fontSize = 14.sp,
|
||||
modifier = Modifier.weight(1f),
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.size(20.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Divider(
|
||||
thickness = 0.25.dp
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ServerConfig(
|
||||
item: NewRelayListViewModel.Relay,
|
||||
onToggleDownload: (NewRelayListViewModel.Relay) -> Unit,
|
||||
onToggleUpload: (NewRelayListViewModel.Relay) -> Unit,
|
||||
onDelete: (NewRelayListViewModel.Relay) -> Unit) {
|
||||
Column(Modifier.fillMaxWidth()) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Column(Modifier.weight(1f)) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Text(
|
||||
text = item.url.removePrefix("wss://"),
|
||||
modifier = Modifier.weight(1f),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Column(Modifier.weight(1f)) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
IconButton(
|
||||
modifier = Modifier.size(30.dp),
|
||||
onClick = { onToggleDownload(item) }
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Download,
|
||||
null,
|
||||
modifier = Modifier.padding(horizontal = 5.dp).size(15.dp),
|
||||
tint = if (item.read) Color.Green else Color.Red
|
||||
)
|
||||
}
|
||||
|
||||
Text(
|
||||
text = "${countToHumanReadable(item.downloadCount)}",
|
||||
maxLines = 1,
|
||||
fontSize = 14.sp,
|
||||
modifier = Modifier.weight(1f),
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
|
||||
)
|
||||
|
||||
IconButton(
|
||||
modifier = Modifier.size(30.dp),
|
||||
onClick = { onToggleUpload(item) }
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Upload,
|
||||
null,
|
||||
modifier = Modifier.padding(horizontal = 5.dp).size(15.dp),
|
||||
tint = if (item.write) Color.Green else Color.Red
|
||||
)
|
||||
}
|
||||
|
||||
Text(
|
||||
text = "${countToHumanReadable(item.uploadCount)}",
|
||||
maxLines = 1,
|
||||
fontSize = 14.sp,
|
||||
modifier = Modifier.weight(1f),
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
|
||||
)
|
||||
|
||||
Icon(
|
||||
imageVector = Icons.Default.SyncProblem,
|
||||
null,
|
||||
modifier = Modifier.padding(horizontal = 5.dp).size(15.dp),
|
||||
tint = if (item.errorCount > 0) Color.Yellow else Color.Green
|
||||
)
|
||||
|
||||
Text(
|
||||
text = "${countToHumanReadable(item.errorCount)}",
|
||||
maxLines = 1,
|
||||
fontSize = 14.sp,
|
||||
modifier = Modifier.weight(1f),
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
|
||||
)
|
||||
|
||||
IconButton(
|
||||
modifier = Modifier.size(30.dp),
|
||||
onClick = { onDelete(item) }
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Cancel,
|
||||
null,
|
||||
modifier = Modifier.padding(horizontal = 5.dp).size(15.dp),
|
||||
tint = Color.Red
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Divider(
|
||||
thickness = 0.25.dp
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun EditableServerConfig(onNewRelay: (NewRelayListViewModel.Relay) -> Unit) {
|
||||
var url by remember { mutableStateOf<String>("") }
|
||||
var read by remember { mutableStateOf(true) }
|
||||
var write by remember { mutableStateOf(true) }
|
||||
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
OutlinedTextField(
|
||||
label = { Text(text = "Add a Relay") },
|
||||
modifier = Modifier.weight(1f),
|
||||
value = url,
|
||||
onValueChange = { url = it },
|
||||
placeholder = {
|
||||
Text(
|
||||
text = "server.com",
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
|
||||
maxLines = 1
|
||||
)
|
||||
},
|
||||
singleLine = true
|
||||
)
|
||||
|
||||
IconButton(onClick = { read = !read }) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Download,
|
||||
null,
|
||||
modifier = Modifier
|
||||
.size(35.dp)
|
||||
.padding(horizontal = 5.dp),
|
||||
tint = if (read) Color.Green else Color.Red
|
||||
)
|
||||
}
|
||||
|
||||
IconButton(onClick = { write = !write }) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Upload,
|
||||
null,
|
||||
modifier = Modifier
|
||||
.size(35.dp)
|
||||
.padding(horizontal = 5.dp),
|
||||
tint = if (write) Color.Green else Color.Red
|
||||
)
|
||||
}
|
||||
|
||||
Button(
|
||||
onClick = {
|
||||
if (url.isNotBlank()) {
|
||||
val addedWSS = if (!url.startsWith("wss://")) "wss://$url" else url
|
||||
onNewRelay(NewRelayListViewModel.Relay(addedWSS, read, write))
|
||||
url = ""
|
||||
write = true
|
||||
read = true
|
||||
}
|
||||
},
|
||||
shape = RoundedCornerShape(20.dp),
|
||||
colors = ButtonDefaults
|
||||
.buttonColors(
|
||||
backgroundColor = if (url.isNotBlank()) MaterialTheme.colors.primary else Color.Gray
|
||||
)
|
||||
) {
|
||||
Text(text = "Add", color = Color.White)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun countToHumanReadable(counter: Int) = when {
|
||||
counter >= 1000000000 -> "${round(counter/1000000000f)}G"
|
||||
counter >= 1000000 -> "${round(counter/1000000f)}M"
|
||||
counter >= 1000 -> "${round(counter/1000f)}k"
|
||||
else -> "$counter"
|
||||
}
|
@ -0,0 +1,92 @@
|
||||
package com.vitorpamplona.amethyst.ui.actions
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.service.Constants
|
||||
import com.vitorpamplona.amethyst.service.relays.RelayPool
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import nostr.postr.events.ContactListEvent
|
||||
|
||||
class NewRelayListViewModel: ViewModel() {
|
||||
private lateinit var account: Account
|
||||
|
||||
data class Relay(
|
||||
val url: String,
|
||||
val read: Boolean,
|
||||
val write: Boolean,
|
||||
val errorCount: Int = 0,
|
||||
val downloadCount: Int = 0,
|
||||
val uploadCount: Int = 0
|
||||
)
|
||||
|
||||
private val _relays = MutableStateFlow<List<Relay>>(emptyList())
|
||||
val relays = _relays.asStateFlow()
|
||||
|
||||
fun load(account: Account) {
|
||||
this.account = account
|
||||
clear()
|
||||
}
|
||||
|
||||
fun create() {
|
||||
relays.let {
|
||||
account.sendNewRelayList(it.value.associate { it.url to ContactListEvent.ReadWrite(it.read, it.write) } )
|
||||
}
|
||||
|
||||
clear()
|
||||
}
|
||||
|
||||
fun clear() {
|
||||
_relays.update {
|
||||
val relayFile = account.userProfile().relays
|
||||
|
||||
if (relayFile != null)
|
||||
relayFile.map {
|
||||
val liveRelay = RelayPool.getRelay(it.key)
|
||||
|
||||
val errorCounter = liveRelay?.errorCounter ?: 0
|
||||
val eventDownloadCounter = liveRelay?.eventDownloadCounter ?: 0
|
||||
val eventUploadCounter = liveRelay?.eventUploadCounter ?: 0
|
||||
|
||||
Relay(it.key, it.value.read, it.value.write, errorCounter, eventDownloadCounter, eventUploadCounter)
|
||||
}.sortedBy { it.downloadCount }.reversed()
|
||||
else
|
||||
Constants.defaultRelays.map {
|
||||
val liveRelay = RelayPool.getRelay(it.url)
|
||||
|
||||
val errorCounter = liveRelay?.errorCounter ?: 0
|
||||
val eventDownloadCounter = liveRelay?.eventDownloadCounter ?: 0
|
||||
val eventUploadCounter = liveRelay?.eventUploadCounter ?: 0
|
||||
|
||||
Relay(it.url, it.read, it.write, errorCounter, eventDownloadCounter, eventUploadCounter)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun addRelay(relay: Relay) {
|
||||
_relays.update {
|
||||
it.plus(relay)
|
||||
}
|
||||
}
|
||||
|
||||
fun deleteRelay(relay: Relay) {
|
||||
_relays.update {
|
||||
it.minus(relay)
|
||||
}
|
||||
}
|
||||
|
||||
fun toggleDownload(relay: Relay) {
|
||||
_relays.update {
|
||||
it.updated(relay, relay.copy(read = !relay.read))
|
||||
}
|
||||
}
|
||||
|
||||
fun toggleUpload(relay: Relay) {
|
||||
_relays.update {
|
||||
it.updated(relay, relay.copy(write = !relay.write))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun <T> Iterable<T>.updated(old: T, new: T): List<T> = map { if (it == old) new else it }
|
@ -1,7 +1,11 @@
|
||||
package com.vitorpamplona.amethyst.ui.navigation
|
||||
|
||||
import android.util.Log
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Box
|
||||
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.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
@ -20,12 +24,17 @@ import androidx.compose.material.icons.filled.ArrowBack
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
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.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.font.FontStyle
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.navigation.NavHostController
|
||||
@ -46,6 +55,8 @@ import com.vitorpamplona.amethyst.service.NostrUserProfileFollowersDataSource
|
||||
import com.vitorpamplona.amethyst.service.NostrUserProfileFollowsDataSource
|
||||
import com.vitorpamplona.amethyst.service.relays.Client
|
||||
import com.vitorpamplona.amethyst.service.relays.RelayPool
|
||||
import com.vitorpamplona.amethyst.ui.actions.NewRelayListView
|
||||
import com.vitorpamplona.amethyst.ui.actions.NewUserMetadataView
|
||||
import com.vitorpamplona.amethyst.ui.screen.RelayPoolViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import kotlinx.coroutines.launch
|
||||
@ -72,49 +83,92 @@ fun MainTopBar(scaffoldState: ScaffoldState, accountViewModel: AccountViewModel)
|
||||
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
|
||||
var wantsToEditRelays by remember {
|
||||
mutableStateOf(false)
|
||||
}
|
||||
|
||||
if (wantsToEditRelays)
|
||||
NewRelayListView({ wantsToEditRelays = false }, account)
|
||||
|
||||
Column() {
|
||||
TopAppBar(
|
||||
elevation = 0.dp,
|
||||
backgroundColor = Color(0xFFFFFF),
|
||||
title = {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(start = 22.dp, end = 0.dp)
|
||||
.fillMaxWidth(),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
IconButton(
|
||||
onClick = {
|
||||
Client.allSubscriptions().map { "${it} ${Client.getSubscriptionFilters(it).joinToString { it.toJson() }}" }.forEach {
|
||||
Log.d("CURRENT FILTERS", it)
|
||||
Box() {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(start = 0.dp, end = 20.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
IconButton(
|
||||
onClick = {
|
||||
Client.allSubscriptions().map {
|
||||
"${it} ${
|
||||
Client.getSubscriptionFilters(it)
|
||||
.joinToString { it.toJson() }
|
||||
}"
|
||||
}.forEach {
|
||||
Log.d("CURRENT FILTERS", it)
|
||||
}
|
||||
|
||||
NostrAccountDataSource.printCounter()
|
||||
NostrChannelDataSource.printCounter()
|
||||
NostrChatRoomDataSource.printCounter()
|
||||
NostrChatroomListDataSource.printCounter()
|
||||
|
||||
NostrGlobalDataSource.printCounter()
|
||||
NostrHomeDataSource.printCounter()
|
||||
NostrNotificationDataSource.printCounter()
|
||||
|
||||
NostrSingleEventDataSource.printCounter()
|
||||
NostrSingleUserDataSource.printCounter()
|
||||
NostrThreadDataSource.printCounter()
|
||||
|
||||
NostrUserProfileDataSource.printCounter()
|
||||
NostrUserProfileFollowersDataSource.printCounter()
|
||||
NostrUserProfileFollowsDataSource.printCounter()
|
||||
|
||||
println("AAA: " + RelayPool.connectedRelays())
|
||||
}
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.amethyst),
|
||||
null,
|
||||
modifier = Modifier.size(40.dp),
|
||||
tint = Color.Unspecified
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.fillMaxHeight(),
|
||||
horizontalAlignment = Alignment.End,
|
||||
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxHeight(),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(
|
||||
"${connectedRelaysLiveData ?: "--"}/${availableRelaysLiveData ?: "--"}",
|
||||
color = if (connectedRelaysLiveData == 0) Color.Red else MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
|
||||
style = MaterialTheme.typography.subtitle1,
|
||||
modifier = Modifier.clickable(
|
||||
onClick = {
|
||||
wantsToEditRelays = true
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
NostrAccountDataSource.printCounter()
|
||||
NostrChannelDataSource.printCounter()
|
||||
NostrChatRoomDataSource.printCounter()
|
||||
NostrChatroomListDataSource.printCounter()
|
||||
|
||||
NostrGlobalDataSource.printCounter()
|
||||
NostrHomeDataSource.printCounter()
|
||||
NostrNotificationDataSource.printCounter()
|
||||
|
||||
NostrSingleEventDataSource.printCounter()
|
||||
NostrSingleUserDataSource.printCounter()
|
||||
NostrThreadDataSource.printCounter()
|
||||
|
||||
NostrUserProfileDataSource.printCounter()
|
||||
NostrUserProfileFollowersDataSource.printCounter()
|
||||
NostrUserProfileFollowsDataSource.printCounter()
|
||||
|
||||
println("AAA: " + RelayPool.connectedRelays())
|
||||
}
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.amethyst),
|
||||
null,
|
||||
modifier = Modifier.size(40.dp),
|
||||
tint = Color.Unspecified
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -131,17 +185,13 @@ fun MainTopBar(scaffoldState: ScaffoldState, accountViewModel: AccountViewModel)
|
||||
model = accountUser?.profilePicture() ?: "https://robohash.org/ohno.png",
|
||||
contentDescription = "Profile Image",
|
||||
modifier = Modifier
|
||||
.width(34.dp).height(34.dp)
|
||||
.width(34.dp)
|
||||
.height(34.dp)
|
||||
.clip(shape = CircleShape),
|
||||
)
|
||||
}
|
||||
},
|
||||
actions = {
|
||||
Text(
|
||||
"${connectedRelaysLiveData ?: "--"}/${availableRelaysLiveData ?: "--"}",
|
||||
color = if (connectedRelaysLiveData == 0) Color.Red else MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
)
|
||||
|
||||
IconButton(
|
||||
onClick = {}, modifier = Modifier
|
||||
) {
|
||||
|
@ -1,7 +1,9 @@
|
||||
package com.vitorpamplona.amethyst.ui.screen
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.security.crypto.EncryptedSharedPreferences
|
||||
import com.vitorpamplona.amethyst.ServiceManager
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.model.DefaultChannels
|
||||
import com.vitorpamplona.amethyst.model.toByteArray
|
||||
@ -13,16 +15,24 @@ import com.vitorpamplona.amethyst.service.NostrNotificationDataSource
|
||||
import com.vitorpamplona.amethyst.service.NostrSingleEventDataSource
|
||||
import com.vitorpamplona.amethyst.service.NostrSingleUserDataSource
|
||||
import com.vitorpamplona.amethyst.service.NostrThreadDataSource
|
||||
import com.vitorpamplona.amethyst.service.relays.Client
|
||||
import com.vitorpamplona.amethyst.ui.MainActivity
|
||||
import fr.acinq.secp256k1.Hex
|
||||
import java.util.regex.Pattern
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import nostr.postr.Persona
|
||||
import nostr.postr.bechToBytes
|
||||
import nostr.postr.toHex
|
||||
|
||||
class AccountStateViewModel(private val encryptedPreferences: EncryptedSharedPreferences): ViewModel() {
|
||||
class AccountStateViewModel(
|
||||
private val encryptedPreferences: EncryptedSharedPreferences
|
||||
): ViewModel() {
|
||||
private val _accountContent = MutableStateFlow<AccountState>(AccountState.LoggedOff)
|
||||
val accountContent = _accountContent.asStateFlow()
|
||||
|
||||
@ -65,19 +75,9 @@ class AccountStateViewModel(private val encryptedPreferences: EncryptedSharedPre
|
||||
else
|
||||
_accountContent.update { AccountState.LoggedInViewOnly ( account ) }
|
||||
|
||||
NostrAccountDataSource.account = account
|
||||
NostrHomeDataSource.account = account
|
||||
NostrNotificationDataSource.account = account
|
||||
NostrChatroomListDataSource.account = account
|
||||
|
||||
NostrAccountDataSource.start()
|
||||
NostrGlobalDataSource.start()
|
||||
NostrHomeDataSource.start()
|
||||
NostrNotificationDataSource.start()
|
||||
NostrSingleEventDataSource.start()
|
||||
NostrSingleUserDataSource.start()
|
||||
NostrThreadDataSource.start()
|
||||
NostrChatroomListDataSource.start()
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
ServiceManager.start(account)
|
||||
}
|
||||
}
|
||||
|
||||
fun logOff() {
|
||||
@ -90,6 +90,7 @@ class AccountStateViewModel(private val encryptedPreferences: EncryptedSharedPre
|
||||
encryptedPreferences.edit().apply {
|
||||
remove("nostr_privkey")
|
||||
remove("nostr_pubkey")
|
||||
remove("following_channels")
|
||||
}.apply()
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user