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.androidApplication)
|
||||||
alias(libs.plugins.jetbrainsKotlinAndroid)
|
alias(libs.plugins.jetbrainsKotlinAndroid)
|
||||||
alias(libs.plugins.gradle.ktlint) version(libs.versions.ktlint)
|
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.kotlin.ksp) version(libs.versions.ksp)
|
||||||
alias(libs.plugins.jetbrainsComposeCompiler)
|
alias(libs.plugins.jetbrainsComposeCompiler)
|
||||||
}
|
}
|
||||||
@@ -58,7 +57,6 @@ android {
|
|||||||
'ku-rTR',
|
'ku-rTR',
|
||||||
'lt-rLT',
|
'lt-rLT',
|
||||||
'ne-rNP',
|
'ne-rNP',
|
||||||
'night',
|
|
||||||
'nl',
|
'nl',
|
||||||
'nl-rBE',
|
'nl-rBE',
|
||||||
'pcm-rNG',
|
'pcm-rNG',
|
||||||
@@ -131,11 +129,8 @@ android {
|
|||||||
flavorDimensions += "version"
|
flavorDimensions += "version"
|
||||||
|
|
||||||
productFlavors {
|
productFlavors {
|
||||||
create("play") {
|
|
||||||
getIsDefault().set(true)
|
|
||||||
dimension = "version"
|
|
||||||
}
|
|
||||||
create("free") {
|
create("free") {
|
||||||
|
getIsDefault().set(true)
|
||||||
dimension = "version"
|
dimension = "version"
|
||||||
}
|
}
|
||||||
create("offline") {
|
create("offline") {
|
||||||
@@ -241,12 +236,8 @@ dependencies {
|
|||||||
// Biometrics
|
// Biometrics
|
||||||
implementation libs.biometric.ktx
|
implementation libs.biometric.ktx
|
||||||
|
|
||||||
playImplementation platform(libs.firebase.bom)
|
|
||||||
playImplementation(libs.firebase.messaging)
|
|
||||||
freeImplementation(libs.unfiedpush)
|
|
||||||
ksp(libs.room.compiler)
|
ksp(libs.room.compiler)
|
||||||
implementation(libs.room.ktx)
|
implementation(libs.room.ktx)
|
||||||
|
|
||||||
freeImplementation(libs.okhttp)
|
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>
|
</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
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:exported="true">
|
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.Account
|
||||||
import com.greenart7c3.nostrsigner.models.AmberSettings
|
import com.greenart7c3.nostrsigner.models.AmberSettings
|
||||||
import com.greenart7c3.nostrsigner.ui.parseBiometricsTimeType
|
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.COMMON_FEED_TYPES
|
||||||
import com.vitorpamplona.ammolite.relays.RelaySetupInfo
|
import com.vitorpamplona.ammolite.relays.RelaySetupInfo
|
||||||
import com.vitorpamplona.ammolite.service.HttpClientManager
|
import com.vitorpamplona.ammolite.service.HttpClientManager
|
||||||
@@ -116,7 +115,6 @@ object LocalPreferences {
|
|||||||
putStringSet(PrefKeys.DEFAULT_RELAYS, settings.defaultRelays.map { it.url }.toSet())
|
putStringSet(PrefKeys.DEFAULT_RELAYS, settings.defaultRelays.map { it.url }.toSet())
|
||||||
putLong(PrefKeys.LAST_BIOMETRICS_TIME, settings.lastBiometricsTime)
|
putLong(PrefKeys.LAST_BIOMETRICS_TIME, settings.lastBiometricsTime)
|
||||||
putBoolean(PrefKeys.USE_AUTH, settings.useAuth)
|
putBoolean(PrefKeys.USE_AUTH, settings.useAuth)
|
||||||
putInt(PrefKeys.NOTIFICATION_TYPE, settings.notificationType.screenCode)
|
|
||||||
putInt(PrefKeys.BIOMETRICS_TYPE, settings.biometricsTimeType.screenCode)
|
putInt(PrefKeys.BIOMETRICS_TYPE, settings.biometricsTimeType.screenCode)
|
||||||
putBoolean(PrefKeys.USE_PIN, settings.usePin)
|
putBoolean(PrefKeys.USE_PIN, settings.usePin)
|
||||||
}.apply()
|
}.apply()
|
||||||
@@ -156,7 +154,6 @@ object LocalPreferences {
|
|||||||
} ?: listOf(RelaySetupInfo("wss://relay.nsec.app", read = true, write = true, feedTypes = COMMON_FEED_TYPES)),
|
} ?: listOf(RelaySetupInfo("wss://relay.nsec.app", read = true, write = true, feedTypes = COMMON_FEED_TYPES)),
|
||||||
lastBiometricsTime = getLong(PrefKeys.LAST_BIOMETRICS_TIME, 0),
|
lastBiometricsTime = getLong(PrefKeys.LAST_BIOMETRICS_TIME, 0),
|
||||||
useAuth = getBoolean(PrefKeys.USE_AUTH, false),
|
useAuth = getBoolean(PrefKeys.USE_AUTH, false),
|
||||||
notificationType = parseNotificationType(getInt(PrefKeys.NOTIFICATION_TYPE, 1)),
|
|
||||||
biometricsTimeType = parseBiometricsTimeType(getInt(PrefKeys.BIOMETRICS_TYPE, 0)),
|
biometricsTimeType = parseBiometricsTimeType(getInt(PrefKeys.BIOMETRICS_TYPE, 0)),
|
||||||
usePin = getBoolean(PrefKeys.USE_PIN, false),
|
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.models.AmberSettings
|
||||||
import com.greenart7c3.nostrsigner.service.ConnectivityService
|
import com.greenart7c3.nostrsigner.service.ConnectivityService
|
||||||
import com.greenart7c3.nostrsigner.service.RelayDisconnectService
|
import com.greenart7c3.nostrsigner.service.RelayDisconnectService
|
||||||
import com.greenart7c3.nostrsigner.ui.NotificationType
|
|
||||||
import com.vitorpamplona.ammolite.relays.Client
|
import com.vitorpamplona.ammolite.relays.Client
|
||||||
import com.vitorpamplona.ammolite.relays.Relay
|
|
||||||
import com.vitorpamplona.ammolite.relays.RelayPool
|
import com.vitorpamplona.ammolite.relays.RelayPool
|
||||||
import com.vitorpamplona.ammolite.relays.RelaySetupInfo
|
import com.vitorpamplona.ammolite.relays.RelaySetupInfo
|
||||||
import com.vitorpamplona.ammolite.relays.RelaySetupInfoToConnect
|
import com.vitorpamplona.ammolite.relays.RelaySetupInfoToConnect
|
||||||
@@ -122,28 +120,11 @@ class NostrSigner : Application() {
|
|||||||
LocalPreferences.loadFromEncryptedStorage(this, it.npub)?.useProxy ?: false
|
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) {
|
if (shouldReconnect) {
|
||||||
checkIfRelaysAreConnected()
|
checkIfRelaysAreConnected()
|
||||||
}
|
}
|
||||||
@Suppress("KotlinConstantConditions")
|
@Suppress("KotlinConstantConditions")
|
||||||
if (settings.notificationType == NotificationType.DIRECT && BuildConfig.FLAVOR != "offline" && savedRelays.isNotEmpty()) {
|
if (BuildConfig.FLAVOR != "offline" && savedRelays.isNotEmpty()) {
|
||||||
Client.reconnect(
|
Client.reconnect(
|
||||||
savedRelays.map { RelaySetupInfoToConnect(it.url, if (isPrivateIp(it.url)) false else useProxy, it.read, it.write, it.feedTypes) }.toTypedArray(),
|
savedRelays.map { RelaySetupInfoToConnect(it.url, if (isPrivateIp(it.url)) false else useProxy, it.read, it.write, it.feedTypes) }.toTypedArray(),
|
||||||
true,
|
true,
|
||||||
|
|||||||
@@ -26,6 +26,9 @@ interface ApplicationDao {
|
|||||||
@Query("SELECT * FROM application where pubKey = :pubKey order by name")
|
@Query("SELECT * FROM application where pubKey = :pubKey order by name")
|
||||||
fun getAll(pubKey: String): List<ApplicationEntity>
|
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")
|
@Query("SELECT * FROM application WHERE `key` = :key")
|
||||||
@Transaction
|
@Transaction
|
||||||
fun getByKey(key: String): ApplicationWithPermissions?
|
fun getByKey(key: String): ApplicationWithPermissions?
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package com.greenart7c3.nostrsigner.models
|
package com.greenart7c3.nostrsigner.models
|
||||||
|
|
||||||
import com.greenart7c3.nostrsigner.ui.BiometricsTimeType
|
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.COMMON_FEED_TYPES
|
||||||
import com.vitorpamplona.ammolite.relays.RelaySetupInfo
|
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 defaultRelays: List<RelaySetupInfo> = listOf(RelaySetupInfo("wss://relay.nsec.app", read = true, write = true, feedTypes = COMMON_FEED_TYPES)),
|
||||||
val lastBiometricsTime: Long = 0,
|
val lastBiometricsTime: Long = 0,
|
||||||
val useAuth: Boolean = false,
|
val useAuth: Boolean = false,
|
||||||
val notificationType: NotificationType = NotificationType.DIRECT,
|
|
||||||
val biometricsTimeType: BiometricsTimeType = BiometricsTimeType.EVERY_TIME,
|
val biometricsTimeType: BiometricsTimeType = BiometricsTimeType.EVERY_TIME,
|
||||||
val usePin: Boolean = false,
|
val usePin: Boolean = false,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -13,10 +13,8 @@ import android.os.IBinder
|
|||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
import com.greenart7c3.nostrsigner.BuildConfig
|
import com.greenart7c3.nostrsigner.BuildConfig
|
||||||
import com.greenart7c3.nostrsigner.LocalPreferences
|
|
||||||
import com.greenart7c3.nostrsigner.NostrSigner
|
import com.greenart7c3.nostrsigner.NostrSigner
|
||||||
import com.greenart7c3.nostrsigner.R
|
import com.greenart7c3.nostrsigner.R
|
||||||
import com.greenart7c3.nostrsigner.ui.NotificationType
|
|
||||||
import com.vitorpamplona.ammolite.relays.RelayPool
|
import com.vitorpamplona.ammolite.relays.RelayPool
|
||||||
import java.util.Timer
|
import java.util.Timer
|
||||||
import java.util.TimerTask
|
import java.util.TimerTask
|
||||||
@@ -40,7 +38,7 @@ class ConnectivityService : Service() {
|
|||||||
@Suppress("KotlinConstantConditions")
|
@Suppress("KotlinConstantConditions")
|
||||||
if (BuildConfig.FLAVOR == "offline") return
|
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) {
|
scope.launch(Dispatchers.IO) {
|
||||||
NotificationDataSource.stopSync()
|
NotificationDataSource.stopSync()
|
||||||
delay(1000)
|
delay(1000)
|
||||||
@@ -66,7 +64,7 @@ class ConnectivityService : Service() {
|
|||||||
"ServiceManager NetworkCallback",
|
"ServiceManager NetworkCallback",
|
||||||
"onCapabilitiesChanged: ${network.networkHandle} hasMobileData ${NostrSigner.getInstance().isOnMobileDataState.value} hasWifi ${NostrSigner.getInstance().isOnWifiDataState.value}",
|
"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()
|
NotificationDataSource.stopSync()
|
||||||
delay(1000)
|
delay(1000)
|
||||||
NotificationDataSource.start()
|
NotificationDataSource.start()
|
||||||
@@ -101,11 +99,6 @@ class ConnectivityService : Service() {
|
|||||||
isStarted = true
|
isStarted = true
|
||||||
startForeground(1, createNotification())
|
startForeground(1, createNotification())
|
||||||
|
|
||||||
scope.launch(Dispatchers.IO) {
|
|
||||||
PushNotificationUtils.hasInit = false
|
|
||||||
PushNotificationUtils.init(LocalPreferences.allSavedAccounts(this@ConnectivityService))
|
|
||||||
}
|
|
||||||
|
|
||||||
@Suppress("KotlinConstantConditions")
|
@Suppress("KotlinConstantConditions")
|
||||||
if (BuildConfig.FLAVOR != "offline") {
|
if (BuildConfig.FLAVOR != "offline") {
|
||||||
val connectivityManager =
|
val connectivityManager =
|
||||||
@@ -119,9 +112,11 @@ class ConnectivityService : Service() {
|
|||||||
timer.schedule(
|
timer.schedule(
|
||||||
object : TimerTask() {
|
object : TimerTask() {
|
||||||
override fun run() {
|
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 {
|
RelayPool.getAll().forEach {
|
||||||
if (!it.isConnected()) {
|
if (!it.isConnected()) {
|
||||||
Log.d(
|
Log.d(
|
||||||
|
|||||||
@@ -42,13 +42,10 @@ import com.greenart7c3.nostrsigner.models.Permission
|
|||||||
import com.greenart7c3.nostrsigner.models.SignerType
|
import com.greenart7c3.nostrsigner.models.SignerType
|
||||||
import com.greenart7c3.nostrsigner.service.NotificationUtils.sendNotification
|
import com.greenart7c3.nostrsigner.service.NotificationUtils.sendNotification
|
||||||
import com.greenart7c3.nostrsigner.service.model.AmberEvent
|
import com.greenart7c3.nostrsigner.service.model.AmberEvent
|
||||||
import com.greenart7c3.nostrsigner.ui.NotificationType
|
|
||||||
import com.vitorpamplona.quartz.crypto.nip04.Nip04
|
import com.vitorpamplona.quartz.crypto.nip04.Nip04
|
||||||
import com.vitorpamplona.quartz.encoders.Hex
|
import com.vitorpamplona.quartz.encoders.Hex
|
||||||
import com.vitorpamplona.quartz.encoders.toNpub
|
import com.vitorpamplona.quartz.encoders.toNpub
|
||||||
import com.vitorpamplona.quartz.events.Event
|
import com.vitorpamplona.quartz.events.Event
|
||||||
import com.vitorpamplona.quartz.events.GiftWrapEvent
|
|
||||||
import com.vitorpamplona.quartz.events.SealedGossipEvent
|
|
||||||
|
|
||||||
class EventNotificationConsumer(private val applicationContext: Context) {
|
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(
|
private fun notify(
|
||||||
event: Event,
|
event: Event,
|
||||||
acc: Account,
|
acc: Account,
|
||||||
|
|||||||
@@ -34,7 +34,6 @@ import com.greenart7c3.nostrsigner.models.TimeUtils
|
|||||||
import com.greenart7c3.nostrsigner.models.containsNip
|
import com.greenart7c3.nostrsigner.models.containsNip
|
||||||
import com.greenart7c3.nostrsigner.relays.AmberListenerSingleton
|
import com.greenart7c3.nostrsigner.relays.AmberListenerSingleton
|
||||||
import com.greenart7c3.nostrsigner.service.model.AmberEvent
|
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.COMMON_FEED_TYPES
|
||||||
import com.vitorpamplona.ammolite.relays.Client
|
import com.vitorpamplona.ammolite.relays.Client
|
||||||
import com.vitorpamplona.ammolite.relays.RelayPool
|
import com.vitorpamplona.ammolite.relays.RelayPool
|
||||||
@@ -388,15 +387,11 @@ object IntentUtils {
|
|||||||
GlobalScope.launch(Dispatchers.IO) {
|
GlobalScope.launch(Dispatchers.IO) {
|
||||||
if (RelayPool.getAll().any { !it.isConnected() }) {
|
if (RelayPool.getAll().any { !it.isConnected() }) {
|
||||||
NostrSigner.getInstance().checkForNewRelays(
|
NostrSigner.getInstance().checkForNewRelays(
|
||||||
NostrSigner.getInstance().settings.notificationType != NotificationType.DIRECT,
|
|
||||||
newRelays = relays.toSet(),
|
newRelays = relays.toSet(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val success = Client.sendAndWaitForResponse(it, relayList = relays)
|
val success = Client.sendAndWaitForResponse(it, relayList = relays)
|
||||||
if (NostrSigner.getInstance().settings.notificationType != NotificationType.DIRECT) {
|
|
||||||
RelayPool.unregister(Client)
|
|
||||||
}
|
|
||||||
if (success) {
|
if (success) {
|
||||||
onDone()
|
onDone()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ import com.greenart7c3.nostrsigner.NostrSigner
|
|||||||
import com.greenart7c3.nostrsigner.checkNotInMainThread
|
import com.greenart7c3.nostrsigner.checkNotInMainThread
|
||||||
import com.greenart7c3.nostrsigner.database.LogEntity
|
import com.greenart7c3.nostrsigner.database.LogEntity
|
||||||
import com.greenart7c3.nostrsigner.models.TimeUtils
|
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.COMMON_FEED_TYPES
|
||||||
import com.vitorpamplona.ammolite.relays.Client
|
import com.vitorpamplona.ammolite.relays.Client
|
||||||
import com.vitorpamplona.ammolite.relays.NostrDataSource
|
import com.vitorpamplona.ammolite.relays.NostrDataSource
|
||||||
@@ -143,9 +142,7 @@ object NotificationDataSource : NostrDataSource("AccountData") {
|
|||||||
init {
|
init {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
LocalPreferences.loadSettingsFromEncryptedStorage()
|
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.PaddingValues
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
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.layout.width
|
||||||
import androidx.compose.foundation.text.selection.SelectionContainer
|
import androidx.compose.foundation.text.selection.SelectionContainer
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
@@ -19,10 +17,7 @@ import androidx.compose.material3.AlertDialog
|
|||||||
import androidx.compose.material3.Button
|
import androidx.compose.material3.Button
|
||||||
import androidx.compose.material3.ButtonColors
|
import androidx.compose.material3.ButtonColors
|
||||||
import androidx.compose.material3.ButtonDefaults
|
import androidx.compose.material3.ButtonDefaults
|
||||||
import androidx.compose.material3.CenterAlignedTopAppBar
|
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.Scaffold
|
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
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.ConnectivityService
|
||||||
import com.greenart7c3.nostrsigner.service.IntentUtils
|
import com.greenart7c3.nostrsigner.service.IntentUtils
|
||||||
import com.greenart7c3.nostrsigner.service.NotificationDataSource
|
import com.greenart7c3.nostrsigner.service.NotificationDataSource
|
||||||
import com.greenart7c3.nostrsigner.service.PushNotificationUtils
|
|
||||||
import com.vitorpamplona.quartz.encoders.toNpub
|
import com.vitorpamplona.quartz.encoders.toNpub
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
|
||||||
@SuppressLint("StateFlowValueCalledInComposition", "UnrememberedMutableState")
|
@SuppressLint("StateFlowValueCalledInComposition", "UnrememberedMutableState")
|
||||||
@Composable
|
@Composable
|
||||||
fun AccountScreen(
|
fun AccountScreen(
|
||||||
@@ -82,115 +75,69 @@ fun AccountScreen(
|
|||||||
MainLoginPage(accountStateViewModel)
|
MainLoginPage(accountStateViewModel)
|
||||||
}
|
}
|
||||||
is AccountState.LoggedIn -> {
|
is AccountState.LoggedIn -> {
|
||||||
var isLoading by remember { mutableStateOf(true) }
|
var intentData by remember {
|
||||||
var isNotificationsConfigured by remember {
|
mutableStateOf<IntentData?>(null)
|
||||||
mutableStateOf(false)
|
|
||||||
}
|
}
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(intent, intents) {
|
||||||
launch(Dispatchers.IO) {
|
IntentUtils.getIntentData(context, intent, packageName, intent.getStringExtra("route"), state.account) {
|
||||||
isNotificationsConfigured = LocalPreferences.isNotificationTypeConfigured()
|
intentData = it
|
||||||
isLoading = false
|
}
|
||||||
|
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) {
|
if (intentData != null) {
|
||||||
Scaffold { innerPadding ->
|
if (intents.none { item -> item.id == intentData!!.id }) {
|
||||||
CenterCircularProgressIndicator(
|
val oldIntents = intents.toMutableList()
|
||||||
Modifier
|
oldIntents.add(intentData!!)
|
||||||
.fillMaxSize()
|
flow.value = oldIntents
|
||||||
.padding(innerPadding),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
if (isNotificationsConfigured) {
|
|
||||||
var intentData by remember {
|
val newIntents =
|
||||||
mutableStateOf<IntentData?>(null)
|
intents.ifEmpty {
|
||||||
|
if (intentData == null) {
|
||||||
|
listOf()
|
||||||
|
} else {
|
||||||
|
listOf(intentData)
|
||||||
}
|
}
|
||||||
LaunchedEffect(intent, intents) {
|
}.mapNotNull { it }
|
||||||
IntentUtils.getIntentData(context, intent, packageName, intent.getStringExtra("route"), state.account) {
|
val database = NostrSigner.getInstance().getDatabase(state.account.signer.keyPair.pubKey.toNpub())
|
||||||
intentData = it
|
val localRoute = mutableStateOf(newIntents.firstNotNullOfOrNull { it.route } ?: state.route)
|
||||||
}
|
|
||||||
val data =
|
|
||||||
intents.firstOrNull {
|
|
||||||
it.currentAccount.isNotBlank()
|
|
||||||
}
|
|
||||||
|
|
||||||
data?.bunkerRequest?.let {
|
SideEffect {
|
||||||
if (it.currentAccount.isNotBlank()) {
|
NostrSigner.getInstance().applicationIOScope.launch(Dispatchers.IO) {
|
||||||
if (LocalPreferences.currentAccount(context) != it.currentAccount) {
|
try {
|
||||||
accountStateViewModel.switchUser(it.currentAccount, null)
|
NostrSigner.getInstance().applicationContext.startForegroundService(
|
||||||
}
|
Intent(NostrSigner.getInstance().applicationContext, ConnectivityService::class.java),
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
} 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,
|
it.permissions,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
if (NostrSigner.getInstance().settings.notificationType == NotificationType.DIRECT) {
|
|
||||||
NostrSigner.getInstance().checkForNewRelays()
|
NostrSigner.getInstance().checkForNewRelays()
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -203,9 +202,7 @@ fun EditConfigurationScreen(
|
|||||||
it.permissions,
|
it.permissions,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
if (NostrSigner.getInstance().settings.notificationType == NotificationType.DIRECT) {
|
NostrSigner.getInstance().checkForNewRelays()
|
||||||
NostrSigner.getInstance().checkForNewRelays()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -255,9 +252,7 @@ fun EditConfigurationScreen(
|
|||||||
it.permissions,
|
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) {
|
scope.launch(Dispatchers.IO) {
|
||||||
database.applicationDao().delete(it.application)
|
database.applicationDao().delete(it.application)
|
||||||
|
|
||||||
if (NostrSigner.getInstance().settings.notificationType == NotificationType.DIRECT) {
|
NostrSigner.getInstance().checkForNewRelays()
|
||||||
NostrSigner.getInstance().checkForNewRelays()
|
|
||||||
}
|
|
||||||
|
|
||||||
scope.launch(Dispatchers.Main) {
|
scope.launch(Dispatchers.Main) {
|
||||||
navController.navigate(Route.Applications.route) {
|
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.models.basicPermissions
|
||||||
import com.greenart7c3.nostrsigner.service.EventNotificationConsumer
|
import com.greenart7c3.nostrsigner.service.EventNotificationConsumer
|
||||||
import com.greenart7c3.nostrsigner.service.IntentUtils
|
import com.greenart7c3.nostrsigner.service.IntentUtils
|
||||||
import com.greenart7c3.nostrsigner.service.PushNotificationUtils
|
|
||||||
import com.greenart7c3.nostrsigner.service.getAppCompatActivity
|
import com.greenart7c3.nostrsigner.service.getAppCompatActivity
|
||||||
import com.greenart7c3.nostrsigner.ui.actions.AccountBackupScreen
|
import com.greenart7c3.nostrsigner.ui.actions.AccountBackupScreen
|
||||||
import com.greenart7c3.nostrsigner.ui.actions.AccountsBottomSheet
|
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)
|
val relays = savedApplication?.application?.relays?.ifEmpty { defaultRelays } ?: (intentData.bunkerRequest?.relays?.ifEmpty { defaultRelays } ?: defaultRelays)
|
||||||
if (intentData.bunkerRequest != null) {
|
if (intentData.bunkerRequest != null) {
|
||||||
NostrSigner.getInstance().checkForNewRelays(
|
NostrSigner.getInstance().checkForNewRelays(
|
||||||
NostrSigner.getInstance().settings.notificationType != NotificationType.DIRECT,
|
newRelays = relays.toSet(),
|
||||||
relays.toSet(),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -259,10 +257,6 @@ fun sendResult(
|
|||||||
true,
|
true,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
PushNotificationUtils.hasInit = false
|
|
||||||
GlobalScope.launch(Dispatchers.IO) {
|
|
||||||
PushNotificationUtils.init(LocalPreferences.allSavedAccounts(context))
|
|
||||||
}
|
|
||||||
|
|
||||||
EventNotificationConsumer(context).notificationManager().cancelAll()
|
EventNotificationConsumer(context).notificationManager().cancelAll()
|
||||||
activity?.intent = null
|
activity?.intent = null
|
||||||
@@ -375,9 +369,6 @@ private suspend fun askNotificationPermission(
|
|||||||
onShouldShowRequestPermissionRationale: () -> Unit,
|
onShouldShowRequestPermissionRationale: () -> Unit,
|
||||||
) {
|
) {
|
||||||
if (ContextCompat.checkSelfPermission(context, "android.permission.POST_NOTIFICATIONS") == PackageManager.PERMISSION_GRANTED) {
|
if (ContextCompat.checkSelfPermission(context, "android.permission.POST_NOTIFICATIONS") == PackageManager.PERMISSION_GRANTED) {
|
||||||
initNotifications(
|
|
||||||
context = context,
|
|
||||||
)
|
|
||||||
return
|
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 {
|
fun Color.Companion.fromHex(colorString: String) = try {
|
||||||
Color(android.graphics.Color.parseColor("#$colorString"))
|
Color(android.graphics.Color.parseColor("#$colorString"))
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
@@ -431,13 +417,7 @@ fun MainScreen(
|
|||||||
rememberLauncherForActivityResult(
|
rememberLauncherForActivityResult(
|
||||||
ActivityResultContracts.RequestPermission(),
|
ActivityResultContracts.RequestPermission(),
|
||||||
) { isGranted: Boolean ->
|
) { isGranted: Boolean ->
|
||||||
if (isGranted) {
|
if (!isGranted) {
|
||||||
scope.launch(Dispatchers.IO) {
|
|
||||||
initNotifications(
|
|
||||||
context = context,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (LocalPreferences.shouldShowRationale(context) == null) {
|
if (LocalPreferences.shouldShowRationale(context) == null) {
|
||||||
LocalPreferences.updateShouldShowRationale(context, true)
|
LocalPreferences.updateShouldShowRationale(context, true)
|
||||||
}
|
}
|
||||||
@@ -682,7 +662,6 @@ fun MainScreen(
|
|||||||
.padding(padding)
|
.padding(padding)
|
||||||
.padding(24.dp),
|
.padding(24.dp),
|
||||||
account = account,
|
account = account,
|
||||||
accountStateViewModel = accountStateViewModel,
|
|
||||||
navController = navController,
|
navController = navController,
|
||||||
database = database,
|
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(
|
composable(
|
||||||
Route.DefaultRelays.route,
|
Route.DefaultRelays.route,
|
||||||
content = {
|
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.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import androidx.navigation.NavController
|
import androidx.navigation.NavController
|
||||||
import com.greenart7c3.nostrsigner.LocalPreferences
|
|
||||||
import com.greenart7c3.nostrsigner.R
|
import com.greenart7c3.nostrsigner.R
|
||||||
import com.greenart7c3.nostrsigner.database.AppDatabase
|
import com.greenart7c3.nostrsigner.database.AppDatabase
|
||||||
import com.greenart7c3.nostrsigner.database.ApplicationEntity
|
|
||||||
import com.greenart7c3.nostrsigner.models.Account
|
import com.greenart7c3.nostrsigner.models.Account
|
||||||
import com.greenart7c3.nostrsigner.service.toShortenHex
|
import com.greenart7c3.nostrsigner.service.toShortenHex
|
||||||
import com.vitorpamplona.quartz.encoders.toHexKey
|
import com.vitorpamplona.quartz.encoders.toHexKey
|
||||||
import com.vitorpamplona.quartz.encoders.toNpub
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun PermissionsScreen(
|
fun PermissionsScreen(
|
||||||
modifier: Modifier,
|
modifier: Modifier,
|
||||||
account: Account,
|
account: Account,
|
||||||
accountStateViewModel: AccountStateViewModel,
|
|
||||||
navController: NavController,
|
navController: NavController,
|
||||||
database: AppDatabase,
|
database: AppDatabase,
|
||||||
) {
|
) {
|
||||||
val lifecycleEvent = rememberLifecycleEvent()
|
val applications = database.applicationDao().getAllFlow(account.signer.keyPair.pubKey.toHexKey()).collectAsStateWithLifecycle(emptyList())
|
||||||
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()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier,
|
modifier,
|
||||||
) {
|
) {
|
||||||
if (applications.isEmpty()) {
|
if (applications.value.isEmpty()) {
|
||||||
Column(
|
Column(
|
||||||
Modifier.fillMaxSize(),
|
Modifier.fillMaxSize(),
|
||||||
Arrangement.Center,
|
Arrangement.Center,
|
||||||
@@ -100,13 +78,13 @@ fun PermissionsScreen(
|
|||||||
// )
|
// )
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
items(applications.size) {
|
items(applications.value.size) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.padding(vertical = 4.dp)
|
.padding(vertical = 4.dp)
|
||||||
.clickable {
|
.clickable {
|
||||||
navController.navigate("Permission/${applications.elementAt(it).key}")
|
navController.navigate("Permission/${applications.value.elementAt(it).key}")
|
||||||
},
|
},
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
@@ -115,7 +93,7 @@ fun PermissionsScreen(
|
|||||||
.fillMaxSize(),
|
.fillMaxSize(),
|
||||||
verticalArrangement = Arrangement.Center,
|
verticalArrangement = Arrangement.Center,
|
||||||
) {
|
) {
|
||||||
val localPermission = applications.elementAt(it)
|
val localPermission = applications.value.elementAt(it)
|
||||||
Text(
|
Text(
|
||||||
modifier = Modifier.padding(top = 16.dp),
|
modifier = Modifier.padding(top = 16.dp),
|
||||||
text = localPermission.name.ifBlank { localPermission.key.toShortenHex() },
|
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.Hub
|
||||||
import androidx.compose.material.icons.filled.Key
|
import androidx.compose.material.icons.filled.Key
|
||||||
import androidx.compose.material.icons.filled.Language
|
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.Security
|
||||||
import androidx.compose.material.icons.filled.SurroundSound
|
import androidx.compose.material.icons.filled.SurroundSound
|
||||||
import androidx.compose.material3.AlertDialog
|
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.TextSpinner
|
||||||
import com.greenart7c3.nostrsigner.ui.components.TitleExplainer
|
import com.greenart7c3.nostrsigner.ui.components.TitleExplainer
|
||||||
import com.greenart7c3.nostrsigner.ui.navigation.Route
|
import com.greenart7c3.nostrsigner.ui.navigation.Route
|
||||||
import com.vitorpamplona.ammolite.relays.RelayPool
|
|
||||||
import com.vitorpamplona.quartz.encoders.toNpub
|
import com.vitorpamplona.quartz.encoders.toNpub
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
@@ -144,20 +142,6 @@ fun SettingsScreen(
|
|||||||
|
|
||||||
@Suppress("KotlinConstantConditions")
|
@Suppress("KotlinConstantConditions")
|
||||||
if (BuildConfig.FLAVOR != "offline") {
|
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(
|
Box(
|
||||||
Modifier
|
Modifier
|
||||||
.padding(8.dp),
|
.padding(8.dp),
|
||||||
@@ -182,43 +166,43 @@ fun SettingsScreen(
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
Modifier
|
Modifier
|
||||||
.padding(8.dp),
|
.padding(8.dp),
|
||||||
) {
|
) {
|
||||||
IconRow(
|
IconRow(
|
||||||
title = stringResource(R.string.default_relays),
|
title = stringResource(R.string.default_relays),
|
||||||
icon = Icons.Default.Hub,
|
icon = Icons.Default.Hub,
|
||||||
tint = MaterialTheme.colorScheme.onBackground,
|
tint = MaterialTheme.colorScheme.onBackground,
|
||||||
onClick = {
|
onClick = {
|
||||||
navController.navigate(Route.DefaultRelays.route)
|
navController.navigate(Route.DefaultRelays.route)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
Modifier
|
Modifier
|
||||||
.padding(8.dp),
|
.padding(8.dp),
|
||||||
) {
|
) {
|
||||||
IconRow(
|
IconRow(
|
||||||
title = if (allowNewConnections) stringResource(R.string.disable_listening_for_new_connections) else stringResource(R.string.enable_listening_for_new_connections),
|
title = if (allowNewConnections) stringResource(R.string.disable_listening_for_new_connections) else stringResource(R.string.enable_listening_for_new_connections),
|
||||||
icon = Icons.Default.SurroundSound,
|
icon = Icons.Default.SurroundSound,
|
||||||
tint = MaterialTheme.colorScheme.onBackground,
|
tint = MaterialTheme.colorScheme.onBackground,
|
||||||
onClick = {
|
onClick = {
|
||||||
allowNewConnections = !allowNewConnections
|
allowNewConnections = !allowNewConnections
|
||||||
account.allowNewConnections = allowNewConnections
|
account.allowNewConnections = allowNewConnections
|
||||||
LocalPreferences.saveToEncryptedStorage(context, account)
|
LocalPreferences.saveToEncryptedStorage(context, account)
|
||||||
},
|
},
|
||||||
onLongClick = {
|
onLongClick = {
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
context,
|
context,
|
||||||
if (allowNewConnections) context.getString(R.string.disable_listening_for_new_connections) else context.getString(R.string.enable_listening_for_new_connections),
|
if (allowNewConnections) context.getString(R.string.disable_listening_for_new_connections) else context.getString(R.string.enable_listening_for_new_connections),
|
||||||
Toast.LENGTH_LONG,
|
Toast.LENGTH_LONG,
|
||||||
).show()
|
).show()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
@@ -307,12 +291,8 @@ fun SettingsScreen(
|
|||||||
LocalPreferences.updateProxy(context, true, proxyPort.value.toInt())
|
LocalPreferences.updateProxy(context, true, proxyPort.value.toInt())
|
||||||
scope.launch(Dispatchers.IO) {
|
scope.launch(Dispatchers.IO) {
|
||||||
NotificationDataSource.stopSync()
|
NotificationDataSource.stopSync()
|
||||||
if (NostrSigner.getInstance().settings.notificationType != NotificationType.DIRECT) {
|
NostrSigner.getInstance().checkForNewRelays()
|
||||||
RelayPool.disconnect()
|
NotificationDataSource.start()
|
||||||
} else {
|
|
||||||
NostrSigner.getInstance().checkForNewRelays()
|
|
||||||
NotificationDataSource.start()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onError = {
|
onError = {
|
||||||
@@ -341,12 +321,8 @@ fun SettingsScreen(
|
|||||||
LocalPreferences.updateProxy(context, false, proxyPort.value.toInt())
|
LocalPreferences.updateProxy(context, false, proxyPort.value.toInt())
|
||||||
scope.launch(Dispatchers.IO) {
|
scope.launch(Dispatchers.IO) {
|
||||||
NotificationDataSource.stopSync()
|
NotificationDataSource.stopSync()
|
||||||
if (NostrSigner.getInstance().settings.notificationType != NotificationType.DIRECT) {
|
NostrSigner.getInstance().checkForNewRelays()
|
||||||
RelayPool.disconnect()
|
NotificationDataSource.start()
|
||||||
} else {
|
|
||||||
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) {
|
enum class BiometricsTimeType(val screenCode: Int, val resourceId: Int) {
|
||||||
EVERY_TIME(0, R.string.every_time),
|
EVERY_TIME(0, R.string.every_time),
|
||||||
ONE_MINUTE(1, R.string.one_minute),
|
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.service.NotificationDataSource
|
||||||
import com.greenart7c3.nostrsigner.ui.AccountStateViewModel
|
import com.greenart7c3.nostrsigner.ui.AccountStateViewModel
|
||||||
import com.greenart7c3.nostrsigner.ui.CenterCircularProgressIndicator
|
import com.greenart7c3.nostrsigner.ui.CenterCircularProgressIndicator
|
||||||
import com.greenart7c3.nostrsigner.ui.NotificationType
|
|
||||||
import com.greenart7c3.nostrsigner.ui.components.AmberButton
|
import com.greenart7c3.nostrsigner.ui.components.AmberButton
|
||||||
import com.vitorpamplona.ammolite.relays.COMMON_FEED_TYPES
|
import com.vitorpamplona.ammolite.relays.COMMON_FEED_TYPES
|
||||||
import com.vitorpamplona.ammolite.relays.Client
|
import com.vitorpamplona.ammolite.relays.Client
|
||||||
@@ -149,7 +148,7 @@ fun DefaultRelaysScreen(
|
|||||||
LocalPreferences.saveSettingsToEncryptedStorage(NostrSigner.getInstance().settings)
|
LocalPreferences.saveSettingsToEncryptedStorage(NostrSigner.getInstance().settings)
|
||||||
scope.launch(Dispatchers.IO) {
|
scope.launch(Dispatchers.IO) {
|
||||||
@Suppress("KotlinConstantConditions")
|
@Suppress("KotlinConstantConditions")
|
||||||
if (NostrSigner.getInstance().settings.notificationType == NotificationType.DIRECT && BuildConfig.FLAVOR != "offline") {
|
if (BuildConfig.FLAVOR != "offline") {
|
||||||
NostrSigner.getInstance().checkForNewRelays()
|
NostrSigner.getInstance().checkForNewRelays()
|
||||||
NotificationDataSource.stop()
|
NotificationDataSource.stop()
|
||||||
delay(2000)
|
delay(2000)
|
||||||
@@ -191,7 +190,7 @@ fun DefaultRelaysScreen(
|
|||||||
LocalPreferences.saveSettingsToEncryptedStorage(NostrSigner.getInstance().settings)
|
LocalPreferences.saveSettingsToEncryptedStorage(NostrSigner.getInstance().settings)
|
||||||
scope.launch(Dispatchers.IO) {
|
scope.launch(Dispatchers.IO) {
|
||||||
@Suppress("KotlinConstantConditions")
|
@Suppress("KotlinConstantConditions")
|
||||||
if (NostrSigner.getInstance().settings.notificationType == NotificationType.DIRECT && BuildConfig.FLAVOR != "offline") {
|
if (BuildConfig.FLAVOR != "offline") {
|
||||||
NostrSigner.getInstance().checkForNewRelays()
|
NostrSigner.getInstance().checkForNewRelays()
|
||||||
NotificationDataSource.stop()
|
NotificationDataSource.stop()
|
||||||
delay(2000)
|
delay(2000)
|
||||||
@@ -238,7 +237,7 @@ fun DefaultRelaysScreen(
|
|||||||
LocalPreferences.saveSettingsToEncryptedStorage(NostrSigner.getInstance().settings)
|
LocalPreferences.saveSettingsToEncryptedStorage(NostrSigner.getInstance().settings)
|
||||||
scope.launch(Dispatchers.IO) {
|
scope.launch(Dispatchers.IO) {
|
||||||
@Suppress("KotlinConstantConditions")
|
@Suppress("KotlinConstantConditions")
|
||||||
if (NostrSigner.getInstance().settings.notificationType == NotificationType.DIRECT && BuildConfig.FLAVOR != "offline") {
|
if (BuildConfig.FLAVOR != "offline") {
|
||||||
NostrSigner.getInstance().checkForNewRelays()
|
NostrSigner.getInstance().checkForNewRelays()
|
||||||
NotificationDataSource.stop()
|
NotificationDataSource.stop()
|
||||||
delay(2000)
|
delay(2000)
|
||||||
|
|||||||
@@ -61,7 +61,6 @@ import com.greenart7c3.nostrsigner.service.IntentUtils
|
|||||||
import com.greenart7c3.nostrsigner.service.getAppCompatActivity
|
import com.greenart7c3.nostrsigner.service.getAppCompatActivity
|
||||||
import com.greenart7c3.nostrsigner.service.model.AmberEvent
|
import com.greenart7c3.nostrsigner.service.model.AmberEvent
|
||||||
import com.greenart7c3.nostrsigner.service.toShortenHex
|
import com.greenart7c3.nostrsigner.service.toShortenHex
|
||||||
import com.greenart7c3.nostrsigner.ui.NotificationType
|
|
||||||
import com.greenart7c3.nostrsigner.ui.Result
|
import com.greenart7c3.nostrsigner.ui.Result
|
||||||
import com.vitorpamplona.quartz.crypto.CryptoUtils
|
import com.vitorpamplona.quartz.crypto.CryptoUtils
|
||||||
import com.vitorpamplona.quartz.encoders.toHexKey
|
import com.vitorpamplona.quartz.encoders.toHexKey
|
||||||
@@ -577,7 +576,7 @@ private fun sendResultIntent(
|
|||||||
private suspend fun reconnectToRelays(intents: List<IntentData>) {
|
private suspend fun reconnectToRelays(intents: List<IntentData>) {
|
||||||
if (!intents.any { it.bunkerRequest != null }) return
|
if (!intents.any { it.bunkerRequest != null }) return
|
||||||
|
|
||||||
NostrSigner.getInstance().checkForNewRelays(NostrSigner.getInstance().settings.notificationType != NotificationType.DIRECT)
|
NostrSigner.getInstance().checkForNewRelays()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|||||||
+4
-9
@@ -36,7 +36,6 @@ import com.greenart7c3.nostrsigner.models.kindToNip
|
|||||||
import com.greenart7c3.nostrsigner.service.AmberUtils
|
import com.greenart7c3.nostrsigner.service.AmberUtils
|
||||||
import com.greenart7c3.nostrsigner.service.getAppCompatActivity
|
import com.greenart7c3.nostrsigner.service.getAppCompatActivity
|
||||||
import com.greenart7c3.nostrsigner.service.toShortenHex
|
import com.greenart7c3.nostrsigner.service.toShortenHex
|
||||||
import com.greenart7c3.nostrsigner.ui.NotificationType
|
|
||||||
import com.greenart7c3.nostrsigner.ui.sendResult
|
import com.greenart7c3.nostrsigner.ui.sendResult
|
||||||
import com.vitorpamplona.quartz.crypto.CryptoUtils
|
import com.vitorpamplona.quartz.crypto.CryptoUtils
|
||||||
import com.vitorpamplona.quartz.encoders.bechToBytes
|
import com.vitorpamplona.quartz.encoders.bechToBytes
|
||||||
@@ -138,8 +137,7 @@ fun SingleEventHomeScreen(
|
|||||||
val relays = savedApplication?.application?.relays?.ifEmpty { defaultRelays } ?: intentData.bunkerRequest.relays.ifEmpty { defaultRelays }
|
val relays = savedApplication?.application?.relays?.ifEmpty { defaultRelays } ?: intentData.bunkerRequest.relays.ifEmpty { defaultRelays }
|
||||||
|
|
||||||
NostrSigner.getInstance().checkForNewRelays(
|
NostrSigner.getInstance().checkForNewRelays(
|
||||||
NostrSigner.getInstance().settings.notificationType != NotificationType.DIRECT,
|
newRelays = relays.toSet(),
|
||||||
relays.toSet(),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
AmberUtils.sendBunkerError(
|
AmberUtils.sendBunkerError(
|
||||||
@@ -242,8 +240,7 @@ fun SingleEventHomeScreen(
|
|||||||
|
|
||||||
if (intentData.bunkerRequest != null) {
|
if (intentData.bunkerRequest != null) {
|
||||||
NostrSigner.getInstance().checkForNewRelays(
|
NostrSigner.getInstance().checkForNewRelays(
|
||||||
NostrSigner.getInstance().settings.notificationType != NotificationType.DIRECT,
|
newRelays = relays.toSet(),
|
||||||
relays.toSet(),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -378,8 +375,7 @@ fun SingleEventHomeScreen(
|
|||||||
|
|
||||||
if (intentData.bunkerRequest != null) {
|
if (intentData.bunkerRequest != null) {
|
||||||
NostrSigner.getInstance().checkForNewRelays(
|
NostrSigner.getInstance().checkForNewRelays(
|
||||||
NostrSigner.getInstance().settings.notificationType != NotificationType.DIRECT,
|
newRelays = relays.toSet(),
|
||||||
relays.toSet(),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -528,8 +524,7 @@ fun SingleEventHomeScreen(
|
|||||||
|
|
||||||
if (intentData.bunkerRequest != null) {
|
if (intentData.bunkerRequest != null) {
|
||||||
NostrSigner.getInstance().checkForNewRelays(
|
NostrSigner.getInstance().checkForNewRelays(
|
||||||
NostrSigner.getInstance().settings.notificationType != NotificationType.DIRECT,
|
newRelays = relays.toSet(),
|
||||||
relays.toSet(),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -81,13 +81,6 @@ sealed class Route(
|
|||||||
selectedIcon = Icons.Default.Settings,
|
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(
|
data object DefaultRelays : Route(
|
||||||
title = NostrSigner.getInstance().getString(R.string.default_relays),
|
title = NostrSigner.getInstance().getString(R.string.default_relays),
|
||||||
route = "DefaultRelays",
|
route = "DefaultRelays",
|
||||||
@@ -182,7 +175,6 @@ val routes = listOf(
|
|||||||
Route.Logs,
|
Route.Logs,
|
||||||
Route.ActiveRelays,
|
Route.ActiveRelays,
|
||||||
Route.Language,
|
Route.Language,
|
||||||
Route.NotificationType,
|
|
||||||
Route.DefaultRelays,
|
Route.DefaultRelays,
|
||||||
Route.SignPolicy,
|
Route.SignPolicy,
|
||||||
Route.Security,
|
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.androidLibrary) apply false
|
||||||
alias(libs.plugins.jetbrainsKotlinAndroid) apply false
|
alias(libs.plugins.jetbrainsKotlinAndroid) apply false
|
||||||
alias(libs.plugins.serialization) 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.kotlin.ksp) version(libs.versions.ksp) apply false
|
||||||
alias(libs.plugins.gradle.ktlint) version(libs.versions.ktlint) apply false
|
alias(libs.plugins.gradle.ktlint) version(libs.versions.ktlint) apply false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,15 +10,13 @@ appName=$2
|
|||||||
|
|
||||||
./gradlew clean bundleRelease --stacktrace
|
./gradlew clean bundleRelease --stacktrace
|
||||||
./gradlew assembleRelease --stacktrace
|
./gradlew assembleRelease --stacktrace
|
||||||
rm ~/release/amber-*
|
rm -rf ~/release/amber-*
|
||||||
rm ~/release/citrine-*
|
rm -rf ~/release/citrine-*
|
||||||
rm ~/release/manifest-*
|
rm -rf ~/release/manifest-*
|
||||||
mv app/build/outputs/bundle/playRelease/app-play-release.aab ~/release/
|
|
||||||
mv app/build/outputs/bundle/offlineRelease/app-offline-release.aab ~/release/
|
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/bundle/freeRelease/app-free-release.aab ~/release/
|
||||||
mv app/build/outputs/apk/offline/release/app-offline-* ~/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/free/release/app-free-* ~/release/
|
||||||
mv app/build/outputs/apk/play/release/app-play-* ~/release/
|
|
||||||
./gradlew --stop
|
./gradlew --stop
|
||||||
cd ~/release
|
cd ~/release
|
||||||
./generate_manifest.sh ${version} ${appName}
|
./generate_manifest.sh ${version} ${appName}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
[versions]
|
[versions]
|
||||||
activityCompose = "1.9.3"
|
activityCompose = "1.9.3"
|
||||||
unfiedpush = "2.4.0"
|
|
||||||
appcompat = "1.7.0"
|
appcompat = "1.7.0"
|
||||||
biometricKtx = "1.2.0-alpha05"
|
biometricKtx = "1.2.0-alpha05"
|
||||||
converterGson = "2.11.0"
|
converterGson = "2.11.0"
|
||||||
@@ -8,8 +7,6 @@ core = "3.5.3"
|
|||||||
coreKtx = "1.13.1"
|
coreKtx = "1.13.1"
|
||||||
coreSplashscreen = "1.0.1"
|
coreSplashscreen = "1.0.1"
|
||||||
espressoCore = "3.6.1"
|
espressoCore = "3.6.1"
|
||||||
firebaseBom = "33.5.1"
|
|
||||||
firebaseMessaging = "24.0.3"
|
|
||||||
jna = "5.14.0"
|
jna = "5.14.0"
|
||||||
junit = "4.13.2"
|
junit = "4.13.2"
|
||||||
junitVersion = "1.2.1"
|
junitVersion = "1.2.1"
|
||||||
@@ -33,7 +30,6 @@ ksp = "2.0.20-1.0.24"
|
|||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
activity-compose = { module = "androidx.activity:activity-compose", version.ref = "activityCompose" }
|
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" }
|
appcompat = { module = "androidx.appcompat:appcompat", version.ref = "appcompat" }
|
||||||
biometric-ktx = { module = "androidx.biometric:biometric-ktx", version.ref = "biometricKtx" }
|
biometric-ktx = { module = "androidx.biometric:biometric-ktx", version.ref = "biometricKtx" }
|
||||||
converter-gson = { module = "com.squareup.retrofit2:converter-gson", version.ref = "converterGson" }
|
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" }
|
core-splashscreen = { module = "androidx.core:core-splashscreen", version.ref = "coreSplashscreen" }
|
||||||
espresso-core = { module = "androidx.test.espresso:espresso-core", version.ref = "espressoCore" }
|
espresso-core = { module = "androidx.test.espresso:espresso-core", version.ref = "espressoCore" }
|
||||||
ext-junit = { module = "androidx.test.ext:junit", version.ref = "junitVersion" }
|
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" }
|
jna = { module = "net.java.dev.jna:jna", version.ref = "jna" }
|
||||||
junit = { module = "junit:junit", version.ref = "junit" }
|
junit = { module = "junit:junit", version.ref = "junit" }
|
||||||
lifecycle-runtime-compose = { module = "androidx.lifecycle:lifecycle-runtime-compose", version.ref = "lifecycle_version" }
|
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" }
|
androidLibrary = { id = "com.android.library", version.ref = "agp" }
|
||||||
androidApplication = { id = "com.android.application", version.ref = "agp" }
|
androidApplication = { id = "com.android.application", version.ref = "agp" }
|
||||||
gradle_ktlint = { id = "org.jlleitschuh.gradle.ktlint", version = "ktlint" }
|
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" }
|
serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
|
||||||
kotlin_ksp = { id = "com.google.devtools.ksp", version = "ksp" }
|
kotlin_ksp = { id = "com.google.devtools.ksp", version = "ksp" }
|
||||||
jetbrainsComposeCompiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
|
jetbrainsComposeCompiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
|
||||||
|
|||||||
Reference in New Issue
Block a user