mirror of
https://github.com/greenart7c3/Amber.git
synced 2026-10-05 10:58:23 +00:00
Remove push notifications server
This commit is contained in:
+1
-10
@@ -4,7 +4,6 @@ plugins {
|
||||
alias(libs.plugins.androidApplication)
|
||||
alias(libs.plugins.jetbrainsKotlinAndroid)
|
||||
alias(libs.plugins.gradle.ktlint) version(libs.versions.ktlint)
|
||||
alias(libs.plugins.google.gms) version(libs.versions.gms)
|
||||
alias(libs.plugins.kotlin.ksp) version(libs.versions.ksp)
|
||||
alias(libs.plugins.jetbrainsComposeCompiler)
|
||||
}
|
||||
@@ -58,7 +57,6 @@ android {
|
||||
'ku-rTR',
|
||||
'lt-rLT',
|
||||
'ne-rNP',
|
||||
'night',
|
||||
'nl',
|
||||
'nl-rBE',
|
||||
'pcm-rNG',
|
||||
@@ -131,11 +129,8 @@ android {
|
||||
flavorDimensions += "version"
|
||||
|
||||
productFlavors {
|
||||
create("play") {
|
||||
getIsDefault().set(true)
|
||||
dimension = "version"
|
||||
}
|
||||
create("free") {
|
||||
getIsDefault().set(true)
|
||||
dimension = "version"
|
||||
}
|
||||
create("offline") {
|
||||
@@ -241,12 +236,8 @@ dependencies {
|
||||
// Biometrics
|
||||
implementation libs.biometric.ktx
|
||||
|
||||
playImplementation platform(libs.firebase.bom)
|
||||
playImplementation(libs.firebase.messaging)
|
||||
freeImplementation(libs.unfiedpush)
|
||||
ksp(libs.room.compiler)
|
||||
implementation(libs.room.ktx)
|
||||
|
||||
freeImplementation(libs.okhttp)
|
||||
playImplementation(libs.okhttp)
|
||||
}
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
{
|
||||
"project_info": {
|
||||
"project_number": "575711686692",
|
||||
"project_id": "amber-c362b",
|
||||
"storage_bucket": "amber-c362b.appspot.com"
|
||||
},
|
||||
"client": [
|
||||
{
|
||||
"client_info": {
|
||||
"mobilesdk_app_id": "1:575711686692:android:11451e08365ea86f6ce8b1",
|
||||
"android_client_info": {
|
||||
"package_name": "com.greenart7c3.nostrsigner"
|
||||
}
|
||||
},
|
||||
"oauth_client": [],
|
||||
"api_key": [
|
||||
{
|
||||
"current_key": "AIzaSyDB2tmjxGmFx_Oz25YhtKkheWbdb_NlnfU"
|
||||
}
|
||||
],
|
||||
"services": {
|
||||
"appinvite_service": {
|
||||
"other_platform_oauth_client": []
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"client_info": {
|
||||
"mobilesdk_app_id": "1:575711686692:android:fb6ef62b68245d696ce8b1",
|
||||
"android_client_info": {
|
||||
"package_name": "com.greenart7c3.nostrsigner.debug"
|
||||
}
|
||||
},
|
||||
"oauth_client": [],
|
||||
"api_key": [
|
||||
{
|
||||
"current_key": "AIzaSyDB2tmjxGmFx_Oz25YhtKkheWbdb_NlnfU"
|
||||
}
|
||||
],
|
||||
"services": {
|
||||
"appinvite_service": {
|
||||
"other_platform_oauth_client": []
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"configuration_version": "1"
|
||||
}
|
||||
@@ -26,19 +26,6 @@
|
||||
/>
|
||||
</service>
|
||||
|
||||
<receiver
|
||||
android:exported="true"
|
||||
android:enabled="true"
|
||||
android:name=".service.PushMessageReceiver">
|
||||
<intent-filter>
|
||||
<action android:name="org.unifiedpush.android.connector.MESSAGE"/>
|
||||
<action android:name="org.unifiedpush.android.connector.UNREGISTERED"/>
|
||||
<action android:name="org.unifiedpush.android.connector.NEW_ENDPOINT"/>
|
||||
<action android:name="org.unifiedpush.android.connector.REGISTRATION_FAILED"/>
|
||||
<action android:name="org.unifiedpush.android.connector.REGISTRATION_REFUSED"/>
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true">
|
||||
|
||||
@@ -1,121 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2024 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.greenart7c3.nostrsigner.service
|
||||
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
import com.greenart7c3.nostrsigner.LocalPreferences
|
||||
import com.greenart7c3.nostrsigner.NostrSigner
|
||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import org.unifiedpush.android.connector.UnifiedPush
|
||||
|
||||
interface PushDistributorActions {
|
||||
fun getSavedDistributor(): String
|
||||
|
||||
fun getInstalledDistributors(): List<String>
|
||||
|
||||
fun saveDistributor(distributor: String)
|
||||
|
||||
fun removeSavedDistributor()
|
||||
}
|
||||
|
||||
object PushDistributorHandler : PushDistributorActions {
|
||||
private val appContext = NostrSigner.getInstance().applicationContext
|
||||
private val unifiedPush: UnifiedPush = UnifiedPush
|
||||
|
||||
private var endpointInternal = NostrSigner.getInstance().settings.endpoint
|
||||
|
||||
fun getSavedEndpoint() = endpointInternal
|
||||
|
||||
@OptIn(DelicateCoroutinesApi::class)
|
||||
fun setEndpoint(newEndpoint: String) {
|
||||
endpointInternal = newEndpoint
|
||||
GlobalScope.launch(Dispatchers.IO) {
|
||||
NostrSigner.getInstance().settings = NostrSigner.getInstance().settings.copy(endpoint = newEndpoint)
|
||||
LocalPreferences.saveSettingsToEncryptedStorage(NostrSigner.getInstance().settings)
|
||||
}
|
||||
Log.d("PushHandler", "New endpoint saved : $endpointInternal")
|
||||
}
|
||||
|
||||
@OptIn(DelicateCoroutinesApi::class)
|
||||
fun removeEndpoint() {
|
||||
endpointInternal = ""
|
||||
GlobalScope.launch(Dispatchers.IO) {
|
||||
NostrSigner.getInstance().settings = NostrSigner.getInstance().settings.copy(endpoint = "")
|
||||
LocalPreferences.saveSettingsToEncryptedStorage(NostrSigner.getInstance().settings)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getSavedDistributor(): String {
|
||||
return unifiedPush.getSavedDistributor(appContext) ?: ""
|
||||
}
|
||||
|
||||
fun savedDistributorExists(): Boolean = getSavedDistributor().isNotEmpty()
|
||||
|
||||
override fun getInstalledDistributors(): List<String> {
|
||||
return unifiedPush.getDistributors(appContext)
|
||||
}
|
||||
|
||||
fun formattedDistributorNames(): List<String> {
|
||||
val distributorsArray = getInstalledDistributors().toTypedArray()
|
||||
val distributorsNameArray =
|
||||
distributorsArray
|
||||
.map {
|
||||
try {
|
||||
val ai =
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
appContext.packageManager.getApplicationInfo(
|
||||
it,
|
||||
PackageManager.ApplicationInfoFlags.of(
|
||||
PackageManager.GET_META_DATA.toLong(),
|
||||
),
|
||||
)
|
||||
} else {
|
||||
appContext.packageManager.getApplicationInfo(it, 0)
|
||||
}
|
||||
appContext.packageManager.getApplicationLabel(ai)
|
||||
} catch (e: PackageManager.NameNotFoundException) {
|
||||
it
|
||||
}
|
||||
as String
|
||||
}
|
||||
.toTypedArray()
|
||||
return distributorsNameArray.toList()
|
||||
}
|
||||
|
||||
override fun saveDistributor(distributor: String) {
|
||||
unifiedPush.saveDistributor(appContext, distributor)
|
||||
unifiedPush.registerApp(appContext)
|
||||
}
|
||||
|
||||
override fun removeSavedDistributor() {
|
||||
unifiedPush.safeRemoveDistributor(appContext)
|
||||
}
|
||||
|
||||
fun forceRemoveDistributor(context: Context) {
|
||||
unifiedPush.forceRemoveDistributor(context)
|
||||
}
|
||||
}
|
||||
@@ -1,130 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2024 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.greenart7c3.nostrsigner.service
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import android.util.LruCache
|
||||
import com.greenart7c3.nostrsigner.LocalPreferences
|
||||
import com.greenart7c3.nostrsigner.NostrSigner
|
||||
import com.greenart7c3.nostrsigner.database.LogEntity
|
||||
import com.vitorpamplona.quartz.events.Event
|
||||
import com.vitorpamplona.quartz.events.GiftWrapEvent
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.launch
|
||||
import org.unifiedpush.android.connector.MessagingReceiver
|
||||
|
||||
class PushMessageReceiver : MessagingReceiver() {
|
||||
companion object {
|
||||
private val TAG = "Amber-OSSPushReceiver"
|
||||
}
|
||||
|
||||
private val appContext = NostrSigner.getInstance().applicationContext
|
||||
private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
|
||||
private val eventCache = LruCache<String, String>(100)
|
||||
private val pushHandler = PushDistributorHandler
|
||||
|
||||
override fun onMessage(
|
||||
context: Context,
|
||||
message: ByteArray,
|
||||
instance: String,
|
||||
) {
|
||||
val messageStr = message.decodeToString()
|
||||
Log.d(TAG, "New message $messageStr for Instance: $instance")
|
||||
scope.launch {
|
||||
try {
|
||||
parseMessage(messageStr)?.let { receiveIfNew(it) }
|
||||
} catch (e: Exception) {
|
||||
if (e is CancellationException) throw e
|
||||
Log.d(TAG, "Message could not be parsed: ${e.message}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun parseMessage(message: String): GiftWrapEvent? {
|
||||
(Event.fromJson(message) as? GiftWrapEvent)?.let {
|
||||
return it
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private suspend fun receiveIfNew(event: GiftWrapEvent) {
|
||||
if (eventCache.get(event.id) == null) {
|
||||
eventCache.put(event.id, event.id)
|
||||
EventNotificationConsumer(appContext).consume(event)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onNewEndpoint(
|
||||
context: Context,
|
||||
endpoint: String,
|
||||
instance: String,
|
||||
) {
|
||||
Log.d(TAG, "New endpoint provided:- $endpoint for Instance: $instance")
|
||||
val sanitizedEndpoint = if (endpoint.endsWith("?up=1")) endpoint.dropLast(5) else endpoint
|
||||
if (pushHandler.getSavedEndpoint() == sanitizedEndpoint) {
|
||||
Log.d(TAG, "Endpoint already saved. ")
|
||||
return
|
||||
}
|
||||
pushHandler.setEndpoint(sanitizedEndpoint)
|
||||
scope.launch(Dispatchers.IO) {
|
||||
RegisterAccounts(LocalPreferences.allSavedAccounts(context)).go(sanitizedEndpoint)
|
||||
NotificationUtils.getOrCreateDMChannel(appContext)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onRegistrationFailed(
|
||||
context: Context,
|
||||
instance: String,
|
||||
) {
|
||||
Log.d(TAG, "Registration failed for Instance: $instance")
|
||||
|
||||
LocalPreferences.allSavedAccounts(context).forEach {
|
||||
NostrSigner.getInstance().getDatabase(it.npub).applicationDao().insertLog(
|
||||
LogEntity(
|
||||
0,
|
||||
"Push server",
|
||||
"Push server",
|
||||
"Registration failed for Instance: $instance",
|
||||
System.currentTimeMillis(),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
scope.cancel()
|
||||
pushHandler.forceRemoveDistributor(context)
|
||||
}
|
||||
|
||||
override fun onUnregistered(
|
||||
context: Context,
|
||||
instance: String,
|
||||
) {
|
||||
val removedEndpoint = pushHandler.getSavedEndpoint()
|
||||
Log.d(TAG, "Endpoint: $removedEndpoint removed for Instance: $instance")
|
||||
Log.d(TAG, "App is unregistered. ")
|
||||
pushHandler.forceRemoveDistributor(context)
|
||||
pushHandler.removeEndpoint()
|
||||
}
|
||||
}
|
||||
@@ -1,114 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2024 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.greenart7c3.nostrsigner.service
|
||||
|
||||
import android.content.Intent
|
||||
import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
import android.net.Uri
|
||||
import android.util.Log
|
||||
import com.greenart7c3.nostrsigner.AccountInfo
|
||||
import com.greenart7c3.nostrsigner.LocalPreferences
|
||||
import com.greenart7c3.nostrsigner.NostrSigner
|
||||
import com.greenart7c3.nostrsigner.R
|
||||
import com.greenart7c3.nostrsigner.database.LogEntity
|
||||
import com.greenart7c3.nostrsigner.ui.AccountStateViewModel
|
||||
import com.greenart7c3.nostrsigner.ui.NotificationType
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
object PushNotificationUtils {
|
||||
var hasInit: Boolean = false
|
||||
private val pushHandler = PushDistributorHandler
|
||||
var accountState: AccountStateViewModel? = null
|
||||
|
||||
@OptIn(DelicateCoroutinesApi::class)
|
||||
suspend fun init(accounts: List<AccountInfo>) {
|
||||
if (hasInit || NostrSigner.getInstance().settings.notificationType == NotificationType.DIRECT) {
|
||||
return
|
||||
}
|
||||
|
||||
val savedDistributor = pushHandler.getSavedDistributor()
|
||||
if (savedDistributor.isBlank()) {
|
||||
val distributions = pushHandler.getInstalledDistributors()
|
||||
if (distributions.isNotEmpty()) {
|
||||
distributions.firstOrNull { it.isNotBlank() }?.let {
|
||||
pushHandler.saveDistributor(it)
|
||||
}
|
||||
} else {
|
||||
accounts.forEach {
|
||||
NostrSigner.getInstance().getDatabase(it.npub).applicationDao().insertLog(
|
||||
LogEntity(
|
||||
0,
|
||||
"Push server",
|
||||
"Push server",
|
||||
"No distributors found for push notifications",
|
||||
System.currentTimeMillis(),
|
||||
),
|
||||
)
|
||||
}
|
||||
if (!NostrSigner.getInstance().settings.pushServerMessage && NostrSigner.getInstance().settings.notificationType != NotificationType.DIRECT) {
|
||||
accountState?.toast(
|
||||
title = NostrSigner.getInstance().getString(R.string.push_server),
|
||||
message = NostrSigner.getInstance().getString(R.string.no_distributors_found),
|
||||
onOk = {
|
||||
GlobalScope.launch(Dispatchers.IO) {
|
||||
NostrSigner.getInstance().settings = NostrSigner.getInstance().settings.copy(pushServerMessage = true)
|
||||
LocalPreferences.saveSettingsToEncryptedStorage(NostrSigner.getInstance().settings)
|
||||
}
|
||||
|
||||
val intent = Intent(Intent.ACTION_VIEW)
|
||||
intent.data = Uri.parse("https://ntfy.sh/")
|
||||
intent.flags = FLAG_ACTIVITY_NEW_TASK
|
||||
NostrSigner.getInstance().startActivity(intent)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
try {
|
||||
if (pushHandler.savedDistributorExists()) {
|
||||
val endpoint = pushHandler.getSavedEndpoint()
|
||||
if (endpoint.isBlank()) {
|
||||
pushHandler.saveDistributor(pushHandler.getSavedDistributor())
|
||||
}
|
||||
|
||||
RegisterAccounts(accounts).go(pushHandler.getSavedEndpoint())
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
if (e is CancellationException) throw e
|
||||
Log.d("Amber-OSSPushUtils", "Failed to get endpoint.")
|
||||
accounts.forEach {
|
||||
NostrSigner.getInstance().getDatabase(it.npub).applicationDao().insertLog(
|
||||
LogEntity(
|
||||
0,
|
||||
"Push server",
|
||||
"Push server",
|
||||
"Failed to get endpoint. ${e.message}",
|
||||
System.currentTimeMillis(),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2024 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.greenart7c3.nostrsigner.ui
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.greenart7c3.nostrsigner.R
|
||||
import com.greenart7c3.nostrsigner.service.PushDistributorHandler
|
||||
import com.greenart7c3.nostrsigner.ui.components.TitleExplainer
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
@Composable
|
||||
fun LoadDistributors(onInner: @Composable (String, ImmutableList<String>, ImmutableList<TitleExplainer>) -> Unit) {
|
||||
val currentDistributor = PushDistributorHandler.getSavedDistributor().ifBlank { null } ?: "None"
|
||||
|
||||
val list =
|
||||
remember {
|
||||
PushDistributorHandler.getInstalledDistributors().plus("None").toImmutableList()
|
||||
}
|
||||
|
||||
val readableListWithExplainer =
|
||||
PushDistributorHandler
|
||||
.formattedDistributorNames()
|
||||
.mapIndexed { index, name ->
|
||||
TitleExplainer(
|
||||
name,
|
||||
stringResource(id = R.string.push_server_uses_app_explainer, list[index]),
|
||||
)
|
||||
}.plus(
|
||||
TitleExplainer(
|
||||
stringResource(id = R.string.push_server_none),
|
||||
stringResource(id = R.string.push_server_none_explainer),
|
||||
),
|
||||
)
|
||||
.toImmutableList()
|
||||
|
||||
onInner(
|
||||
currentDistributor,
|
||||
list,
|
||||
readableListWithExplainer,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun PushNotificationSettingsRow() {
|
||||
val context = LocalContext.current
|
||||
LoadDistributors { currentDistributor, list, readableListWithExplainer ->
|
||||
SettingsRow(
|
||||
R.string.push_server_title,
|
||||
R.string.push_server_explainer,
|
||||
selectedItems = readableListWithExplainer,
|
||||
selectedIndex = list.indexOf(currentDistributor),
|
||||
) { index ->
|
||||
if (list[index] == "None") {
|
||||
PushDistributorHandler.forceRemoveDistributor(context)
|
||||
} else {
|
||||
PushDistributorHandler.saveDistributor(list[index])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,6 @@ import com.greenart7c3.nostrsigner.database.ApplicationPermissionsEntity
|
||||
import com.greenart7c3.nostrsigner.models.Account
|
||||
import com.greenart7c3.nostrsigner.models.AmberSettings
|
||||
import com.greenart7c3.nostrsigner.ui.parseBiometricsTimeType
|
||||
import com.greenart7c3.nostrsigner.ui.parseNotificationType
|
||||
import com.vitorpamplona.ammolite.relays.COMMON_FEED_TYPES
|
||||
import com.vitorpamplona.ammolite.relays.RelaySetupInfo
|
||||
import com.vitorpamplona.ammolite.service.HttpClientManager
|
||||
@@ -116,7 +115,6 @@ object LocalPreferences {
|
||||
putStringSet(PrefKeys.DEFAULT_RELAYS, settings.defaultRelays.map { it.url }.toSet())
|
||||
putLong(PrefKeys.LAST_BIOMETRICS_TIME, settings.lastBiometricsTime)
|
||||
putBoolean(PrefKeys.USE_AUTH, settings.useAuth)
|
||||
putInt(PrefKeys.NOTIFICATION_TYPE, settings.notificationType.screenCode)
|
||||
putInt(PrefKeys.BIOMETRICS_TYPE, settings.biometricsTimeType.screenCode)
|
||||
putBoolean(PrefKeys.USE_PIN, settings.usePin)
|
||||
}.apply()
|
||||
@@ -156,7 +154,6 @@ object LocalPreferences {
|
||||
} ?: listOf(RelaySetupInfo("wss://relay.nsec.app", read = true, write = true, feedTypes = COMMON_FEED_TYPES)),
|
||||
lastBiometricsTime = getLong(PrefKeys.LAST_BIOMETRICS_TIME, 0),
|
||||
useAuth = getBoolean(PrefKeys.USE_AUTH, false),
|
||||
notificationType = parseNotificationType(getInt(PrefKeys.NOTIFICATION_TYPE, 1)),
|
||||
biometricsTimeType = parseBiometricsTimeType(getInt(PrefKeys.BIOMETRICS_TYPE, 0)),
|
||||
usePin = getBoolean(PrefKeys.USE_PIN, false),
|
||||
)
|
||||
|
||||
@@ -12,9 +12,7 @@ import com.greenart7c3.nostrsigner.database.AppDatabase
|
||||
import com.greenart7c3.nostrsigner.models.AmberSettings
|
||||
import com.greenart7c3.nostrsigner.service.ConnectivityService
|
||||
import com.greenart7c3.nostrsigner.service.RelayDisconnectService
|
||||
import com.greenart7c3.nostrsigner.ui.NotificationType
|
||||
import com.vitorpamplona.ammolite.relays.Client
|
||||
import com.vitorpamplona.ammolite.relays.Relay
|
||||
import com.vitorpamplona.ammolite.relays.RelayPool
|
||||
import com.vitorpamplona.ammolite.relays.RelaySetupInfo
|
||||
import com.vitorpamplona.ammolite.relays.RelaySetupInfoToConnect
|
||||
@@ -122,28 +120,11 @@ class NostrSigner : Application() {
|
||||
LocalPreferences.loadFromEncryptedStorage(this, it.npub)?.useProxy ?: false
|
||||
}
|
||||
|
||||
if (settings.notificationType != NotificationType.DIRECT) {
|
||||
RelayPool.register(Client)
|
||||
savedRelays.forEach { setupInfo ->
|
||||
if (RelayPool.getRelay(setupInfo.url) == null) {
|
||||
RelayPool.addRelay(
|
||||
Relay(
|
||||
setupInfo.url,
|
||||
setupInfo.read,
|
||||
setupInfo.write,
|
||||
if (isPrivateIp(setupInfo.url)) false else useProxy,
|
||||
setupInfo.feedTypes,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldReconnect) {
|
||||
checkIfRelaysAreConnected()
|
||||
}
|
||||
@Suppress("KotlinConstantConditions")
|
||||
if (settings.notificationType == NotificationType.DIRECT && BuildConfig.FLAVOR != "offline" && savedRelays.isNotEmpty()) {
|
||||
if (BuildConfig.FLAVOR != "offline" && savedRelays.isNotEmpty()) {
|
||||
Client.reconnect(
|
||||
savedRelays.map { RelaySetupInfoToConnect(it.url, if (isPrivateIp(it.url)) false else useProxy, it.read, it.write, it.feedTypes) }.toTypedArray(),
|
||||
true,
|
||||
|
||||
@@ -26,6 +26,9 @@ interface ApplicationDao {
|
||||
@Query("SELECT * FROM application where pubKey = :pubKey order by name")
|
||||
fun getAll(pubKey: String): List<ApplicationEntity>
|
||||
|
||||
@Query("SELECT * FROM application where pubKey = :pubKey order by name")
|
||||
fun getAllFlow(pubKey: String): Flow<List<ApplicationEntity>>
|
||||
|
||||
@Query("SELECT * FROM application WHERE `key` = :key")
|
||||
@Transaction
|
||||
fun getByKey(key: String): ApplicationWithPermissions?
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package com.greenart7c3.nostrsigner.models
|
||||
|
||||
import com.greenart7c3.nostrsigner.ui.BiometricsTimeType
|
||||
import com.greenart7c3.nostrsigner.ui.NotificationType
|
||||
import com.vitorpamplona.ammolite.relays.COMMON_FEED_TYPES
|
||||
import com.vitorpamplona.ammolite.relays.RelaySetupInfo
|
||||
|
||||
@@ -11,7 +10,6 @@ data class AmberSettings(
|
||||
val defaultRelays: List<RelaySetupInfo> = listOf(RelaySetupInfo("wss://relay.nsec.app", read = true, write = true, feedTypes = COMMON_FEED_TYPES)),
|
||||
val lastBiometricsTime: Long = 0,
|
||||
val useAuth: Boolean = false,
|
||||
val notificationType: NotificationType = NotificationType.DIRECT,
|
||||
val biometricsTimeType: BiometricsTimeType = BiometricsTimeType.EVERY_TIME,
|
||||
val usePin: Boolean = false,
|
||||
)
|
||||
|
||||
@@ -13,10 +13,8 @@ import android.os.IBinder
|
||||
import android.util.Log
|
||||
import androidx.core.app.NotificationCompat
|
||||
import com.greenart7c3.nostrsigner.BuildConfig
|
||||
import com.greenart7c3.nostrsigner.LocalPreferences
|
||||
import com.greenart7c3.nostrsigner.NostrSigner
|
||||
import com.greenart7c3.nostrsigner.R
|
||||
import com.greenart7c3.nostrsigner.ui.NotificationType
|
||||
import com.vitorpamplona.ammolite.relays.RelayPool
|
||||
import java.util.Timer
|
||||
import java.util.TimerTask
|
||||
@@ -40,7 +38,7 @@ class ConnectivityService : Service() {
|
||||
@Suppress("KotlinConstantConditions")
|
||||
if (BuildConfig.FLAVOR == "offline") return
|
||||
|
||||
if (lastNetwork != null && lastNetwork != network && NostrSigner.getInstance().settings.notificationType == NotificationType.DIRECT) {
|
||||
if (lastNetwork != null && lastNetwork != network) {
|
||||
scope.launch(Dispatchers.IO) {
|
||||
NotificationDataSource.stopSync()
|
||||
delay(1000)
|
||||
@@ -66,7 +64,7 @@ class ConnectivityService : Service() {
|
||||
"ServiceManager NetworkCallback",
|
||||
"onCapabilitiesChanged: ${network.networkHandle} hasMobileData ${NostrSigner.getInstance().isOnMobileDataState.value} hasWifi ${NostrSigner.getInstance().isOnWifiDataState.value}",
|
||||
)
|
||||
if (NostrSigner.getInstance().updateNetworkCapabilities(networkCapabilities) && NostrSigner.getInstance().settings.notificationType == NotificationType.DIRECT) {
|
||||
if (NostrSigner.getInstance().updateNetworkCapabilities(networkCapabilities)) {
|
||||
NotificationDataSource.stopSync()
|
||||
delay(1000)
|
||||
NotificationDataSource.start()
|
||||
@@ -101,11 +99,6 @@ class ConnectivityService : Service() {
|
||||
isStarted = true
|
||||
startForeground(1, createNotification())
|
||||
|
||||
scope.launch(Dispatchers.IO) {
|
||||
PushNotificationUtils.hasInit = false
|
||||
PushNotificationUtils.init(LocalPreferences.allSavedAccounts(this@ConnectivityService))
|
||||
}
|
||||
|
||||
@Suppress("KotlinConstantConditions")
|
||||
if (BuildConfig.FLAVOR != "offline") {
|
||||
val connectivityManager =
|
||||
@@ -119,9 +112,11 @@ class ConnectivityService : Service() {
|
||||
timer.schedule(
|
||||
object : TimerTask() {
|
||||
override fun run() {
|
||||
if (NostrSigner.getInstance().settings.notificationType == NotificationType.PUSH) return
|
||||
@Suppress("KotlinConstantConditions")
|
||||
if (BuildConfig.FLAVOR == "offline") {
|
||||
return
|
||||
}
|
||||
|
||||
Log.d("ConnectivityService", "Checking connectivity...")
|
||||
RelayPool.getAll().forEach {
|
||||
if (!it.isConnected()) {
|
||||
Log.d(
|
||||
|
||||
@@ -42,13 +42,10 @@ import com.greenart7c3.nostrsigner.models.Permission
|
||||
import com.greenart7c3.nostrsigner.models.SignerType
|
||||
import com.greenart7c3.nostrsigner.service.NotificationUtils.sendNotification
|
||||
import com.greenart7c3.nostrsigner.service.model.AmberEvent
|
||||
import com.greenart7c3.nostrsigner.ui.NotificationType
|
||||
import com.vitorpamplona.quartz.crypto.nip04.Nip04
|
||||
import com.vitorpamplona.quartz.encoders.Hex
|
||||
import com.vitorpamplona.quartz.encoders.toNpub
|
||||
import com.vitorpamplona.quartz.events.Event
|
||||
import com.vitorpamplona.quartz.events.GiftWrapEvent
|
||||
import com.vitorpamplona.quartz.events.SealedGossipEvent
|
||||
|
||||
class EventNotificationConsumer(private val applicationContext: Context) {
|
||||
|
||||
@@ -64,61 +61,6 @@ class EventNotificationConsumer(private val applicationContext: Context) {
|
||||
}
|
||||
}
|
||||
|
||||
fun consume(event: GiftWrapEvent) {
|
||||
if (!notificationManager().areNotificationsEnabled()) return
|
||||
if (!event.hasCorrectIDHash()) return
|
||||
if (!event.hasVerifiedSignature()) return
|
||||
|
||||
// PushNotification Wraps don't include a receiver.
|
||||
// Test with all logged in accounts
|
||||
LocalPreferences.allSavedAccounts(applicationContext).forEach {
|
||||
if (it.hasPrivKey) {
|
||||
LocalPreferences.loadFromEncryptedStorage(applicationContext, it.npub)?.let { acc ->
|
||||
if (NostrSigner.getInstance().settings.notificationType == NotificationType.PUSH) {
|
||||
consumeIfMatchesAccount(event, acc)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun consumeIfMatchesAccount(
|
||||
pushWrappedEvent: GiftWrapEvent,
|
||||
account: Account,
|
||||
) {
|
||||
pushWrappedEvent.unwrap(account.signer) { notificationEvent ->
|
||||
unwrapAndConsume(notificationEvent, account) { innerEvent ->
|
||||
if (innerEvent.kind == 24133) {
|
||||
if (innerEvent.hasCorrectIDHash()) {
|
||||
if (innerEvent.hasVerifiedSignature()) {
|
||||
notify(innerEvent, account)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun unwrapAndConsume(
|
||||
event: Event,
|
||||
account: Account,
|
||||
onReady: (Event) -> Unit,
|
||||
) {
|
||||
when (event) {
|
||||
is GiftWrapEvent -> {
|
||||
event.unwrap(account.signer) { unwrapAndConsume(it, account, onReady) }
|
||||
}
|
||||
is SealedGossipEvent -> {
|
||||
event.unseal(account.signer) {
|
||||
onReady(it)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
onReady(event)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun notify(
|
||||
event: Event,
|
||||
acc: Account,
|
||||
|
||||
@@ -34,7 +34,6 @@ import com.greenart7c3.nostrsigner.models.TimeUtils
|
||||
import com.greenart7c3.nostrsigner.models.containsNip
|
||||
import com.greenart7c3.nostrsigner.relays.AmberListenerSingleton
|
||||
import com.greenart7c3.nostrsigner.service.model.AmberEvent
|
||||
import com.greenart7c3.nostrsigner.ui.NotificationType
|
||||
import com.vitorpamplona.ammolite.relays.COMMON_FEED_TYPES
|
||||
import com.vitorpamplona.ammolite.relays.Client
|
||||
import com.vitorpamplona.ammolite.relays.RelayPool
|
||||
@@ -388,15 +387,11 @@ object IntentUtils {
|
||||
GlobalScope.launch(Dispatchers.IO) {
|
||||
if (RelayPool.getAll().any { !it.isConnected() }) {
|
||||
NostrSigner.getInstance().checkForNewRelays(
|
||||
NostrSigner.getInstance().settings.notificationType != NotificationType.DIRECT,
|
||||
newRelays = relays.toSet(),
|
||||
)
|
||||
}
|
||||
|
||||
val success = Client.sendAndWaitForResponse(it, relayList = relays)
|
||||
if (NostrSigner.getInstance().settings.notificationType != NotificationType.DIRECT) {
|
||||
RelayPool.unregister(Client)
|
||||
}
|
||||
if (success) {
|
||||
onDone()
|
||||
}
|
||||
|
||||
@@ -25,7 +25,6 @@ import com.greenart7c3.nostrsigner.NostrSigner
|
||||
import com.greenart7c3.nostrsigner.checkNotInMainThread
|
||||
import com.greenart7c3.nostrsigner.database.LogEntity
|
||||
import com.greenart7c3.nostrsigner.models.TimeUtils
|
||||
import com.greenart7c3.nostrsigner.ui.NotificationType
|
||||
import com.vitorpamplona.ammolite.relays.COMMON_FEED_TYPES
|
||||
import com.vitorpamplona.ammolite.relays.Client
|
||||
import com.vitorpamplona.ammolite.relays.NostrDataSource
|
||||
@@ -143,9 +142,7 @@ object NotificationDataSource : NostrDataSource("AccountData") {
|
||||
init {
|
||||
scope.launch {
|
||||
LocalPreferences.loadSettingsFromEncryptedStorage()
|
||||
if (NostrSigner.getInstance().settings.notificationType == NotificationType.DIRECT) {
|
||||
Client.subscribe(clientListener)
|
||||
}
|
||||
Client.subscribe(clientListener)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,228 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2024 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.greenart7c3.nostrsigner.service
|
||||
|
||||
import android.util.Log
|
||||
import com.greenart7c3.nostrsigner.AccountInfo
|
||||
import com.greenart7c3.nostrsigner.BuildConfig
|
||||
import com.greenart7c3.nostrsigner.LocalPreferences
|
||||
import com.greenart7c3.nostrsigner.NostrSigner
|
||||
import com.greenart7c3.nostrsigner.database.LogEntity
|
||||
import com.greenart7c3.nostrsigner.models.Account
|
||||
import com.vitorpamplona.ammolite.service.HttpClientManager
|
||||
import com.vitorpamplona.quartz.encoders.toHexKey
|
||||
import com.vitorpamplona.quartz.encoders.toNpub
|
||||
import com.vitorpamplona.quartz.events.RelayAuthEvent
|
||||
import java.net.URL
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withContext
|
||||
import okhttp3.MediaType.Companion.toMediaType
|
||||
import okhttp3.Request
|
||||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
|
||||
class RegisterAccounts(
|
||||
private val accounts: List<AccountInfo>,
|
||||
) {
|
||||
private fun recursiveAuthCreation(
|
||||
notificationToken: String,
|
||||
remainingTos: List<Pair<Account, String>>,
|
||||
output: MutableList<RelayAuthEvent>,
|
||||
onReady: (List<RelayAuthEvent>) -> Unit,
|
||||
) {
|
||||
if (remainingTos.isEmpty()) {
|
||||
onReady(output)
|
||||
return
|
||||
}
|
||||
|
||||
val next = remainingTos.first()
|
||||
|
||||
next.first.createAuthEvent(next.second, notificationToken) {
|
||||
output.add(it)
|
||||
recursiveAuthCreation(notificationToken, remainingTos.filter { remainingTo -> next != remainingTo }, output, onReady)
|
||||
}
|
||||
}
|
||||
|
||||
private fun isSupportedUrl(url: String): Boolean {
|
||||
return url.isNotBlank() &&
|
||||
!url.contains("brb.io") && // no broken relays
|
||||
!url.contains("echo.websocket.org") && // test relay
|
||||
!url.contains("127.0") && // no local relays
|
||||
!url.contains("umbrel.local") && // no local relays
|
||||
!url.contains("192.168.") && // no local relays
|
||||
!url.contains(".onion") && // we are not running on Tor
|
||||
!url.contains("https://") && // not a websocket
|
||||
!url.contains("http://") && // not a websocket
|
||||
!url.contains("www://") && // not a websocket
|
||||
!url.contains("https//") && // not a websocket
|
||||
!url.contains("http//") && // not a websocket
|
||||
!url.contains("www//") && // not a websocket
|
||||
!url.contains("npub1") && // does not allow custom uris
|
||||
!url.contains("was://") && // common mispellings
|
||||
!url.contains("ws://umbrel:") && // local domain
|
||||
!url.contains("\t") && // tab is not allowed
|
||||
!url.contains(" ") && // space is not allowed
|
||||
isValidUrl(url)
|
||||
}
|
||||
|
||||
private fun isValidUrl(url: String): Boolean {
|
||||
try {
|
||||
URL(url.replace("wss://", "https://").replace("ws://", "http://"))
|
||||
return true
|
||||
} catch (e: Exception) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// creates proof that it controls all accounts
|
||||
private fun signEventsToProveControlOfAccounts(
|
||||
accounts: List<AccountInfo>,
|
||||
notificationToken: String,
|
||||
onReady: (List<RelayAuthEvent>) -> Unit,
|
||||
) {
|
||||
val readyToSend: MutableSet<Pair<Account, String>> = mutableSetOf()
|
||||
accounts.forEach {
|
||||
val acc = LocalPreferences.loadFromEncryptedStorage(NostrSigner.getInstance(), it.npub)
|
||||
if (acc != null) {
|
||||
val permissions =
|
||||
NostrSigner.getInstance().getDatabase(
|
||||
acc.signer.keyPair.pubKey.toNpub(),
|
||||
).applicationDao().getAll(acc.signer.keyPair.pubKey.toHexKey())
|
||||
permissions.forEach { permission ->
|
||||
permission.relays.forEach { relay ->
|
||||
if (isSupportedUrl(relay.url)) {
|
||||
readyToSend.add(
|
||||
Pair(acc, relay.url),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
runBlocking {
|
||||
LocalPreferences.loadSettingsFromEncryptedStorage()
|
||||
}
|
||||
val defaultRelays = NostrSigner.getInstance().settings.defaultRelays
|
||||
defaultRelays.forEach { relay ->
|
||||
if (isSupportedUrl(relay.url)) {
|
||||
readyToSend.add(
|
||||
Pair(acc, relay.url),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val listOfAuthEvents = mutableListOf<RelayAuthEvent>()
|
||||
recursiveAuthCreation(
|
||||
notificationToken,
|
||||
readyToSend.toList(),
|
||||
listOfAuthEvents,
|
||||
onReady,
|
||||
)
|
||||
}
|
||||
|
||||
private fun postRegistrationEvent(events: List<RelayAuthEvent>, notificationToken: String) {
|
||||
if (notificationToken.isBlank()) {
|
||||
Log.d("FirebaseMsgService", "No notification token to register with push server")
|
||||
return
|
||||
}
|
||||
try {
|
||||
val jsonObject =
|
||||
"""{
|
||||
"events": [ ${events.joinToString(", ") { it.toJson() }} ]
|
||||
}
|
||||
"""
|
||||
|
||||
val mediaType = "application/json; charset=utf-8".toMediaType()
|
||||
val body = jsonObject.toRequestBody(mediaType)
|
||||
|
||||
val request =
|
||||
Request.Builder()
|
||||
.header("User-Agent", "Amber/${BuildConfig.VERSION_NAME}")
|
||||
.url("https://push.greenart7c3.com/register")
|
||||
.post(body)
|
||||
.build()
|
||||
|
||||
val useProxy = accounts.any {
|
||||
val account = LocalPreferences.loadFromEncryptedStorage(context = NostrSigner.getInstance(), npub = it.npub)
|
||||
val useProxy = account?.useProxy ?: false
|
||||
|
||||
if (useProxy) {
|
||||
HttpClientManager.setDefaultProxyOnPort(account?.proxyPort ?: 9050)
|
||||
}
|
||||
|
||||
useProxy
|
||||
}
|
||||
|
||||
val client = HttpClientManager.getHttpClient(useProxy)
|
||||
var isSuccess = client.newCall(request).execute().use {
|
||||
it.isSuccessful
|
||||
}
|
||||
var tries = 0
|
||||
while (!isSuccess && tries < 3) {
|
||||
Log.d("FirebaseMsgService", "Failed to register with push server, retrying...")
|
||||
isSuccess = client.newCall(request).execute().use {
|
||||
it.isSuccessful
|
||||
}
|
||||
tries++
|
||||
}
|
||||
if (isSuccess) {
|
||||
Log.d("FirebaseMsgService", "Successfully registered with push server")
|
||||
} else {
|
||||
accounts.forEach {
|
||||
NostrSigner.getInstance().getDatabase(it.npub).applicationDao().insertLog(
|
||||
LogEntity(
|
||||
0,
|
||||
"Push server",
|
||||
"Push server",
|
||||
"Failed to register with push server",
|
||||
System.currentTimeMillis(),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
} catch (e: java.lang.Exception) {
|
||||
if (e is CancellationException) throw e
|
||||
val tag = "FirebaseMsgService"
|
||||
|
||||
Log.e(tag, "Unable to register with push server", e)
|
||||
accounts.forEach {
|
||||
NostrSigner.getInstance().getDatabase(it.npub).applicationDao().insertLog(
|
||||
LogEntity(
|
||||
0,
|
||||
"Push server",
|
||||
"Push server",
|
||||
"Unable to register with push server: ${e.message}",
|
||||
System.currentTimeMillis(),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun go(notificationToken: String) =
|
||||
withContext(Dispatchers.IO) {
|
||||
signEventsToProveControlOfAccounts(accounts, notificationToken) { postRegistrationEvent(it, notificationToken) }
|
||||
|
||||
PushNotificationUtils.hasInit = true
|
||||
}
|
||||
}
|
||||
@@ -9,8 +9,6 @@ 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.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.text.selection.SelectionContainer
|
||||
import androidx.compose.material.icons.Icons
|
||||
@@ -19,10 +17,7 @@ import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.ButtonColors
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.CenterAlignedTopAppBar
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
@@ -49,14 +44,12 @@ import com.greenart7c3.nostrsigner.relays.AmberListenerSingleton
|
||||
import com.greenart7c3.nostrsigner.service.ConnectivityService
|
||||
import com.greenart7c3.nostrsigner.service.IntentUtils
|
||||
import com.greenart7c3.nostrsigner.service.NotificationDataSource
|
||||
import com.greenart7c3.nostrsigner.service.PushNotificationUtils
|
||||
import com.vitorpamplona.quartz.encoders.toNpub
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@SuppressLint("StateFlowValueCalledInComposition", "UnrememberedMutableState")
|
||||
@Composable
|
||||
fun AccountScreen(
|
||||
@@ -82,115 +75,69 @@ fun AccountScreen(
|
||||
MainLoginPage(accountStateViewModel)
|
||||
}
|
||||
is AccountState.LoggedIn -> {
|
||||
var isLoading by remember { mutableStateOf(true) }
|
||||
var isNotificationsConfigured by remember {
|
||||
mutableStateOf(false)
|
||||
var intentData by remember {
|
||||
mutableStateOf<IntentData?>(null)
|
||||
}
|
||||
LaunchedEffect(Unit) {
|
||||
launch(Dispatchers.IO) {
|
||||
isNotificationsConfigured = LocalPreferences.isNotificationTypeConfigured()
|
||||
isLoading = false
|
||||
LaunchedEffect(intent, intents) {
|
||||
IntentUtils.getIntentData(context, intent, packageName, intent.getStringExtra("route"), state.account) {
|
||||
intentData = it
|
||||
}
|
||||
val data =
|
||||
intents.firstOrNull {
|
||||
it.currentAccount.isNotBlank()
|
||||
}
|
||||
|
||||
data?.bunkerRequest?.let {
|
||||
if (it.currentAccount.isNotBlank()) {
|
||||
if (LocalPreferences.currentAccount(context) != it.currentAccount) {
|
||||
accountStateViewModel.switchUser(it.currentAccount, null)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isLoading) {
|
||||
Scaffold { innerPadding ->
|
||||
CenterCircularProgressIndicator(
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.padding(innerPadding),
|
||||
)
|
||||
if (intentData != null) {
|
||||
if (intents.none { item -> item.id == intentData!!.id }) {
|
||||
val oldIntents = intents.toMutableList()
|
||||
oldIntents.add(intentData!!)
|
||||
flow.value = oldIntents
|
||||
}
|
||||
} else {
|
||||
if (isNotificationsConfigured) {
|
||||
var intentData by remember {
|
||||
mutableStateOf<IntentData?>(null)
|
||||
}
|
||||
|
||||
val newIntents =
|
||||
intents.ifEmpty {
|
||||
if (intentData == null) {
|
||||
listOf()
|
||||
} else {
|
||||
listOf(intentData)
|
||||
}
|
||||
LaunchedEffect(intent, intents) {
|
||||
IntentUtils.getIntentData(context, intent, packageName, intent.getStringExtra("route"), state.account) {
|
||||
intentData = it
|
||||
}
|
||||
val data =
|
||||
intents.firstOrNull {
|
||||
it.currentAccount.isNotBlank()
|
||||
}
|
||||
}.mapNotNull { it }
|
||||
val database = NostrSigner.getInstance().getDatabase(state.account.signer.keyPair.pubKey.toNpub())
|
||||
val localRoute = mutableStateOf(newIntents.firstNotNullOfOrNull { it.route } ?: state.route)
|
||||
|
||||
data?.bunkerRequest?.let {
|
||||
if (it.currentAccount.isNotBlank()) {
|
||||
if (LocalPreferences.currentAccount(context) != it.currentAccount) {
|
||||
accountStateViewModel.switchUser(it.currentAccount, null)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (intentData != null) {
|
||||
if (intents.none { item -> item.id == intentData!!.id }) {
|
||||
val oldIntents = intents.toMutableList()
|
||||
oldIntents.add(intentData!!)
|
||||
flow.value = oldIntents
|
||||
}
|
||||
}
|
||||
|
||||
val newIntents =
|
||||
intents.ifEmpty {
|
||||
if (intentData == null) {
|
||||
listOf()
|
||||
} else {
|
||||
listOf(intentData)
|
||||
}
|
||||
}.mapNotNull { it }
|
||||
val database = NostrSigner.getInstance().getDatabase(state.account.signer.keyPair.pubKey.toNpub())
|
||||
val localRoute = mutableStateOf(newIntents.firstNotNullOfOrNull { it.route } ?: state.route)
|
||||
|
||||
SideEffect {
|
||||
NostrSigner.getInstance().applicationIOScope.launch(Dispatchers.IO) {
|
||||
PushNotificationUtils.accountState = accountStateViewModel
|
||||
try {
|
||||
NostrSigner.getInstance().applicationContext.startForegroundService(
|
||||
Intent(NostrSigner.getInstance().applicationContext, ConnectivityService::class.java),
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
Log.d("NostrSigner", "Failed to start ConnectivityService", e)
|
||||
}
|
||||
|
||||
@Suppress("KotlinConstantConditions")
|
||||
if (NostrSigner.getInstance().settings.notificationType == NotificationType.DIRECT && BuildConfig.FLAVOR != "offline") {
|
||||
NostrSigner.getInstance().checkForNewRelays()
|
||||
NotificationDataSource.start()
|
||||
delay(5000)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AmberListenerSingleton.accountStateViewModel = accountStateViewModel
|
||||
|
||||
DisplayErrorMessages(accountStateViewModel)
|
||||
MainScreen(state.account, accountStateViewModel, newIntents, packageName, appName, localRoute, database, navController)
|
||||
} else {
|
||||
AmberListenerSingleton.accountStateViewModel = accountStateViewModel
|
||||
DisplayErrorMessages(accountStateViewModel)
|
||||
Scaffold(
|
||||
topBar = {
|
||||
CenterAlignedTopAppBar(
|
||||
title = {
|
||||
Text(stringResource(R.string.notification_type))
|
||||
},
|
||||
)
|
||||
},
|
||||
) { innerPadding ->
|
||||
NotificationTypeScreen(
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.padding(innerPadding)
|
||||
.padding(40.dp),
|
||||
onDone = {
|
||||
isNotificationsConfigured = true
|
||||
},
|
||||
SideEffect {
|
||||
NostrSigner.getInstance().applicationIOScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
NostrSigner.getInstance().applicationContext.startForegroundService(
|
||||
Intent(NostrSigner.getInstance().applicationContext, ConnectivityService::class.java),
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
Log.d("NostrSigner", "Failed to start ConnectivityService", e)
|
||||
}
|
||||
|
||||
@Suppress("KotlinConstantConditions")
|
||||
if (BuildConfig.FLAVOR != "offline") {
|
||||
NostrSigner.getInstance().checkForNewRelays()
|
||||
NotificationDataSource.start()
|
||||
delay(5000)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AmberListenerSingleton.accountStateViewModel = accountStateViewModel
|
||||
|
||||
DisplayErrorMessages(accountStateViewModel)
|
||||
MainScreen(state.account, accountStateViewModel, newIntents, packageName, appName, localRoute, database, navController)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,9 +162,8 @@ fun EditConfigurationScreen(
|
||||
it.permissions,
|
||||
),
|
||||
)
|
||||
if (NostrSigner.getInstance().settings.notificationType == NotificationType.DIRECT) {
|
||||
NostrSigner.getInstance().checkForNewRelays()
|
||||
}
|
||||
|
||||
NostrSigner.getInstance().checkForNewRelays()
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -203,9 +202,7 @@ fun EditConfigurationScreen(
|
||||
it.permissions,
|
||||
),
|
||||
)
|
||||
if (NostrSigner.getInstance().settings.notificationType == NotificationType.DIRECT) {
|
||||
NostrSigner.getInstance().checkForNewRelays()
|
||||
}
|
||||
NostrSigner.getInstance().checkForNewRelays()
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -255,9 +252,7 @@ fun EditConfigurationScreen(
|
||||
it.permissions,
|
||||
),
|
||||
)
|
||||
if (NostrSigner.getInstance().settings.notificationType == NotificationType.DIRECT) {
|
||||
NostrSigner.getInstance().checkForNewRelays()
|
||||
}
|
||||
NostrSigner.getInstance().checkForNewRelays()
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -280,9 +275,7 @@ fun EditConfigurationScreen(
|
||||
scope.launch(Dispatchers.IO) {
|
||||
database.applicationDao().delete(it.application)
|
||||
|
||||
if (NostrSigner.getInstance().settings.notificationType == NotificationType.DIRECT) {
|
||||
NostrSigner.getInstance().checkForNewRelays()
|
||||
}
|
||||
NostrSigner.getInstance().checkForNewRelays()
|
||||
|
||||
scope.launch(Dispatchers.Main) {
|
||||
navController.navigate(Route.Applications.route) {
|
||||
|
||||
@@ -86,7 +86,6 @@ import com.greenart7c3.nostrsigner.models.TimeUtils
|
||||
import com.greenart7c3.nostrsigner.models.basicPermissions
|
||||
import com.greenart7c3.nostrsigner.service.EventNotificationConsumer
|
||||
import com.greenart7c3.nostrsigner.service.IntentUtils
|
||||
import com.greenart7c3.nostrsigner.service.PushNotificationUtils
|
||||
import com.greenart7c3.nostrsigner.service.getAppCompatActivity
|
||||
import com.greenart7c3.nostrsigner.ui.actions.AccountBackupScreen
|
||||
import com.greenart7c3.nostrsigner.ui.actions.AccountsBottomSheet
|
||||
@@ -131,8 +130,7 @@ fun sendResult(
|
||||
val relays = savedApplication?.application?.relays?.ifEmpty { defaultRelays } ?: (intentData.bunkerRequest?.relays?.ifEmpty { defaultRelays } ?: defaultRelays)
|
||||
if (intentData.bunkerRequest != null) {
|
||||
NostrSigner.getInstance().checkForNewRelays(
|
||||
NostrSigner.getInstance().settings.notificationType != NotificationType.DIRECT,
|
||||
relays.toSet(),
|
||||
newRelays = relays.toSet(),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -259,10 +257,6 @@ fun sendResult(
|
||||
true,
|
||||
),
|
||||
)
|
||||
PushNotificationUtils.hasInit = false
|
||||
GlobalScope.launch(Dispatchers.IO) {
|
||||
PushNotificationUtils.init(LocalPreferences.allSavedAccounts(context))
|
||||
}
|
||||
|
||||
EventNotificationConsumer(context).notificationManager().cancelAll()
|
||||
activity?.intent = null
|
||||
@@ -375,9 +369,6 @@ private suspend fun askNotificationPermission(
|
||||
onShouldShowRequestPermissionRationale: () -> Unit,
|
||||
) {
|
||||
if (ContextCompat.checkSelfPermission(context, "android.permission.POST_NOTIFICATIONS") == PackageManager.PERMISSION_GRANTED) {
|
||||
initNotifications(
|
||||
context = context,
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -398,11 +389,6 @@ private suspend fun askNotificationPermission(
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun initNotifications(context: Context) {
|
||||
PushNotificationUtils.hasInit = false
|
||||
PushNotificationUtils.init(LocalPreferences.allSavedAccounts(context))
|
||||
}
|
||||
|
||||
fun Color.Companion.fromHex(colorString: String) = try {
|
||||
Color(android.graphics.Color.parseColor("#$colorString"))
|
||||
} catch (e: Exception) {
|
||||
@@ -431,13 +417,7 @@ fun MainScreen(
|
||||
rememberLauncherForActivityResult(
|
||||
ActivityResultContracts.RequestPermission(),
|
||||
) { isGranted: Boolean ->
|
||||
if (isGranted) {
|
||||
scope.launch(Dispatchers.IO) {
|
||||
initNotifications(
|
||||
context = context,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
if (!isGranted) {
|
||||
if (LocalPreferences.shouldShowRationale(context) == null) {
|
||||
LocalPreferences.updateShouldShowRationale(context, true)
|
||||
}
|
||||
@@ -682,7 +662,6 @@ fun MainScreen(
|
||||
.padding(padding)
|
||||
.padding(24.dp),
|
||||
account = account,
|
||||
accountStateViewModel = accountStateViewModel,
|
||||
navController = navController,
|
||||
database = database,
|
||||
)
|
||||
@@ -779,22 +758,6 @@ fun MainScreen(
|
||||
},
|
||||
)
|
||||
|
||||
composable(
|
||||
Route.NotificationType.route,
|
||||
content = {
|
||||
NotificationTypeScreen(
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.padding(padding)
|
||||
.padding(40.dp),
|
||||
onDone = {
|
||||
navController.navigateUp()
|
||||
},
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
composable(
|
||||
Route.DefaultRelays.route,
|
||||
content = {
|
||||
|
||||
@@ -1,133 +0,0 @@
|
||||
package com.greenart7c3.nostrsigner.ui
|
||||
|
||||
import android.content.Intent
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.greenart7c3.nostrsigner.BuildConfig
|
||||
import com.greenart7c3.nostrsigner.LocalPreferences
|
||||
import com.greenart7c3.nostrsigner.NostrSigner
|
||||
import com.greenart7c3.nostrsigner.R
|
||||
import com.greenart7c3.nostrsigner.relays.AmberListenerSingleton
|
||||
import com.greenart7c3.nostrsigner.service.ConnectivityService
|
||||
import com.greenart7c3.nostrsigner.service.NotificationDataSource
|
||||
import com.greenart7c3.nostrsigner.service.PushNotificationUtils
|
||||
import com.greenart7c3.nostrsigner.ui.components.AmberButton
|
||||
import com.greenart7c3.nostrsigner.ui.components.TitleExplainer
|
||||
import com.vitorpamplona.ammolite.relays.RelayPool
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Composable
|
||||
fun NotificationTypeScreen(
|
||||
modifier: Modifier = Modifier,
|
||||
onDone: () -> Unit,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
launch(Dispatchers.IO) {
|
||||
if (!LocalPreferences.isNotificationTypeConfigured()) {
|
||||
AmberListenerSingleton.accountStateViewModel?.toast(
|
||||
context.getString(R.string.notification_settings),
|
||||
context.getString(R.string.please_configure_your_notification_settings_before_continuing),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val scope = rememberCoroutineScope()
|
||||
var notificationItemsIndex by remember {
|
||||
mutableIntStateOf(NostrSigner.getInstance().settings.notificationType.screenCode)
|
||||
}
|
||||
val notificationItems =
|
||||
persistentListOf(
|
||||
TitleExplainer(
|
||||
stringResource(NotificationType.PUSH.resourceId),
|
||||
stringResource(R.string.push_notifications_explainer),
|
||||
),
|
||||
TitleExplainer(
|
||||
stringResource(NotificationType.DIRECT.resourceId),
|
||||
stringResource(R.string.direct_notifications_explainer),
|
||||
),
|
||||
)
|
||||
Column(
|
||||
modifier = modifier
|
||||
.fillMaxSize(),
|
||||
) {
|
||||
Column(
|
||||
Modifier.weight(1f),
|
||||
) {
|
||||
Box(
|
||||
Modifier
|
||||
.padding(8.dp),
|
||||
) {
|
||||
SettingsRow(
|
||||
R.string.notification_type,
|
||||
R.string.select_the_type_of_notification_you_want_to_receive,
|
||||
notificationItems,
|
||||
notificationItemsIndex,
|
||||
) {
|
||||
notificationItemsIndex = it
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("KotlinConstantConditions")
|
||||
if (BuildConfig.FLAVOR == "free") {
|
||||
Box(
|
||||
Modifier
|
||||
.padding(8.dp),
|
||||
) {
|
||||
PushNotificationSettingsRow()
|
||||
}
|
||||
}
|
||||
}
|
||||
AmberButton(
|
||||
onClick = {
|
||||
scope.launch(Dispatchers.IO) {
|
||||
NostrSigner.getInstance().settings = NostrSigner.getInstance().settings.copy(
|
||||
notificationType = parseNotificationType(notificationItemsIndex),
|
||||
)
|
||||
LocalPreferences.saveSettingsToEncryptedStorage(NostrSigner.getInstance().settings)
|
||||
PushNotificationUtils.hasInit = false
|
||||
PushNotificationUtils.init(LocalPreferences.allSavedAccounts(context))
|
||||
if (notificationItemsIndex == 0) {
|
||||
NotificationDataSource.stopSync()
|
||||
RelayPool.disconnect()
|
||||
} else {
|
||||
NostrSigner.getInstance().checkForNewRelays()
|
||||
NotificationDataSource.start()
|
||||
}
|
||||
NostrSigner.getInstance().applicationContext.startForegroundService(
|
||||
Intent(
|
||||
NostrSigner.getInstance().applicationContext,
|
||||
ConnectivityService::class.java,
|
||||
),
|
||||
)
|
||||
scope.launch(Dispatchers.Main) {
|
||||
onDone()
|
||||
}
|
||||
}
|
||||
},
|
||||
content = {
|
||||
Text(
|
||||
text = stringResource(R.string.save),
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -12,57 +12,35 @@ import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.navigation.NavController
|
||||
import com.greenart7c3.nostrsigner.LocalPreferences
|
||||
import com.greenart7c3.nostrsigner.R
|
||||
import com.greenart7c3.nostrsigner.database.AppDatabase
|
||||
import com.greenart7c3.nostrsigner.database.ApplicationEntity
|
||||
import com.greenart7c3.nostrsigner.models.Account
|
||||
import com.greenart7c3.nostrsigner.service.toShortenHex
|
||||
import com.vitorpamplona.quartz.encoders.toHexKey
|
||||
import com.vitorpamplona.quartz.encoders.toNpub
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Composable
|
||||
fun PermissionsScreen(
|
||||
modifier: Modifier,
|
||||
account: Account,
|
||||
accountStateViewModel: AccountStateViewModel,
|
||||
navController: NavController,
|
||||
database: AppDatabase,
|
||||
) {
|
||||
val lifecycleEvent = rememberLifecycleEvent()
|
||||
val context = LocalContext.current
|
||||
val localAccount = LocalPreferences.loadFromEncryptedStorage(context, account.signer.keyPair.pubKey.toNpub())!!
|
||||
val applications =
|
||||
remember {
|
||||
mutableListOf<ApplicationEntity>()
|
||||
}
|
||||
val scope = rememberCoroutineScope()
|
||||
// val sortBy by remember { mutableStateOf("name") }
|
||||
|
||||
LaunchedEffect(lifecycleEvent) {
|
||||
scope.launch(Dispatchers.IO) {
|
||||
applications.clear()
|
||||
applications.addAll(database.applicationDao().getAll(localAccount.signer.keyPair.pubKey.toHexKey()))
|
||||
}
|
||||
}
|
||||
val applications = database.applicationDao().getAllFlow(account.signer.keyPair.pubKey.toHexKey()).collectAsStateWithLifecycle(emptyList())
|
||||
|
||||
Column(
|
||||
modifier,
|
||||
) {
|
||||
if (applications.isEmpty()) {
|
||||
if (applications.value.isEmpty()) {
|
||||
Column(
|
||||
Modifier.fillMaxSize(),
|
||||
Arrangement.Center,
|
||||
@@ -100,13 +78,13 @@ fun PermissionsScreen(
|
||||
// )
|
||||
// }
|
||||
// }
|
||||
items(applications.size) {
|
||||
items(applications.value.size) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(vertical = 4.dp)
|
||||
.clickable {
|
||||
navController.navigate("Permission/${applications.elementAt(it).key}")
|
||||
navController.navigate("Permission/${applications.value.elementAt(it).key}")
|
||||
},
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
@@ -115,7 +93,7 @@ fun PermissionsScreen(
|
||||
.fillMaxSize(),
|
||||
verticalArrangement = Arrangement.Center,
|
||||
) {
|
||||
val localPermission = applications.elementAt(it)
|
||||
val localPermission = applications.value.elementAt(it)
|
||||
Text(
|
||||
modifier = Modifier.padding(top = 16.dp),
|
||||
text = localPermission.name.ifBlank { localPermission.key.toShortenHex() },
|
||||
|
||||
@@ -18,7 +18,6 @@ import androidx.compose.material.icons.filled.FilterList
|
||||
import androidx.compose.material.icons.filled.Hub
|
||||
import androidx.compose.material.icons.filled.Key
|
||||
import androidx.compose.material.icons.filled.Language
|
||||
import androidx.compose.material.icons.filled.Notifications
|
||||
import androidx.compose.material.icons.filled.Security
|
||||
import androidx.compose.material.icons.filled.SurroundSound
|
||||
import androidx.compose.material3.AlertDialog
|
||||
@@ -61,7 +60,6 @@ import com.greenart7c3.nostrsigner.ui.components.IconRow
|
||||
import com.greenart7c3.nostrsigner.ui.components.TextSpinner
|
||||
import com.greenart7c3.nostrsigner.ui.components.TitleExplainer
|
||||
import com.greenart7c3.nostrsigner.ui.navigation.Route
|
||||
import com.vitorpamplona.ammolite.relays.RelayPool
|
||||
import com.vitorpamplona.quartz.encoders.toNpub
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -144,20 +142,6 @@ fun SettingsScreen(
|
||||
|
||||
@Suppress("KotlinConstantConditions")
|
||||
if (BuildConfig.FLAVOR != "offline") {
|
||||
Box(
|
||||
Modifier
|
||||
.padding(8.dp),
|
||||
) {
|
||||
IconRow(
|
||||
title = stringResource(R.string.notification_type),
|
||||
icon = Icons.Default.Notifications,
|
||||
tint = MaterialTheme.colorScheme.onBackground,
|
||||
onClick = {
|
||||
navController.navigate(Route.NotificationType.route)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
Box(
|
||||
Modifier
|
||||
.padding(8.dp),
|
||||
@@ -182,43 +166,43 @@ fun SettingsScreen(
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Box(
|
||||
Modifier
|
||||
.padding(8.dp),
|
||||
) {
|
||||
IconRow(
|
||||
title = stringResource(R.string.default_relays),
|
||||
icon = Icons.Default.Hub,
|
||||
tint = MaterialTheme.colorScheme.onBackground,
|
||||
onClick = {
|
||||
navController.navigate(Route.DefaultRelays.route)
|
||||
},
|
||||
)
|
||||
}
|
||||
Box(
|
||||
Modifier
|
||||
.padding(8.dp),
|
||||
) {
|
||||
IconRow(
|
||||
title = stringResource(R.string.default_relays),
|
||||
icon = Icons.Default.Hub,
|
||||
tint = MaterialTheme.colorScheme.onBackground,
|
||||
onClick = {
|
||||
navController.navigate(Route.DefaultRelays.route)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
Box(
|
||||
Modifier
|
||||
.padding(8.dp),
|
||||
) {
|
||||
IconRow(
|
||||
title = if (allowNewConnections) stringResource(R.string.disable_listening_for_new_connections) else stringResource(R.string.enable_listening_for_new_connections),
|
||||
icon = Icons.Default.SurroundSound,
|
||||
tint = MaterialTheme.colorScheme.onBackground,
|
||||
onClick = {
|
||||
allowNewConnections = !allowNewConnections
|
||||
account.allowNewConnections = allowNewConnections
|
||||
LocalPreferences.saveToEncryptedStorage(context, account)
|
||||
},
|
||||
onLongClick = {
|
||||
Toast.makeText(
|
||||
context,
|
||||
if (allowNewConnections) context.getString(R.string.disable_listening_for_new_connections) else context.getString(R.string.enable_listening_for_new_connections),
|
||||
Toast.LENGTH_LONG,
|
||||
).show()
|
||||
},
|
||||
)
|
||||
Box(
|
||||
Modifier
|
||||
.padding(8.dp),
|
||||
) {
|
||||
IconRow(
|
||||
title = if (allowNewConnections) stringResource(R.string.disable_listening_for_new_connections) else stringResource(R.string.enable_listening_for_new_connections),
|
||||
icon = Icons.Default.SurroundSound,
|
||||
tint = MaterialTheme.colorScheme.onBackground,
|
||||
onClick = {
|
||||
allowNewConnections = !allowNewConnections
|
||||
account.allowNewConnections = allowNewConnections
|
||||
LocalPreferences.saveToEncryptedStorage(context, account)
|
||||
},
|
||||
onLongClick = {
|
||||
Toast.makeText(
|
||||
context,
|
||||
if (allowNewConnections) context.getString(R.string.disable_listening_for_new_connections) else context.getString(R.string.enable_listening_for_new_connections),
|
||||
Toast.LENGTH_LONG,
|
||||
).show()
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Box(
|
||||
@@ -307,12 +291,8 @@ fun SettingsScreen(
|
||||
LocalPreferences.updateProxy(context, true, proxyPort.value.toInt())
|
||||
scope.launch(Dispatchers.IO) {
|
||||
NotificationDataSource.stopSync()
|
||||
if (NostrSigner.getInstance().settings.notificationType != NotificationType.DIRECT) {
|
||||
RelayPool.disconnect()
|
||||
} else {
|
||||
NostrSigner.getInstance().checkForNewRelays()
|
||||
NotificationDataSource.start()
|
||||
}
|
||||
NostrSigner.getInstance().checkForNewRelays()
|
||||
NotificationDataSource.start()
|
||||
}
|
||||
},
|
||||
onError = {
|
||||
@@ -341,12 +321,8 @@ fun SettingsScreen(
|
||||
LocalPreferences.updateProxy(context, false, proxyPort.value.toInt())
|
||||
scope.launch(Dispatchers.IO) {
|
||||
NotificationDataSource.stopSync()
|
||||
if (NostrSigner.getInstance().settings.notificationType != NotificationType.DIRECT) {
|
||||
RelayPool.disconnect()
|
||||
} else {
|
||||
NostrSigner.getInstance().checkForNewRelays()
|
||||
NotificationDataSource.start()
|
||||
}
|
||||
NostrSigner.getInstance().checkForNewRelays()
|
||||
NotificationDataSource.start()
|
||||
}
|
||||
},
|
||||
) {
|
||||
@@ -406,11 +382,6 @@ fun SettingsRow(
|
||||
}
|
||||
}
|
||||
|
||||
enum class NotificationType(val screenCode: Int, val resourceId: Int) {
|
||||
PUSH(0, R.string.push_notifications),
|
||||
DIRECT(1, R.string.direct_connection),
|
||||
}
|
||||
|
||||
enum class BiometricsTimeType(val screenCode: Int, val resourceId: Int) {
|
||||
EVERY_TIME(0, R.string.every_time),
|
||||
ONE_MINUTE(1, R.string.one_minute),
|
||||
@@ -428,13 +399,3 @@ fun parseBiometricsTimeType(screenCode: Int): BiometricsTimeType {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun parseNotificationType(screenCode: Int): NotificationType {
|
||||
return when (screenCode) {
|
||||
NotificationType.PUSH.screenCode -> NotificationType.PUSH
|
||||
NotificationType.DIRECT.screenCode -> NotificationType.DIRECT
|
||||
else -> {
|
||||
NotificationType.PUSH
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,7 +60,6 @@ import com.greenart7c3.nostrsigner.service.Nip11Retriever
|
||||
import com.greenart7c3.nostrsigner.service.NotificationDataSource
|
||||
import com.greenart7c3.nostrsigner.ui.AccountStateViewModel
|
||||
import com.greenart7c3.nostrsigner.ui.CenterCircularProgressIndicator
|
||||
import com.greenart7c3.nostrsigner.ui.NotificationType
|
||||
import com.greenart7c3.nostrsigner.ui.components.AmberButton
|
||||
import com.vitorpamplona.ammolite.relays.COMMON_FEED_TYPES
|
||||
import com.vitorpamplona.ammolite.relays.Client
|
||||
@@ -149,7 +148,7 @@ fun DefaultRelaysScreen(
|
||||
LocalPreferences.saveSettingsToEncryptedStorage(NostrSigner.getInstance().settings)
|
||||
scope.launch(Dispatchers.IO) {
|
||||
@Suppress("KotlinConstantConditions")
|
||||
if (NostrSigner.getInstance().settings.notificationType == NotificationType.DIRECT && BuildConfig.FLAVOR != "offline") {
|
||||
if (BuildConfig.FLAVOR != "offline") {
|
||||
NostrSigner.getInstance().checkForNewRelays()
|
||||
NotificationDataSource.stop()
|
||||
delay(2000)
|
||||
@@ -191,7 +190,7 @@ fun DefaultRelaysScreen(
|
||||
LocalPreferences.saveSettingsToEncryptedStorage(NostrSigner.getInstance().settings)
|
||||
scope.launch(Dispatchers.IO) {
|
||||
@Suppress("KotlinConstantConditions")
|
||||
if (NostrSigner.getInstance().settings.notificationType == NotificationType.DIRECT && BuildConfig.FLAVOR != "offline") {
|
||||
if (BuildConfig.FLAVOR != "offline") {
|
||||
NostrSigner.getInstance().checkForNewRelays()
|
||||
NotificationDataSource.stop()
|
||||
delay(2000)
|
||||
@@ -238,7 +237,7 @@ fun DefaultRelaysScreen(
|
||||
LocalPreferences.saveSettingsToEncryptedStorage(NostrSigner.getInstance().settings)
|
||||
scope.launch(Dispatchers.IO) {
|
||||
@Suppress("KotlinConstantConditions")
|
||||
if (NostrSigner.getInstance().settings.notificationType == NotificationType.DIRECT && BuildConfig.FLAVOR != "offline") {
|
||||
if (BuildConfig.FLAVOR != "offline") {
|
||||
NostrSigner.getInstance().checkForNewRelays()
|
||||
NotificationDataSource.stop()
|
||||
delay(2000)
|
||||
|
||||
@@ -61,7 +61,6 @@ import com.greenart7c3.nostrsigner.service.IntentUtils
|
||||
import com.greenart7c3.nostrsigner.service.getAppCompatActivity
|
||||
import com.greenart7c3.nostrsigner.service.model.AmberEvent
|
||||
import com.greenart7c3.nostrsigner.service.toShortenHex
|
||||
import com.greenart7c3.nostrsigner.ui.NotificationType
|
||||
import com.greenart7c3.nostrsigner.ui.Result
|
||||
import com.vitorpamplona.quartz.crypto.CryptoUtils
|
||||
import com.vitorpamplona.quartz.encoders.toHexKey
|
||||
@@ -577,7 +576,7 @@ private fun sendResultIntent(
|
||||
private suspend fun reconnectToRelays(intents: List<IntentData>) {
|
||||
if (!intents.any { it.bunkerRequest != null }) return
|
||||
|
||||
NostrSigner.getInstance().checkForNewRelays(NostrSigner.getInstance().settings.notificationType != NotificationType.DIRECT)
|
||||
NostrSigner.getInstance().checkForNewRelays()
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
||||
+4
-9
@@ -36,7 +36,6 @@ import com.greenart7c3.nostrsigner.models.kindToNip
|
||||
import com.greenart7c3.nostrsigner.service.AmberUtils
|
||||
import com.greenart7c3.nostrsigner.service.getAppCompatActivity
|
||||
import com.greenart7c3.nostrsigner.service.toShortenHex
|
||||
import com.greenart7c3.nostrsigner.ui.NotificationType
|
||||
import com.greenart7c3.nostrsigner.ui.sendResult
|
||||
import com.vitorpamplona.quartz.crypto.CryptoUtils
|
||||
import com.vitorpamplona.quartz.encoders.bechToBytes
|
||||
@@ -138,8 +137,7 @@ fun SingleEventHomeScreen(
|
||||
val relays = savedApplication?.application?.relays?.ifEmpty { defaultRelays } ?: intentData.bunkerRequest.relays.ifEmpty { defaultRelays }
|
||||
|
||||
NostrSigner.getInstance().checkForNewRelays(
|
||||
NostrSigner.getInstance().settings.notificationType != NotificationType.DIRECT,
|
||||
relays.toSet(),
|
||||
newRelays = relays.toSet(),
|
||||
)
|
||||
|
||||
AmberUtils.sendBunkerError(
|
||||
@@ -242,8 +240,7 @@ fun SingleEventHomeScreen(
|
||||
|
||||
if (intentData.bunkerRequest != null) {
|
||||
NostrSigner.getInstance().checkForNewRelays(
|
||||
NostrSigner.getInstance().settings.notificationType != NotificationType.DIRECT,
|
||||
relays.toSet(),
|
||||
newRelays = relays.toSet(),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -378,8 +375,7 @@ fun SingleEventHomeScreen(
|
||||
|
||||
if (intentData.bunkerRequest != null) {
|
||||
NostrSigner.getInstance().checkForNewRelays(
|
||||
NostrSigner.getInstance().settings.notificationType != NotificationType.DIRECT,
|
||||
relays.toSet(),
|
||||
newRelays = relays.toSet(),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -528,8 +524,7 @@ fun SingleEventHomeScreen(
|
||||
|
||||
if (intentData.bunkerRequest != null) {
|
||||
NostrSigner.getInstance().checkForNewRelays(
|
||||
NostrSigner.getInstance().settings.notificationType != NotificationType.DIRECT,
|
||||
relays.toSet(),
|
||||
newRelays = relays.toSet(),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -81,13 +81,6 @@ sealed class Route(
|
||||
selectedIcon = Icons.Default.Settings,
|
||||
)
|
||||
|
||||
data object NotificationType : Route(
|
||||
title = NostrSigner.getInstance().getString(R.string.notification_type),
|
||||
route = "NotificationType",
|
||||
icon = Icons.Outlined.Settings,
|
||||
selectedIcon = Icons.Default.Settings,
|
||||
)
|
||||
|
||||
data object DefaultRelays : Route(
|
||||
title = NostrSigner.getInstance().getString(R.string.default_relays),
|
||||
route = "DefaultRelays",
|
||||
@@ -182,7 +175,6 @@ val routes = listOf(
|
||||
Route.Logs,
|
||||
Route.ActiveRelays,
|
||||
Route.Language,
|
||||
Route.NotificationType,
|
||||
Route.DefaultRelays,
|
||||
Route.SignPolicy,
|
||||
Route.Security,
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
|
||||
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
|
||||
|
||||
<queries>
|
||||
<package android:name="org.torproject.android"/>
|
||||
</queries>
|
||||
|
||||
<application
|
||||
android:name=".NostrSigner">
|
||||
|
||||
<service
|
||||
android:name=".service.ConnectivityService"
|
||||
android:enabled="true"
|
||||
android:foregroundServiceType="specialUse"
|
||||
android:exported="false">
|
||||
<property
|
||||
android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
|
||||
android:value="Run a foreground service to keep the connection to the relays active"
|
||||
/>
|
||||
</service>
|
||||
|
||||
<service
|
||||
android:name=".service.Notifications"
|
||||
android:enabled="true"
|
||||
android:exported="false">
|
||||
<intent-filter>
|
||||
<action android:name="com.google.firebase.MESSAGING_EVENT" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="com.greenart7c3.nostrsigner.MAIN_ACTIVITY" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data android:scheme="nostrsigner" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data android:scheme="nostrconnect" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
@@ -1,71 +0,0 @@
|
||||
package com.greenart7c3.nostrsigner.service
|
||||
|
||||
import android.content.Intent
|
||||
import android.util.Log
|
||||
import android.util.LruCache
|
||||
import com.google.firebase.messaging.FirebaseMessagingService
|
||||
import com.google.firebase.messaging.RemoteMessage
|
||||
import com.greenart7c3.nostrsigner.LocalPreferences
|
||||
import com.greenart7c3.nostrsigner.service.NotificationUtils.getOrCreateDMChannel
|
||||
import com.vitorpamplona.quartz.events.Event
|
||||
import com.vitorpamplona.quartz.events.GiftWrapEvent
|
||||
import kotlin.time.measureTimedValue
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class Notifications : FirebaseMessagingService() {
|
||||
private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
|
||||
private val eventCache = LruCache<String, String>(100)
|
||||
|
||||
override fun getStartCommandIntent(originalIntent: Intent?): Intent {
|
||||
return super.getStartCommandIntent(originalIntent)
|
||||
}
|
||||
|
||||
override fun handleIntent(intent: Intent?) {
|
||||
Log.d("Time", "Intent received $intent")
|
||||
super.handleIntent(intent)
|
||||
}
|
||||
|
||||
override fun onMessageReceived(remoteMessage: RemoteMessage) {
|
||||
scope.launch(Dispatchers.IO) {
|
||||
val (_, elapsed) =
|
||||
measureTimedValue { parseMessage(remoteMessage.data)?.let { receiveIfNew(it) } }
|
||||
Log.d("Time", "Notification processed in $elapsed")
|
||||
}
|
||||
super.onMessageReceived(remoteMessage)
|
||||
}
|
||||
|
||||
private fun parseMessage(params: Map<String, String>): GiftWrapEvent? {
|
||||
params["encryptedEvent"]?.let { eventStr ->
|
||||
(Event.fromJson(eventStr) as? GiftWrapEvent)?.let {
|
||||
return it
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private suspend fun receiveIfNew(event: GiftWrapEvent) {
|
||||
if (eventCache.get(event.id) == null) {
|
||||
eventCache.put(event.id, event.id)
|
||||
getOrCreateDMChannel(applicationContext)
|
||||
EventNotificationConsumer(applicationContext).consume(event)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onNewToken(token: String) {
|
||||
scope.launch(Dispatchers.IO) {
|
||||
RegisterAccounts(LocalPreferences.allSavedAccounts(applicationContext)).go(token)
|
||||
}
|
||||
super.onNewToken(token)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
Log.d("Lifetime Event", "PushNotificationReceiverService.onDestroy")
|
||||
|
||||
scope.cancel()
|
||||
super.onDestroy()
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2024 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.greenart7c3.nostrsigner.service
|
||||
|
||||
import android.util.Log
|
||||
import com.google.firebase.messaging.FirebaseMessaging
|
||||
import com.greenart7c3.nostrsigner.AccountInfo
|
||||
import com.greenart7c3.nostrsigner.NostrSigner
|
||||
import com.greenart7c3.nostrsigner.ui.AccountStateViewModel
|
||||
import com.greenart7c3.nostrsigner.ui.NotificationType
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.tasks.await
|
||||
|
||||
object PushNotificationUtils {
|
||||
var hasInit: Boolean = false
|
||||
var accountState: AccountStateViewModel? = null
|
||||
|
||||
suspend fun init(accounts: List<AccountInfo>) =
|
||||
with(Dispatchers.IO) {
|
||||
if (hasInit || NostrSigner.getInstance().settings.notificationType == NotificationType.DIRECT) {
|
||||
return@with
|
||||
}
|
||||
// get user notification token provided by firebase
|
||||
try {
|
||||
RegisterAccounts(accounts).go(FirebaseMessaging.getInstance().token.await())
|
||||
} catch (e: Exception) {
|
||||
if (e is CancellationException) throw e
|
||||
Log.e("Firebase token", "failed to get firebase token", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
package com.greenart7c3.nostrsigner.ui
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
|
||||
@Composable
|
||||
fun PushNotificationSettingsRow() {}
|
||||
@@ -6,7 +6,6 @@ plugins {
|
||||
alias(libs.plugins.androidLibrary) apply false
|
||||
alias(libs.plugins.jetbrainsKotlinAndroid) apply false
|
||||
alias(libs.plugins.serialization) apply false
|
||||
alias(libs.plugins.google.gms) version(libs.versions.gms) apply false
|
||||
alias(libs.plugins.kotlin.ksp) version(libs.versions.ksp) apply false
|
||||
alias(libs.plugins.gradle.ktlint) version(libs.versions.ktlint) apply false
|
||||
}
|
||||
|
||||
@@ -10,15 +10,13 @@ appName=$2
|
||||
|
||||
./gradlew clean bundleRelease --stacktrace
|
||||
./gradlew assembleRelease --stacktrace
|
||||
rm ~/release/amber-*
|
||||
rm ~/release/citrine-*
|
||||
rm ~/release/manifest-*
|
||||
mv app/build/outputs/bundle/playRelease/app-play-release.aab ~/release/
|
||||
rm -rf ~/release/amber-*
|
||||
rm -rf ~/release/citrine-*
|
||||
rm -rf ~/release/manifest-*
|
||||
mv app/build/outputs/bundle/offlineRelease/app-offline-release.aab ~/release/
|
||||
mv app/build/outputs/bundle/freeRelease/app-free-release.aab ~/release/
|
||||
mv app/build/outputs/apk/offline/release/app-offline-* ~/release/
|
||||
mv app/build/outputs/apk/free/release/app-free-* ~/release/
|
||||
mv app/build/outputs/apk/play/release/app-play-* ~/release/
|
||||
./gradlew --stop
|
||||
cd ~/release
|
||||
./generate_manifest.sh ${version} ${appName}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
[versions]
|
||||
activityCompose = "1.9.3"
|
||||
unfiedpush = "2.4.0"
|
||||
appcompat = "1.7.0"
|
||||
biometricKtx = "1.2.0-alpha05"
|
||||
converterGson = "2.11.0"
|
||||
@@ -8,8 +7,6 @@ core = "3.5.3"
|
||||
coreKtx = "1.13.1"
|
||||
coreSplashscreen = "1.0.1"
|
||||
espressoCore = "3.6.1"
|
||||
firebaseBom = "33.5.1"
|
||||
firebaseMessaging = "24.0.3"
|
||||
jna = "5.14.0"
|
||||
junit = "4.13.2"
|
||||
junitVersion = "1.2.1"
|
||||
@@ -33,7 +30,6 @@ ksp = "2.0.20-1.0.24"
|
||||
|
||||
[libraries]
|
||||
activity-compose = { module = "androidx.activity:activity-compose", version.ref = "activityCompose" }
|
||||
unfiedpush = { module = "com.github.UnifiedPush:android-connector", version.ref = "unfiedpush" }
|
||||
appcompat = { module = "androidx.appcompat:appcompat", version.ref = "appcompat" }
|
||||
biometric-ktx = { module = "androidx.biometric:biometric-ktx", version.ref = "biometricKtx" }
|
||||
converter-gson = { module = "com.squareup.retrofit2:converter-gson", version.ref = "converterGson" }
|
||||
@@ -42,8 +38,6 @@ core-ktx = { module = "androidx.core:core-ktx", version.ref = "coreKtx" }
|
||||
core-splashscreen = { module = "androidx.core:core-splashscreen", version.ref = "coreSplashscreen" }
|
||||
espresso-core = { module = "androidx.test.espresso:espresso-core", version.ref = "espressoCore" }
|
||||
ext-junit = { module = "androidx.test.ext:junit", version.ref = "junitVersion" }
|
||||
firebase-bom = { module = "com.google.firebase:firebase-bom", version.ref = "firebaseBom" }
|
||||
firebase-messaging = { module = "com.google.firebase:firebase-messaging", version.ref = "firebaseMessaging" }
|
||||
jna = { module = "net.java.dev.jna:jna", version.ref = "jna" }
|
||||
junit = { module = "junit:junit", version.ref = "junit" }
|
||||
lifecycle-runtime-compose = { module = "androidx.lifecycle:lifecycle-runtime-compose", version.ref = "lifecycle_version" }
|
||||
@@ -76,7 +70,6 @@ jetbrainsKotlinAndroid = { id = "org.jetbrains.kotlin.android", version.ref = "k
|
||||
androidLibrary = { id = "com.android.library", version.ref = "agp" }
|
||||
androidApplication = { id = "com.android.application", version.ref = "agp" }
|
||||
gradle_ktlint = { id = "org.jlleitschuh.gradle.ktlint", version = "ktlint" }
|
||||
google_gms = { id = "com.google.gms.google-services", version = "gms" }
|
||||
serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
|
||||
kotlin_ksp = { id = "com.google.devtools.ksp", version = "ksp" }
|
||||
jetbrainsComposeCompiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
|
||||
|
||||
Reference in New Issue
Block a user