Avoids creating new corotine scopes

This commit is contained in:
Vitor Pamplona
2023-08-21 13:34:18 -04:00
parent 0e6a2c339e
commit ec3b07147c
18 changed files with 160 additions and 228 deletions
@@ -45,7 +45,7 @@ class CashuProcessor {
} }
} }
fun melt(token: CashuToken, lud16: String, onSuccess: (String) -> Unit, onError: (String) -> Unit) { suspend fun melt(token: CashuToken, lud16: String, onSuccess: (String) -> Unit, onError: (String) -> Unit) {
checkNotInMainThread() checkNotInMainThread()
runCatching { runCatching {
@@ -2,10 +2,7 @@ package com.vitorpamplona.amethyst.service
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.vitorpamplona.amethyst.BuildConfig import com.vitorpamplona.amethyst.BuildConfig
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import okhttp3.Call import okhttp3.Call
import okhttp3.Callback import okhttp3.Callback
@@ -26,55 +23,46 @@ class Nip05Verifier() {
return null return null
} }
fun fetchNip05Json(nip05address: String, onSuccess: (String) -> Unit, onError: (String) -> Unit) { suspend fun fetchNip05Json(nip05: String, onSuccess: (String) -> Unit, onError: (String) -> Unit) = withContext(Dispatchers.IO) {
val scope = CoroutineScope(Job() + Dispatchers.IO)
scope.launch {
fetchNip05JsonSuspend(nip05address, onSuccess, onError)
}
}
private suspend fun fetchNip05JsonSuspend(nip05: String, onSuccess: (String) -> Unit, onError: (String) -> Unit) {
checkNotInMainThread() checkNotInMainThread()
val url = assembleUrl(nip05) val url = assembleUrl(nip05)
if (url == null) { if (url == null) {
onError("Could not assemble url from Nip05: \"${nip05}\". Check the user's setup") onError("Could not assemble url from Nip05: \"${nip05}\". Check the user's setup")
return return@withContext
} }
withContext(Dispatchers.IO) { try {
try { val request = Request.Builder()
val request = Request.Builder() .header("User-Agent", "Amethyst/${BuildConfig.VERSION_NAME}")
.header("User-Agent", "Amethyst/${BuildConfig.VERSION_NAME}") .url(url)
.url(url) .build()
.build()
HttpClient.getHttpClient().newCall(request).enqueue(object : Callback { HttpClient.getHttpClient().newCall(request).enqueue(object : Callback {
override fun onResponse(call: Call, response: Response) { override fun onResponse(call: Call, response: Response) {
checkNotInMainThread() checkNotInMainThread()
response.use { response.use {
if (it.isSuccessful) { if (it.isSuccessful) {
onSuccess(it.body.string()) onSuccess(it.body.string())
} else { } else {
onError("Could not resolve $nip05. Error: ${it.code}. Check if the server up and if the address $nip05 is correct") onError("Could not resolve $nip05. Error: ${it.code}. Check if the server up and if the address $nip05 is correct")
}
} }
} }
}
override fun onFailure(call: Call, e: java.io.IOException) { override fun onFailure(call: Call, e: java.io.IOException) {
onError("Could not resolve $url. Check if the server up and if the address $nip05 is correct") onError("Could not resolve $url. Check if the server up and if the address $nip05 is correct")
e.printStackTrace() e.printStackTrace()
} }
}) })
} catch (e: java.lang.Exception) { } catch (e: java.lang.Exception) {
onError("Could not resolve '$url': ${e.message}") onError("Could not resolve '$url': ${e.message}")
}
} }
} }
fun verifyNip05(nip05: String, onSuccess: (String) -> Unit, onError: (String) -> Unit) { suspend fun verifyNip05(nip05: String, onSuccess: (String) -> Unit, onError: (String) -> Unit) {
// check fails on tests // check fails on tests
checkNotInMainThread() checkNotInMainThread()
@@ -7,10 +7,7 @@ import com.vitorpamplona.amethyst.service.checkNotInMainThread
import com.vitorpamplona.quartz.encoders.Bech32 import com.vitorpamplona.quartz.encoders.Bech32
import com.vitorpamplona.quartz.encoders.LnInvoiceUtil import com.vitorpamplona.quartz.encoders.LnInvoiceUtil
import com.vitorpamplona.quartz.encoders.toLnUrl import com.vitorpamplona.quartz.encoders.toLnUrl
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import okhttp3.Call import okhttp3.Call
import okhttp3.Callback import okhttp3.Callback
@@ -40,96 +37,70 @@ class LightningAddressResolver() {
return null return null
} }
fun fetchLightningAddressJson(lnaddress: String, onSuccess: (String) -> Unit, onError: (String) -> Unit) { private suspend fun fetchLightningAddressJson(lnaddress: String, onSuccess: suspend (String) -> Unit, onError: (String) -> Unit) = withContext(Dispatchers.IO) {
val scope = CoroutineScope(Job() + Dispatchers.IO)
scope.launch {
fetchLightningAddressJsonSuspend(lnaddress, onSuccess, onError)
}
}
private suspend fun fetchLightningAddressJsonSuspend(lnaddress: String, onSuccess: (String) -> Unit, onError: (String) -> Unit) {
checkNotInMainThread() checkNotInMainThread()
val url = assembleUrl(lnaddress) val url = assembleUrl(lnaddress)
if (url == null) { if (url == null) {
onError("Could not assemble LNUrl from Lightning Address \"${lnaddress}\". Check the user's setup") onError("Could not assemble LNUrl from Lightning Address \"${lnaddress}\". Check the user's setup")
return return@withContext
} }
try { try {
withContext(Dispatchers.IO) {
val request: Request = Request.Builder()
.header("User-Agent", "Amethyst/${BuildConfig.VERSION_NAME}")
.url(url)
.build()
client.newCall(request).enqueue(object : Callback {
override fun onResponse(call: Call, response: Response) {
response.use {
if (it.isSuccessful) {
onSuccess(it.body.string())
} else {
onError("Could not resolve $lnaddress. Error: ${it.code}. Check if the server up and if the lightning address $lnaddress is correct")
}
}
}
override fun onFailure(call: Call, e: java.io.IOException) {
onError("Could not resolve $url. Check if the server up and if the lightning address $lnaddress is correct")
e.printStackTrace()
}
})
}
} catch (e: Exception) {
onError("Could not resolve $url. Check if the server up and if the lightning address $lnaddress is correct")
}
}
fun fetchLightningInvoice(lnCallback: String, milliSats: Long, message: String, nostrRequest: String? = null, onSuccess: (String) -> Unit, onError: (String) -> Unit) {
val scope = CoroutineScope(Job() + Dispatchers.IO)
scope.launch {
fetchLightningInvoiceSuspend(lnCallback, milliSats, message, nostrRequest, onSuccess, onError)
}
}
private suspend fun fetchLightningInvoiceSuspend(lnCallback: String, milliSats: Long, message: String, nostrRequest: String? = null, onSuccess: (String) -> Unit, onError: (String) -> Unit) {
withContext(Dispatchers.IO) {
val encodedMessage = URLEncoder.encode(message, "utf-8")
val urlBinder = if (lnCallback.contains("?")) "&" else "?"
var url = "$lnCallback${urlBinder}amount=$milliSats&comment=$encodedMessage"
if (nostrRequest != null) {
val encodedNostrRequest = URLEncoder.encode(nostrRequest, "utf-8")
url += "&nostr=$encodedNostrRequest"
}
val request: Request = Request.Builder() val request: Request = Request.Builder()
.header("User-Agent", "Amethyst/${BuildConfig.VERSION_NAME}") .header("User-Agent", "Amethyst/${BuildConfig.VERSION_NAME}")
.url(url) .url(url)
.build() .build()
client.newCall(request).enqueue(object : Callback { client.newCall(request).execute().use {
override fun onResponse(call: Call, response: Response) { if (it.isSuccessful) {
response.use { onSuccess(it.body.string())
if (it.isSuccessful) { } else {
onSuccess(response.body.string()) onError("Could not resolve $lnaddress. Error: ${it.code}. Check if the server up and if the lightning address $lnaddress is correct")
} else {
onError("Could not fetch invoice from $lnCallback")
}
}
} }
}
override fun onFailure(call: Call, e: java.io.IOException) { } catch (e: Exception) {
onError("Could not fetch an invoice from $lnCallback. Message ${e.message}") e.printStackTrace()
e.printStackTrace() onError("Could not resolve $url. Check if the server up and if the lightning address $lnaddress is correct")
}
})
} }
} }
fun lnAddressToLnUrl(lnaddress: String, onSuccess: (String) -> Unit, onError: (String) -> Unit) { suspend fun fetchLightningInvoice(lnCallback: String, milliSats: Long, message: String, nostrRequest: String? = null, onSuccess: (String) -> Unit, onError: (String) -> Unit) = withContext(Dispatchers.IO) {
val encodedMessage = URLEncoder.encode(message, "utf-8")
val urlBinder = if (lnCallback.contains("?")) "&" else "?"
var url = "$lnCallback${urlBinder}amount=$milliSats&comment=$encodedMessage"
if (nostrRequest != null) {
val encodedNostrRequest = URLEncoder.encode(nostrRequest, "utf-8")
url += "&nostr=$encodedNostrRequest"
}
val request: Request = Request.Builder()
.header("User-Agent", "Amethyst/${BuildConfig.VERSION_NAME}")
.url(url)
.build()
client.newCall(request).enqueue(object : Callback {
override fun onResponse(call: Call, response: Response) {
response.use {
if (it.isSuccessful) {
onSuccess(response.body.string())
} else {
onError("Could not fetch invoice from $lnCallback")
}
}
}
override fun onFailure(call: Call, e: java.io.IOException) {
onError("Could not fetch an invoice from $lnCallback. Message ${e.message}")
e.printStackTrace()
}
})
}
suspend fun lnAddressToLnUrl(lnaddress: String, onSuccess: (String) -> Unit, onError: (String) -> Unit) {
fetchLightningAddressJson( fetchLightningAddressJson(
lnaddress, lnaddress,
onSuccess = { onSuccess = {
@@ -139,7 +110,15 @@ class LightningAddressResolver() {
) )
} }
fun lnAddressInvoice(lnaddress: String, milliSats: Long, message: String, nostrRequest: String? = null, onSuccess: (String) -> Unit, onError: (String) -> Unit, onProgress: (percent: Float) -> Unit) { suspend fun lnAddressInvoice(
lnaddress: String,
milliSats: Long,
message: String,
nostrRequest: String? = null,
onSuccess: (String) -> Unit,
onError: (String) -> Unit,
onProgress: (percent: Float) -> Unit
) {
val mapper = jacksonObjectMapper() val mapper = jacksonObjectMapper()
fetchLightningAddressJson( fetchLightningAddressJson(
@@ -20,26 +20,20 @@ import com.vitorpamplona.quartz.events.LnZapRequestEvent
import com.vitorpamplona.quartz.events.PrivateDmEvent import com.vitorpamplona.quartz.events.PrivateDmEvent
import com.vitorpamplona.quartz.events.SealedGossipEvent import com.vitorpamplona.quartz.events.SealedGossipEvent
import kotlinx.collections.immutable.persistentSetOf import kotlinx.collections.immutable.persistentSetOf
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
class EventNotificationConsumer(private val applicationContext: Context) { class EventNotificationConsumer(private val applicationContext: Context) {
fun consume(event: Event) {
val scope = CoroutineScope(Job() + Dispatchers.IO)
scope.launch {
if (LocalCache.notes[event.id] == null) {
if (LocalCache.justVerify(event)) {
LocalCache.justConsume(event, null)
val manager = notificationManager() fun consume(event: Event) {
if (manager.areNotificationsEnabled()) { if (LocalCache.notes[event.id] == null) {
when (event) { if (LocalCache.justVerify(event)) {
is PrivateDmEvent -> notify(event) LocalCache.justConsume(event, null)
is LnZapEvent -> notify(event)
is GiftWrapEvent -> unwrapAndNotify(event) val manager = notificationManager()
} if (manager.areNotificationsEnabled()) {
when (event) {
is PrivateDmEvent -> notify(event)
is LnZapEvent -> notify(event)
is GiftWrapEvent -> unwrapAndNotify(event)
} }
} }
} }
@@ -6,10 +6,8 @@ import com.vitorpamplona.amethyst.BuildConfig
import com.vitorpamplona.amethyst.LocalPreferences import com.vitorpamplona.amethyst.LocalPreferences
import com.vitorpamplona.amethyst.service.HttpClient import com.vitorpamplona.amethyst.service.HttpClient
import com.vitorpamplona.quartz.events.RelayAuthEvent import com.vitorpamplona.quartz.events.RelayAuthEvent
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.withContext
import kotlinx.coroutines.launch
import okhttp3.MediaType.Companion.toMediaType import okhttp3.MediaType.Companion.toMediaType
import okhttp3.Request import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.RequestBody.Companion.toRequestBody
@@ -64,12 +62,9 @@ class RegisterAccounts(
} }
} }
fun go(notificationToken: String) { suspend fun go(notificationToken: String) = withContext(Dispatchers.IO) {
val scope = CoroutineScope(Job() + Dispatchers.IO) postRegistrationEvent(
scope.launch { signEventsToProveControlOfAccounts(accounts, notificationToken)
postRegistrationEvent( )
signEventsToProveControlOfAccounts(accounts, notificationToken)
)
}
} }
} }
@@ -4,18 +4,11 @@ import androidx.lifecycle.LiveData
import com.vitorpamplona.amethyst.service.checkNotInMainThread import com.vitorpamplona.amethyst.service.checkNotInMainThread
import com.vitorpamplona.quartz.events.Event import com.vitorpamplona.quartz.events.Event
import com.vitorpamplona.quartz.events.EventInterface import com.vitorpamplona.quartz.events.EventInterface
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
/** /**
* RelayPool manages the connection to multiple Relays and lets consumers deal with simple events. * RelayPool manages the connection to multiple Relays and lets consumers deal with simple events.
*/ */
object RelayPool : Relay.Listener { object RelayPool : Relay.Listener {
val scope = CoroutineScope(Job() + Dispatchers.IO)
private var relays = listOf<Relay>() private var relays = listOf<Relay>()
private var listeners = setOf<Listener>() private var listeners = setOf<Listener>()
@@ -136,9 +129,7 @@ object RelayPool : Relay.Listener {
val live: RelayPoolLiveData = RelayPoolLiveData(this) val live: RelayPoolLiveData = RelayPoolLiveData(this)
private fun refreshObservers() { private fun refreshObservers() {
scope.launch { live.refresh()
live.refresh()
}
} }
} }
@@ -97,7 +97,9 @@ class MainActivity : AppCompatActivity() {
ServiceManager.start(this@MainActivity) ServiceManager.start(this@MainActivity)
} }
PushNotificationUtils().init(LocalPreferences.allSavedAccounts()) GlobalScope.launch(Dispatchers.IO) {
PushNotificationUtils().init(LocalPreferences.allSavedAccounts())
}
} }
override fun onPause() { override fun onPause() {
@@ -5,7 +5,6 @@ import com.vitorpamplona.amethyst.service.checkNotInMainThread
import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.NonCancellable import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel import kotlinx.coroutines.cancel
@@ -78,7 +77,6 @@ class BundledInsert<T>(
return return
} }
val scope = CoroutineScope(Job() + dispatcher)
scope.launch(Dispatchers.IO) { scope.launch(Dispatchers.IO) {
try { try {
val mySet = mutableSetOf<T>() val mySet = mutableSetOf<T>()
@@ -39,6 +39,7 @@ import com.vitorpamplona.amethyst.service.lnurl.LightningAddressResolver
import com.vitorpamplona.amethyst.ui.theme.QuoteBorder import com.vitorpamplona.amethyst.ui.theme.QuoteBorder
import com.vitorpamplona.amethyst.ui.theme.placeholderText import com.vitorpamplona.amethyst.ui.theme.placeholderText
import com.vitorpamplona.amethyst.ui.theme.subtleBorder import com.vitorpamplona.amethyst.ui.theme.subtleBorder
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@Composable @Composable
@@ -152,23 +153,25 @@ fun InvoiceRequest(
Button( Button(
modifier = Modifier.fillMaxWidth().padding(vertical = 10.dp), modifier = Modifier.fillMaxWidth().padding(vertical = 10.dp),
onClick = { onClick = {
val zapRequest = account.createZapRequestFor(toUserPubKeyHex, message, account.defaultZapType) scope.launch(Dispatchers.IO) {
val zapRequest = account.createZapRequestFor(toUserPubKeyHex, message, account.defaultZapType)
LightningAddressResolver().lnAddressInvoice( LightningAddressResolver().lnAddressInvoice(
lud16, lud16,
amount * 1000, amount * 1000,
message, message,
zapRequest?.toJson(), zapRequest?.toJson(),
onSuccess = onSuccess, onSuccess = onSuccess,
onError = { onError = {
scope.launch { scope.launch {
Toast.makeText(context, it, Toast.LENGTH_SHORT).show() Toast.makeText(context, it, Toast.LENGTH_SHORT).show()
onClose() onClose()
}
},
onProgress = {
} }
}, )
onProgress = { }
}
)
}, },
shape = QuoteBorder, shape = QuoteBorder,
colors = ButtonDefaults.buttonColors( colors = ButtonDefaults.buttonColors(
@@ -118,7 +118,6 @@ import com.vitorpamplona.quartz.events.PeopleListEvent
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toImmutableList
import kotlinx.collections.immutable.toPersistentList import kotlinx.collections.immutable.toPersistentList
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
@@ -536,8 +535,7 @@ class FollowListViewModel(val account: Account) : ViewModel() {
val followLists = _followLists.asStateFlow() val followLists = _followLists.asStateFlow()
fun refresh() { fun refresh() {
val scope = CoroutineScope(Job() + Dispatchers.Default) viewModelScope.launch(Dispatchers.Default) {
scope.launch {
refreshFollows() refreshFollows()
} }
} }
@@ -13,11 +13,9 @@ import com.vitorpamplona.quartz.encoders.Hex
import com.vitorpamplona.quartz.encoders.Nip19 import com.vitorpamplona.quartz.encoders.Nip19
import com.vitorpamplona.quartz.encoders.bechToBytes import com.vitorpamplona.quartz.encoders.bechToBytes
import com.vitorpamplona.quartz.encoders.hexToByteArray import com.vitorpamplona.quartz.encoders.hexToByteArray
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
@@ -89,8 +87,7 @@ class AccountStateViewModel(val context: Context) : ViewModel() {
} else { } else {
_accountContent.update { AccountState.LoggedInViewOnly(account) } _accountContent.update { AccountState.LoggedInViewOnly(account) }
} }
val scope = CoroutineScope(Job() + Dispatchers.IO) GlobalScope.launch(Dispatchers.IO) {
scope.launch {
ServiceManager.start(account, context) ServiceManager.start(account, context)
} }
GlobalScope.launch(Dispatchers.Main) { GlobalScope.launch(Dispatchers.Main) {
@@ -28,7 +28,6 @@ import com.vitorpamplona.quartz.events.ReactionEvent
import com.vitorpamplona.quartz.events.RepostEvent import com.vitorpamplona.quartz.events.RepostEvent
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
@@ -76,8 +75,7 @@ open class CardFeedViewModel(val localFilter: FeedFilter<Note>) : ViewModel() {
private var lastNotes: Set<Note>? = null private var lastNotes: Set<Note>? = null
fun refresh() { fun refresh() {
val scope = CoroutineScope(Job() + Dispatchers.Default) viewModelScope.launch(Dispatchers.Default) {
scope.launch {
refreshSuspended() refreshSuspended()
} }
} }
@@ -217,8 +215,7 @@ open class CardFeedViewModel(val localFilter: FeedFilter<Note>) : ViewModel() {
} }
private fun updateFeed(notes: ImmutableList<Card>) { private fun updateFeed(notes: ImmutableList<Card>) {
val scope = CoroutineScope(Job() + Dispatchers.Main) viewModelScope.launch(Dispatchers.Main) {
scope.launch {
val currentState = _feedContent.value val currentState = _feedContent.value
if (notes.isEmpty()) { if (notes.isEmpty()) {
@@ -41,7 +41,6 @@ import com.vitorpamplona.amethyst.ui.dal.VideoFeedFilter
import com.vitorpamplona.quartz.events.ChatroomKey import com.vitorpamplona.quartz.events.ChatroomKey
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
@@ -228,8 +227,7 @@ abstract class FeedViewModel(val localFilter: FeedFilter<Note>) : ViewModel(), I
} }
private fun refresh() { private fun refresh() {
val scope = CoroutineScope(Job() + Dispatchers.Default) viewModelScope.launch(Dispatchers.Default) {
scope.launch {
refreshSuspended() refreshSuspended()
} }
} }
@@ -251,8 +249,7 @@ abstract class FeedViewModel(val localFilter: FeedFilter<Note>) : ViewModel(), I
} }
private fun updateFeed(notes: ImmutableList<Note>) { private fun updateFeed(notes: ImmutableList<Note>) {
val scope = CoroutineScope(Job() + Dispatchers.Main) viewModelScope.launch(Dispatchers.Main) {
scope.launch {
val currentState = _feedContent.value val currentState = _feedContent.value
if (notes.isEmpty()) { if (notes.isEmpty()) {
_feedContent.update { FeedState.Empty } _feedContent.update { FeedState.Empty }
@@ -14,7 +14,6 @@ import com.vitorpamplona.amethyst.ui.dal.FeedFilter
import com.vitorpamplona.amethyst.ui.dal.UserProfileZapsFeedFilter import com.vitorpamplona.amethyst.ui.dal.UserProfileZapsFeedFilter
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
@@ -36,8 +35,7 @@ open class LnZapFeedViewModel(val dataSource: FeedFilter<ZapReqResponse>) : View
val feedContent = _feedContent.asStateFlow() val feedContent = _feedContent.asStateFlow()
private fun refresh() { private fun refresh() {
val scope = CoroutineScope(Job() + Dispatchers.Default) viewModelScope.launch(Dispatchers.Default) {
scope.launch {
refreshSuspended() refreshSuspended()
} }
} }
@@ -58,8 +56,7 @@ open class LnZapFeedViewModel(val dataSource: FeedFilter<ZapReqResponse>) : View
} }
private fun updateFeed(notes: ImmutableList<ZapReqResponse>) { private fun updateFeed(notes: ImmutableList<ZapReqResponse>) {
val scope = CoroutineScope(Job() + Dispatchers.Main) viewModelScope.launch(Dispatchers.Main) {
scope.launch {
val currentState = _feedContent.value val currentState = _feedContent.value
if (notes.isEmpty()) { if (notes.isEmpty()) {
_feedContent.update { LnZapFeedState.Empty } _feedContent.update { LnZapFeedState.Empty }
@@ -18,7 +18,6 @@ import com.vitorpamplona.amethyst.ui.dal.UserProfileFollowersFeedFilter
import com.vitorpamplona.amethyst.ui.dal.UserProfileFollowsFeedFilter import com.vitorpamplona.amethyst.ui.dal.UserProfileFollowsFeedFilter
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
@@ -64,8 +63,7 @@ open class UserFeedViewModel(val dataSource: FeedFilter<User>) : ViewModel(), In
val feedContent = _feedContent.asStateFlow() val feedContent = _feedContent.asStateFlow()
private fun refresh() { private fun refresh() {
val scope = CoroutineScope(Job() + Dispatchers.Default) viewModelScope.launch(Dispatchers.Default) {
scope.launch {
refreshSuspended() refreshSuspended()
} }
} }
@@ -87,8 +85,7 @@ open class UserFeedViewModel(val dataSource: FeedFilter<User>) : ViewModel(), In
} }
private fun updateFeed(notes: ImmutableList<User>) { private fun updateFeed(notes: ImmutableList<User>) {
val scope = CoroutineScope(Job() + Dispatchers.Main) viewModelScope.launch(Dispatchers.Main) {
scope.launch {
val currentState = _feedContent.value val currentState = _feedContent.value
if (notes.isEmpty()) { if (notes.isEmpty()) {
_feedContent.update { UserFeedState.Empty } _feedContent.update { UserFeedState.Empty }
@@ -8,22 +8,37 @@ import com.vitorpamplona.amethyst.LocalPreferences
import com.vitorpamplona.amethyst.service.notifications.NotificationUtils.getOrCreateDMChannel import com.vitorpamplona.amethyst.service.notifications.NotificationUtils.getOrCreateDMChannel
import com.vitorpamplona.amethyst.service.notifications.NotificationUtils.getOrCreateZapChannel import com.vitorpamplona.amethyst.service.notifications.NotificationUtils.getOrCreateZapChannel
import com.vitorpamplona.quartz.events.Event import com.vitorpamplona.quartz.events.Event
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
class PushNotificationReceiverService : FirebaseMessagingService() { class PushNotificationReceiverService : FirebaseMessagingService() {
val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
// this is called when a message is received // this is called when a message is received
override fun onMessageReceived(remoteMessage: RemoteMessage) { override fun onMessageReceived(remoteMessage: RemoteMessage) {
remoteMessage.data.let { scope.launch(Dispatchers.IO) {
val eventStr = remoteMessage.data["event"] ?: return remoteMessage.data.let {
val event = Event.fromJson(eventStr) val eventStr = remoteMessage.data["event"] ?: return@let
EventNotificationConsumer(applicationContext).consume(event) val event = Event.fromJson(eventStr)
EventNotificationConsumer(applicationContext).consume(event)
}
} }
} }
override fun onDestroy() {
scope.cancel()
super.onDestroy()
}
override fun onNewToken(token: String) { override fun onNewToken(token: String) {
RegisterAccounts(LocalPreferences.allSavedAccounts()).go(token) scope.launch(Dispatchers.IO) {
notificationManager().getOrCreateZapChannel(applicationContext) RegisterAccounts(LocalPreferences.allSavedAccounts()).go(token)
notificationManager().getOrCreateDMChannel(applicationContext) notificationManager().getOrCreateZapChannel(applicationContext)
notificationManager().getOrCreateDMChannel(applicationContext)
}
} }
fun notificationManager(): NotificationManager { fun notificationManager(): NotificationManager {
@@ -1,31 +1,13 @@
package com.vitorpamplona.amethyst.service.notifications package com.vitorpamplona.amethyst.service.notifications
import android.util.Log
import com.google.android.gms.tasks.OnCompleteListener
import com.google.firebase.messaging.FirebaseMessaging import com.google.firebase.messaging.FirebaseMessaging
import com.vitorpamplona.amethyst.AccountInfo import com.vitorpamplona.amethyst.AccountInfo
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.tasks.await
import kotlinx.coroutines.launch
class PushNotificationUtils { class PushNotificationUtils {
fun init(accounts: List<AccountInfo>) { suspend fun init(accounts: List<AccountInfo>) = with(Dispatchers.IO) {
val scope = CoroutineScope(Job() + Dispatchers.IO) // get user notification token provided by firebase
scope.launch { RegisterAccounts(accounts).go(FirebaseMessaging.getInstance().token.await())
// get user notification token provided by firebase
FirebaseMessaging.getInstance().token.addOnCompleteListener(
OnCompleteListener { task ->
if (!task.isSuccessful) {
Log.w("FirebaseMsgService", "Fetching FCM registration token failed", task.exception)
return@OnCompleteListener
}
// Get new FCM registration token
val notificationToken = task.result
RegisterAccounts(accounts).go(notificationToken)
}
)
}
} }
} }
@@ -2,11 +2,13 @@ package com.vitorpamplona.amethyst.service
import android.os.Looper import android.os.Looper
import io.mockk.MockKAnnotations import io.mockk.MockKAnnotations
import io.mockk.coEvery
import io.mockk.every import io.mockk.every
import io.mockk.impl.annotations.SpyK import io.mockk.impl.annotations.SpyK
import io.mockk.mockk import io.mockk.mockk
import io.mockk.mockkStatic import io.mockk.mockkStatic
import io.mockk.unmockkAll import io.mockk.unmockkAll
import kotlinx.coroutines.runBlocking
import org.junit.After import org.junit.After
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
import org.junit.Assert.assertNull import org.junit.Assert.assertNull
@@ -30,7 +32,7 @@ class Nip05VerifierTest {
} }
@Test @Test
fun `test with matching case on user name`() { fun `test with matching case on user name`() = runBlocking {
// Set-up // Set-up
val userNameToTest = ALL_UPPER_CASE_USER_NAME val userNameToTest = ALL_UPPER_CASE_USER_NAME
val expectedPubKey = "ca29c211f1c72d5b6622268ff43d2288ea2b2cb5b9aa196ff9f1704fc914b71b" val expectedPubKey = "ca29c211f1c72d5b6622268ff43d2288ea2b2cb5b9aa196ff9f1704fc914b71b"
@@ -41,7 +43,7 @@ class Nip05VerifierTest {
" }\n" + " }\n" +
"}" "}"
every { nip05Verifier.fetchNip05Json(any(), any(), any()) } answers { coEvery { nip05Verifier.fetchNip05Json(any(), any(), any()) } answers {
secondArg<(String) -> Unit>().invoke(nostrJson) secondArg<(String) -> Unit>().invoke(nostrJson)
} }
@@ -64,7 +66,7 @@ class Nip05VerifierTest {
} }
@Test @Test
fun `test with NOT matching case on user name`() { fun `test with NOT matching case on user name`() = runBlocking {
// Set-up // Set-up
val expectedPubKey = "ca29c211f1c72d5b6622268ff43d2288ea2b2cb5b9aa196ff9f1704fc914b71b" val expectedPubKey = "ca29c211f1c72d5b6622268ff43d2288ea2b2cb5b9aa196ff9f1704fc914b71b"
@@ -73,7 +75,7 @@ class Nip05VerifierTest {
" \"$ALL_UPPER_CASE_USER_NAME\": \"$expectedPubKey\" \n" + " \"$ALL_UPPER_CASE_USER_NAME\": \"$expectedPubKey\" \n" +
" }\n" + " }\n" +
"}" "}"
every { nip05Verifier.fetchNip05Json(any(), any(), any()) } answers { coEvery { nip05Verifier.fetchNip05Json(any(), any(), any()) } answers {
secondArg<(String) -> Unit>().invoke(nostrJson) secondArg<(String) -> Unit>().invoke(nostrJson)
} }