diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/CashuProcessor.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/CashuProcessor.kt index 39fc58ca4..0773e70a8 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/CashuProcessor.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/CashuProcessor.kt @@ -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() runCatching { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/Nip05Verifier.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/Nip05Verifier.kt index 37147ba2c..4dd37d4b6 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/Nip05Verifier.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/Nip05Verifier.kt @@ -2,10 +2,7 @@ package com.vitorpamplona.amethyst.service import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import com.vitorpamplona.amethyst.BuildConfig -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job -import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import okhttp3.Call import okhttp3.Callback @@ -26,55 +23,46 @@ class Nip05Verifier() { return null } - fun fetchNip05Json(nip05address: String, onSuccess: (String) -> Unit, onError: (String) -> Unit) { - val scope = CoroutineScope(Job() + Dispatchers.IO) - scope.launch { - fetchNip05JsonSuspend(nip05address, onSuccess, onError) - } - } - - private suspend fun fetchNip05JsonSuspend(nip05: String, onSuccess: (String) -> Unit, onError: (String) -> Unit) { + suspend fun fetchNip05Json(nip05: String, onSuccess: (String) -> Unit, onError: (String) -> Unit) = withContext(Dispatchers.IO) { checkNotInMainThread() val url = assembleUrl(nip05) if (url == null) { onError("Could not assemble url from Nip05: \"${nip05}\". Check the user's setup") - return + return@withContext } - withContext(Dispatchers.IO) { - try { - val request = Request.Builder() - .header("User-Agent", "Amethyst/${BuildConfig.VERSION_NAME}") - .url(url) - .build() + try { + val request = Request.Builder() + .header("User-Agent", "Amethyst/${BuildConfig.VERSION_NAME}") + .url(url) + .build() - HttpClient.getHttpClient().newCall(request).enqueue(object : Callback { - override fun onResponse(call: Call, response: Response) { - checkNotInMainThread() + HttpClient.getHttpClient().newCall(request).enqueue(object : Callback { + override fun onResponse(call: Call, response: Response) { + checkNotInMainThread() - response.use { - if (it.isSuccessful) { - onSuccess(it.body.string()) - } else { - onError("Could not resolve $nip05. Error: ${it.code}. Check if the server up and if the address $nip05 is correct") - } + response.use { + if (it.isSuccessful) { + onSuccess(it.body.string()) + } else { + 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) { - onError("Could not resolve $url. Check if the server up and if the address $nip05 is correct") - e.printStackTrace() - } - }) - } catch (e: java.lang.Exception) { - onError("Could not resolve '$url': ${e.message}") - } + 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") + e.printStackTrace() + } + }) + } catch (e: java.lang.Exception) { + 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 checkNotInMainThread() diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/lnurl/LightningAddressResolver.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/lnurl/LightningAddressResolver.kt index 01e372fa3..61e8d3d17 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/lnurl/LightningAddressResolver.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/lnurl/LightningAddressResolver.kt @@ -7,10 +7,7 @@ import com.vitorpamplona.amethyst.service.checkNotInMainThread import com.vitorpamplona.quartz.encoders.Bech32 import com.vitorpamplona.quartz.encoders.LnInvoiceUtil import com.vitorpamplona.quartz.encoders.toLnUrl -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job -import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import okhttp3.Call import okhttp3.Callback @@ -40,96 +37,70 @@ class LightningAddressResolver() { return null } - fun fetchLightningAddressJson(lnaddress: String, onSuccess: (String) -> Unit, onError: (String) -> Unit) { - val scope = CoroutineScope(Job() + Dispatchers.IO) - scope.launch { - fetchLightningAddressJsonSuspend(lnaddress, onSuccess, onError) - } - } - - private suspend fun fetchLightningAddressJsonSuspend(lnaddress: String, onSuccess: (String) -> Unit, onError: (String) -> Unit) { + private suspend fun fetchLightningAddressJson(lnaddress: String, onSuccess: suspend (String) -> Unit, onError: (String) -> Unit) = withContext(Dispatchers.IO) { checkNotInMainThread() val url = assembleUrl(lnaddress) if (url == null) { onError("Could not assemble LNUrl from Lightning Address \"${lnaddress}\". Check the user's setup") - return + return@withContext } 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() .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") - } - } + client.newCall(request).execute().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 fetch an invoice from $lnCallback. Message ${e.message}") - e.printStackTrace() - } - }) + } + } catch (e: Exception) { + 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( lnaddress, 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() fetchLightningAddressJson( diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/notifications/EventNotificationConsumer.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/notifications/EventNotificationConsumer.kt index c154778a4..d73c22b10 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/notifications/EventNotificationConsumer.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/notifications/EventNotificationConsumer.kt @@ -20,26 +20,20 @@ import com.vitorpamplona.quartz.events.LnZapRequestEvent import com.vitorpamplona.quartz.events.PrivateDmEvent import com.vitorpamplona.quartz.events.SealedGossipEvent 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) { - 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() - if (manager.areNotificationsEnabled()) { - when (event) { - is PrivateDmEvent -> notify(event) - is LnZapEvent -> notify(event) - is GiftWrapEvent -> unwrapAndNotify(event) - } + fun consume(event: Event) { + if (LocalCache.notes[event.id] == null) { + if (LocalCache.justVerify(event)) { + LocalCache.justConsume(event, null) + + val manager = notificationManager() + if (manager.areNotificationsEnabled()) { + when (event) { + is PrivateDmEvent -> notify(event) + is LnZapEvent -> notify(event) + is GiftWrapEvent -> unwrapAndNotify(event) } } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/notifications/RegisterAccounts.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/notifications/RegisterAccounts.kt index 3fb7f7c3f..624d57647 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/notifications/RegisterAccounts.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/notifications/RegisterAccounts.kt @@ -6,10 +6,8 @@ import com.vitorpamplona.amethyst.BuildConfig import com.vitorpamplona.amethyst.LocalPreferences import com.vitorpamplona.amethyst.service.HttpClient import com.vitorpamplona.quartz.events.RelayAuthEvent -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job -import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import okhttp3.MediaType.Companion.toMediaType import okhttp3.Request import okhttp3.RequestBody.Companion.toRequestBody @@ -64,12 +62,9 @@ class RegisterAccounts( } } - fun go(notificationToken: String) { - val scope = CoroutineScope(Job() + Dispatchers.IO) - scope.launch { - postRegistrationEvent( - signEventsToProveControlOfAccounts(accounts, notificationToken) - ) - } + suspend fun go(notificationToken: String) = withContext(Dispatchers.IO) { + postRegistrationEvent( + signEventsToProveControlOfAccounts(accounts, notificationToken) + ) } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/relays/RelayPool.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/relays/RelayPool.kt index 38d2c5868..3233307e9 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/relays/RelayPool.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/relays/RelayPool.kt @@ -4,18 +4,11 @@ import androidx.lifecycle.LiveData import com.vitorpamplona.amethyst.service.checkNotInMainThread import com.vitorpamplona.quartz.events.Event 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. */ object RelayPool : Relay.Listener { - - val scope = CoroutineScope(Job() + Dispatchers.IO) - private var relays = listOf() private var listeners = setOf() @@ -136,9 +129,7 @@ object RelayPool : Relay.Listener { val live: RelayPoolLiveData = RelayPoolLiveData(this) private fun refreshObservers() { - scope.launch { - live.refresh() - } + live.refresh() } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/MainActivity.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/MainActivity.kt index ef452f774..bb82a3357 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/MainActivity.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/MainActivity.kt @@ -97,7 +97,9 @@ class MainActivity : AppCompatActivity() { ServiceManager.start(this@MainActivity) } - PushNotificationUtils().init(LocalPreferences.allSavedAccounts()) + GlobalScope.launch(Dispatchers.IO) { + PushNotificationUtils().init(LocalPreferences.allSavedAccounts()) + } } override fun onPause() { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/BundledUpdate.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/BundledUpdate.kt index f5e702150..d9b5c2feb 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/BundledUpdate.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/BundledUpdate.kt @@ -5,7 +5,6 @@ import com.vitorpamplona.amethyst.service.checkNotInMainThread import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job import kotlinx.coroutines.NonCancellable import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.cancel @@ -78,7 +77,6 @@ class BundledInsert( return } - val scope = CoroutineScope(Job() + dispatcher) scope.launch(Dispatchers.IO) { try { val mySet = mutableSetOf() diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/InvoiceRequest.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/InvoiceRequest.kt index d83a8b9db..97f400502 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/InvoiceRequest.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/InvoiceRequest.kt @@ -39,6 +39,7 @@ import com.vitorpamplona.amethyst.service.lnurl.LightningAddressResolver import com.vitorpamplona.amethyst.ui.theme.QuoteBorder import com.vitorpamplona.amethyst.ui.theme.placeholderText import com.vitorpamplona.amethyst.ui.theme.subtleBorder +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @Composable @@ -152,23 +153,25 @@ fun InvoiceRequest( Button( modifier = Modifier.fillMaxWidth().padding(vertical = 10.dp), onClick = { - val zapRequest = account.createZapRequestFor(toUserPubKeyHex, message, account.defaultZapType) + scope.launch(Dispatchers.IO) { + val zapRequest = account.createZapRequestFor(toUserPubKeyHex, message, account.defaultZapType) - LightningAddressResolver().lnAddressInvoice( - lud16, - amount * 1000, - message, - zapRequest?.toJson(), - onSuccess = onSuccess, - onError = { - scope.launch { - Toast.makeText(context, it, Toast.LENGTH_SHORT).show() - onClose() + LightningAddressResolver().lnAddressInvoice( + lud16, + amount * 1000, + message, + zapRequest?.toJson(), + onSuccess = onSuccess, + onError = { + scope.launch { + Toast.makeText(context, it, Toast.LENGTH_SHORT).show() + onClose() + } + }, + onProgress = { } - }, - onProgress = { - } - ) + ) + } }, shape = QuoteBorder, colors = ButtonDefaults.buttonColors( diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppTopBar.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppTopBar.kt index 816a63d46..89c4e19eb 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppTopBar.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppTopBar.kt @@ -118,7 +118,6 @@ import com.vitorpamplona.quartz.events.PeopleListEvent import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toPersistentList -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.flow.MutableStateFlow @@ -536,8 +535,7 @@ class FollowListViewModel(val account: Account) : ViewModel() { val followLists = _followLists.asStateFlow() fun refresh() { - val scope = CoroutineScope(Job() + Dispatchers.Default) - scope.launch { + viewModelScope.launch(Dispatchers.Default) { refreshFollows() } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/AccountStateViewModel.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/AccountStateViewModel.kt index 1b3d963df..7dbdccf30 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/AccountStateViewModel.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/AccountStateViewModel.kt @@ -13,11 +13,9 @@ import com.vitorpamplona.quartz.encoders.Hex import com.vitorpamplona.quartz.encoders.Nip19 import com.vitorpamplona.quartz.encoders.bechToBytes import com.vitorpamplona.quartz.encoders.hexToByteArray -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.Job import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update @@ -89,8 +87,7 @@ class AccountStateViewModel(val context: Context) : ViewModel() { } else { _accountContent.update { AccountState.LoggedInViewOnly(account) } } - val scope = CoroutineScope(Job() + Dispatchers.IO) - scope.launch { + GlobalScope.launch(Dispatchers.IO) { ServiceManager.start(account, context) } GlobalScope.launch(Dispatchers.Main) { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedViewModel.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedViewModel.kt index 1133fc121..04d8a249c 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedViewModel.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedViewModel.kt @@ -28,7 +28,6 @@ import com.vitorpamplona.quartz.events.ReactionEvent import com.vitorpamplona.quartz.events.RepostEvent import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.flow.MutableStateFlow @@ -76,8 +75,7 @@ open class CardFeedViewModel(val localFilter: FeedFilter) : ViewModel() { private var lastNotes: Set? = null fun refresh() { - val scope = CoroutineScope(Job() + Dispatchers.Default) - scope.launch { + viewModelScope.launch(Dispatchers.Default) { refreshSuspended() } } @@ -217,8 +215,7 @@ open class CardFeedViewModel(val localFilter: FeedFilter) : ViewModel() { } private fun updateFeed(notes: ImmutableList) { - val scope = CoroutineScope(Job() + Dispatchers.Main) - scope.launch { + viewModelScope.launch(Dispatchers.Main) { val currentState = _feedContent.value if (notes.isEmpty()) { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedViewModel.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedViewModel.kt index 4a02d8f93..fd1d6aafa 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedViewModel.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedViewModel.kt @@ -41,7 +41,6 @@ import com.vitorpamplona.amethyst.ui.dal.VideoFeedFilter import com.vitorpamplona.quartz.events.ChatroomKey import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.flow.MutableStateFlow @@ -228,8 +227,7 @@ abstract class FeedViewModel(val localFilter: FeedFilter) : ViewModel(), I } private fun refresh() { - val scope = CoroutineScope(Job() + Dispatchers.Default) - scope.launch { + viewModelScope.launch(Dispatchers.Default) { refreshSuspended() } } @@ -251,8 +249,7 @@ abstract class FeedViewModel(val localFilter: FeedFilter) : ViewModel(), I } private fun updateFeed(notes: ImmutableList) { - val scope = CoroutineScope(Job() + Dispatchers.Main) - scope.launch { + viewModelScope.launch(Dispatchers.Main) { val currentState = _feedContent.value if (notes.isEmpty()) { _feedContent.update { FeedState.Empty } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/LnZapFeedViewModel.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/LnZapFeedViewModel.kt index b9494a78c..e3a5af241 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/LnZapFeedViewModel.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/LnZapFeedViewModel.kt @@ -14,7 +14,6 @@ import com.vitorpamplona.amethyst.ui.dal.FeedFilter import com.vitorpamplona.amethyst.ui.dal.UserProfileZapsFeedFilter import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.flow.MutableStateFlow @@ -36,8 +35,7 @@ open class LnZapFeedViewModel(val dataSource: FeedFilter) : View val feedContent = _feedContent.asStateFlow() private fun refresh() { - val scope = CoroutineScope(Job() + Dispatchers.Default) - scope.launch { + viewModelScope.launch(Dispatchers.Default) { refreshSuspended() } } @@ -58,8 +56,7 @@ open class LnZapFeedViewModel(val dataSource: FeedFilter) : View } private fun updateFeed(notes: ImmutableList) { - val scope = CoroutineScope(Job() + Dispatchers.Main) - scope.launch { + viewModelScope.launch(Dispatchers.Main) { val currentState = _feedContent.value if (notes.isEmpty()) { _feedContent.update { LnZapFeedState.Empty } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/UserFeedViewModel.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/UserFeedViewModel.kt index d2ca45832..18aaacb0b 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/UserFeedViewModel.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/UserFeedViewModel.kt @@ -18,7 +18,6 @@ import com.vitorpamplona.amethyst.ui.dal.UserProfileFollowersFeedFilter import com.vitorpamplona.amethyst.ui.dal.UserProfileFollowsFeedFilter import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.flow.MutableStateFlow @@ -64,8 +63,7 @@ open class UserFeedViewModel(val dataSource: FeedFilter) : ViewModel(), In val feedContent = _feedContent.asStateFlow() private fun refresh() { - val scope = CoroutineScope(Job() + Dispatchers.Default) - scope.launch { + viewModelScope.launch(Dispatchers.Default) { refreshSuspended() } } @@ -87,8 +85,7 @@ open class UserFeedViewModel(val dataSource: FeedFilter) : ViewModel(), In } private fun updateFeed(notes: ImmutableList) { - val scope = CoroutineScope(Job() + Dispatchers.Main) - scope.launch { + viewModelScope.launch(Dispatchers.Main) { val currentState = _feedContent.value if (notes.isEmpty()) { _feedContent.update { UserFeedState.Empty } diff --git a/app/src/play/java/com/vitorpamplona/amethyst/service/notifications/PushNotificationReceiverService.kt b/app/src/play/java/com/vitorpamplona/amethyst/service/notifications/PushNotificationReceiverService.kt index 60cb97d47..f262d246b 100644 --- a/app/src/play/java/com/vitorpamplona/amethyst/service/notifications/PushNotificationReceiverService.kt +++ b/app/src/play/java/com/vitorpamplona/amethyst/service/notifications/PushNotificationReceiverService.kt @@ -8,22 +8,37 @@ import com.vitorpamplona.amethyst.LocalPreferences import com.vitorpamplona.amethyst.service.notifications.NotificationUtils.getOrCreateDMChannel import com.vitorpamplona.amethyst.service.notifications.NotificationUtils.getOrCreateZapChannel 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() { + val scope = CoroutineScope(Dispatchers.IO + SupervisorJob()) // this is called when a message is received override fun onMessageReceived(remoteMessage: RemoteMessage) { - remoteMessage.data.let { - val eventStr = remoteMessage.data["event"] ?: return - val event = Event.fromJson(eventStr) - EventNotificationConsumer(applicationContext).consume(event) + scope.launch(Dispatchers.IO) { + remoteMessage.data.let { + val eventStr = remoteMessage.data["event"] ?: return@let + val event = Event.fromJson(eventStr) + EventNotificationConsumer(applicationContext).consume(event) + } } } + override fun onDestroy() { + scope.cancel() + super.onDestroy() + } + override fun onNewToken(token: String) { - RegisterAccounts(LocalPreferences.allSavedAccounts()).go(token) - notificationManager().getOrCreateZapChannel(applicationContext) - notificationManager().getOrCreateDMChannel(applicationContext) + scope.launch(Dispatchers.IO) { + RegisterAccounts(LocalPreferences.allSavedAccounts()).go(token) + notificationManager().getOrCreateZapChannel(applicationContext) + notificationManager().getOrCreateDMChannel(applicationContext) + } } fun notificationManager(): NotificationManager { diff --git a/app/src/play/java/com/vitorpamplona/amethyst/service/notifications/PushNotificationUtils.kt b/app/src/play/java/com/vitorpamplona/amethyst/service/notifications/PushNotificationUtils.kt index 9fc979cb9..cafb5c564 100644 --- a/app/src/play/java/com/vitorpamplona/amethyst/service/notifications/PushNotificationUtils.kt +++ b/app/src/play/java/com/vitorpamplona/amethyst/service/notifications/PushNotificationUtils.kt @@ -1,31 +1,13 @@ 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.vitorpamplona.amethyst.AccountInfo -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job -import kotlinx.coroutines.launch +import kotlinx.coroutines.tasks.await class PushNotificationUtils { - fun init(accounts: List) { - val scope = CoroutineScope(Job() + Dispatchers.IO) - scope.launch { - // 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) - } - ) - } + suspend fun init(accounts: List) = with(Dispatchers.IO) { + // get user notification token provided by firebase + RegisterAccounts(accounts).go(FirebaseMessaging.getInstance().token.await()) } } diff --git a/app/src/test/java/com/vitorpamplona/amethyst/service/Nip05VerifierTest.kt b/app/src/test/java/com/vitorpamplona/amethyst/service/Nip05VerifierTest.kt index 83a3de19f..e31232616 100644 --- a/app/src/test/java/com/vitorpamplona/amethyst/service/Nip05VerifierTest.kt +++ b/app/src/test/java/com/vitorpamplona/amethyst/service/Nip05VerifierTest.kt @@ -2,11 +2,13 @@ package com.vitorpamplona.amethyst.service import android.os.Looper import io.mockk.MockKAnnotations +import io.mockk.coEvery import io.mockk.every import io.mockk.impl.annotations.SpyK import io.mockk.mockk import io.mockk.mockkStatic import io.mockk.unmockkAll +import kotlinx.coroutines.runBlocking import org.junit.After import org.junit.Assert.assertEquals import org.junit.Assert.assertNull @@ -30,7 +32,7 @@ class Nip05VerifierTest { } @Test - fun `test with matching case on user name`() { + fun `test with matching case on user name`() = runBlocking { // Set-up val userNameToTest = ALL_UPPER_CASE_USER_NAME val expectedPubKey = "ca29c211f1c72d5b6622268ff43d2288ea2b2cb5b9aa196ff9f1704fc914b71b" @@ -41,7 +43,7 @@ class Nip05VerifierTest { " }\n" + "}" - every { nip05Verifier.fetchNip05Json(any(), any(), any()) } answers { + coEvery { nip05Verifier.fetchNip05Json(any(), any(), any()) } answers { secondArg<(String) -> Unit>().invoke(nostrJson) } @@ -64,7 +66,7 @@ class Nip05VerifierTest { } @Test - fun `test with NOT matching case on user name`() { + fun `test with NOT matching case on user name`() = runBlocking { // Set-up val expectedPubKey = "ca29c211f1c72d5b6622268ff43d2288ea2b2cb5b9aa196ff9f1704fc914b71b" @@ -73,7 +75,7 @@ class Nip05VerifierTest { " \"$ALL_UPPER_CASE_USER_NAME\": \"$expectedPubKey\" \n" + " }\n" + "}" - every { nip05Verifier.fetchNip05Json(any(), any(), any()) } answers { + coEvery { nip05Verifier.fetchNip05Json(any(), any(), any()) } answers { secondArg<(String) -> Unit>().invoke(nostrJson) }