add external signer

This commit is contained in:
2026-06-08 10:16:38 +07:00
parent 50b7f7a3f3
commit 5554421762
5 changed files with 164 additions and 31 deletions

View File

@@ -11,6 +11,14 @@
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<queries>
<intent>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="nostrsigner" />
</intent>
</queries>
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"

View File

@@ -0,0 +1,46 @@
package su.reya.coop
import android.content.Intent
import androidx.activity.ComponentActivity
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.net.toUri
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlin.coroutines.resume
class AndroidExternalSignerLauncher(activity: ComponentActivity) : ExternalSignerLauncher {
private var callback: ((String?) -> Unit)? = null
private val launcher: ActivityResultLauncher<Intent> =
activity.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
val intent = result.data
val result = intent?.getStringExtra("signature")
?: intent?.getStringExtra("public_key")
?: intent?.getStringExtra("content")
?: intent?.dataString
callback?.invoke(result)
callback = null
}
override suspend fun launch(
content: String,
type: String,
pubkey: String?,
id: String?
): String? =
suspendCancellableCoroutine { continuation ->
callback = { continuation.resume(it) }
val intent = Intent(Intent.ACTION_VIEW, "nostrsigner:$content".toUri())
intent.putExtra("type", type)
pubkey?.let { intent.putExtra("pubkey", it) }
id?.let { intent.putExtra("id", it) }
try {
launcher.launch(intent)
} catch (e: Exception) {
callback?.invoke(null)
}
}
}

View File

@@ -2,6 +2,8 @@ package su.reya.coop
import android.content.Intent
import android.os.Bundle
import android.os.Process
import android.util.Log
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
@@ -14,50 +16,28 @@ import su.reya.coop.coop.storage.SecretStore
import kotlin.system.exitProcess
class MainActivity : ComponentActivity() {
private val viewModel: NostrViewModel by viewModels {
object : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
val secretStore = SecretStore(this@MainActivity)
return NostrViewModel(NostrManager.instance, secretStore) as T
}
}
}
private lateinit var externalSignerLauncher: AndroidExternalSignerLauncher
override fun onCreate(savedInstanceState: Bundle?) {
Thread.setDefaultUncaughtExceptionHandler { thread, throwable ->
throwable.printStackTrace()
android.util.Log.e(
"CoopCrash",
"Uncaught exception in thread ${thread.name}",
throwable
)
// Start the Crash Activity
val intent = Intent(this, CrashActivity::class.java).apply {
putExtra("error", throwable.stackTraceToString())
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
}
startActivity(intent)
// Exit
android.os.Process.killProcess(android.os.Process.myPid())
exitProcess(1)
}
val splashScreen = installSplashScreen()
setupCrashHandler()
enableEdgeToEdge()
super.onCreate(savedInstanceState)
val serviceIntent = Intent(this, NostrForegroundService::class.java)
startForegroundService(serviceIntent)
// Initialize the nostr service and external signer
setupExternalSigner()
startNostrService()
// Initialize the ViewModel
val viewModel: NostrViewModel by viewModels { NostrViewModelFactory(this) }
// Keep the splash screen visible until the signer check is complete
splashScreen.setKeepOnScreenCondition {
viewModel.signerRequired.value == null
}
// Bind the lifecycle of the ViewModel to the Activity's lifecycle'
// Bind the lifecycle of the ViewModel to the Activity's lifecycle
viewModel.bindLifecycle(ProcessLifecycleOwner.get().lifecycle)
setContent {
@@ -65,8 +45,44 @@ class MainActivity : ComponentActivity() {
}
}
private fun setupExternalSigner() {
val launcher = AndroidExternalSignerLauncher(this)
ExternalSignerLauncherProvider.launcher = launcher
}
private fun startNostrService() {
val intent = Intent(this, NostrForegroundService::class.java)
startForegroundService(intent)
}
private fun setupCrashHandler() {
Thread.setDefaultUncaughtExceptionHandler { thread, throwable ->
throwable.printStackTrace()
Log.e("CoopCrash", "Uncaught exception in thread ${thread.name}", throwable)
val intent = Intent(this, CrashActivity::class.java).apply {
putExtra("error", throwable.stackTraceToString())
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
}
startActivity(intent)
Process.killProcess(Process.myPid())
exitProcess(1)
}
}
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
setIntent(intent)
}
}
class NostrViewModelFactory(
private val activity: ComponentActivity
) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
val secretStore = SecretStore(activity)
return NostrViewModel(NostrManager.instance, secretStore) as T
}
}