diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedOff/login/ExternalSignerButton.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedOff/login/ExternalSignerButton.kt
index 4285dd087..6280c09bd 100644
--- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedOff/login/ExternalSignerButton.kt
+++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedOff/login/ExternalSignerButton.kt
@@ -23,12 +23,39 @@ package com.vitorpamplona.amethyst.ui.screen.loggedOff.login
import android.app.Activity
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.items
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.Surface
+import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.TextFieldValue
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import androidx.compose.ui.window.Dialog
+import androidx.core.graphics.drawable.toBitmap
+import coil3.compose.rememberAsyncImagePainter
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.DefaultSignerPermissions
import com.vitorpamplona.amethyst.ui.theme.Size0dp
@@ -37,6 +64,7 @@ import com.vitorpamplona.amethyst.ui.theme.Size40dp
import com.vitorpamplona.quartz.nip55AndroidSigner.api.PubKeyResult
import com.vitorpamplona.quartz.nip55AndroidSigner.api.SignerResult
import com.vitorpamplona.quartz.nip55AndroidSigner.client.ExternalSignerLogin
+import com.vitorpamplona.quartz.nip55AndroidSigner.client.getExternalSignersInstalled
import com.vitorpamplona.quartz.utils.Log
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.launch
@@ -44,6 +72,9 @@ import kotlinx.coroutines.launch
@Composable
fun ExternalSignerButton(loginViewModel: LoginViewModel) {
val scope = rememberCoroutineScope()
+ val context = LocalContext.current
+ val installedSigners = getExternalSignersInstalled(context)
+ var shouldSelectSigner by remember { mutableStateOf(false) }
val launcher =
rememberLauncherForActivityResult(
@@ -63,6 +94,81 @@ fun ExternalSignerButton(loginViewModel: LoginViewModel) {
}
}
+ if (shouldSelectSigner) {
+ Dialog(
+ onDismissRequest = {
+ shouldSelectSigner = false
+ },
+ content = {
+ Surface(
+ shape = RoundedCornerShape(4.dp),
+ ) {
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ Text(
+ modifier = Modifier.padding(8.dp),
+ text = stringResource(R.string.select_signer),
+ fontWeight = FontWeight.Bold,
+ fontSize = 24.sp,
+ )
+ Spacer(Modifier.height(4.dp))
+ LazyColumn {
+ items(installedSigners) {
+ val appName = it.loadLabel(context.packageManager).toString()
+ val appIcon = it.loadIcon(context.packageManager)
+ val iconBitmap = appIcon.toBitmap()
+
+ Row(
+ Modifier
+ .fillMaxWidth()
+ .padding(start = 16.dp, end = 16.dp, bottom = 8.dp, top = 8.dp)
+ .clickable {
+ if (!loginViewModel.acceptedTerms) {
+ loginViewModel.termsAcceptanceIsRequiredError = true
+ } else {
+ try {
+ launcher.launch(ExternalSignerLogin.createIntent(DefaultSignerPermissions, it.activityInfo.packageName))
+ } catch (e: Exception) {
+ if (e is CancellationException) throw e
+ Log.e("ExternalSigner", "Error opening Signer app", e)
+ loginViewModel.errorManager.error(R.string.error_opening_external_signer)
+ } finally {
+ shouldSelectSigner = false
+ }
+ }
+ },
+ ) {
+ val painter =
+ rememberAsyncImagePainter(
+ iconBitmap,
+ )
+
+ Image(
+ painter = painter,
+ contentDescription = appName,
+ modifier =
+ Modifier
+ .size(48.dp)
+ .padding(end = 16.dp),
+ )
+ Column {
+ Text(appName)
+ Text(
+ it.activityInfo.packageName,
+ fontSize = 14.sp,
+ color = Color.Gray,
+ )
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ )
+ }
+
Box(modifier = Modifier.padding(Size40dp, Size20dp, Size40dp, Size0dp)) {
LoginWithAmberButton(
enabled = loginViewModel.acceptedTerms,
@@ -71,7 +177,11 @@ fun ExternalSignerButton(loginViewModel: LoginViewModel) {
loginViewModel.termsAcceptanceIsRequiredError = true
} else {
try {
- launcher.launch(ExternalSignerLogin.createIntent(DefaultSignerPermissions))
+ if (installedSigners.size == 1) {
+ launcher.launch(ExternalSignerLogin.createIntent(DefaultSignerPermissions))
+ } else {
+ shouldSelectSigner = true
+ }
} catch (e: Exception) {
if (e is CancellationException) throw e
Log.e("ExternalSigner", "Error opening Signer app", e)
diff --git a/amethyst/src/main/res/values/strings.xml b/amethyst/src/main/res/values/strings.xml
index d5b837e88..9c6f4283c 100644
--- a/amethyst/src/main/res/values/strings.xml
+++ b/amethyst/src/main/res/values/strings.xml
@@ -1289,4 +1289,5 @@
Would you like to send the recent crash report to Amethyst in a DM? No personal information will be shared
Send it
This message will disappear in %1$d days
+ Select Signer
diff --git a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip55AndroidSigner/client/ExternalSignerLogin.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip55AndroidSigner/client/ExternalSignerLogin.kt
index 015c3fc65..2ec43bd09 100644
--- a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip55AndroidSigner/client/ExternalSignerLogin.kt
+++ b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip55AndroidSigner/client/ExternalSignerLogin.kt
@@ -29,9 +29,15 @@ import com.vitorpamplona.quartz.nip55AndroidSigner.api.foreground.intents.result
import com.vitorpamplona.quartz.nip55AndroidSigner.api.permission.Permission
object ExternalSignerLogin {
- fun createIntent(permissions: List = LoginRequest.DefaultPermissions): Intent {
+ fun createIntent(
+ permissions: List = LoginRequest.DefaultPermissions,
+ packageName: String = "",
+ ): Intent {
val intent = LoginRequest.assemble(permissions)
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP)
+ if (packageName.isNotBlank()) {
+ intent.`package` = packageName
+ }
return intent
}
diff --git a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip55AndroidSigner/client/IsExternalSignerInstalled.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip55AndroidSigner/client/IsExternalSignerInstalled.kt
index 92d7f5e48..cef8fa631 100644
--- a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip55AndroidSigner/client/IsExternalSignerInstalled.kt
+++ b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip55AndroidSigner/client/IsExternalSignerInstalled.kt
@@ -23,6 +23,7 @@ package com.vitorpamplona.quartz.nip55AndroidSigner.client
import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
+import android.content.pm.ResolveInfo
import androidx.core.net.toUri
@SuppressLint("QueryPermissionsNeeded")
@@ -35,3 +36,14 @@ fun isExternalSignerInstalled(context: Context): Boolean =
},
0,
).isNotEmpty()
+
+@SuppressLint("QueryPermissionsNeeded")
+fun getExternalSignersInstalled(context: Context): List =
+ context.packageManager
+ .queryIntentActivities(
+ Intent().apply {
+ action = Intent.ACTION_VIEW
+ data = "nostrsigner:".toUri()
+ },
+ 0,
+ )