diff --git a/composeApp/src/androidMain/AndroidManifest.xml b/composeApp/src/androidMain/AndroidManifest.xml
index 89f75cb..c4d8237 100644
--- a/composeApp/src/androidMain/AndroidManifest.xml
+++ b/composeApp/src/androidMain/AndroidManifest.xml
@@ -19,6 +19,12 @@
android:supportsRtl="true"
android:theme="@android:style/Theme.Material.Light.NoActionBar">
+
+
+ Surface(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(innerPadding),
+ color = MaterialTheme.colorScheme.background
+ ) {
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(16.dp),
+ verticalArrangement = Arrangement.spacedBy(16.dp)
+ ) {
+ Column {
+ Text(
+ "App Crashed",
+ style = MaterialTheme.typography.titleMediumEmphasized,
+ color = MaterialTheme.colorScheme.error
+ )
+ Text(
+ "Please copy the log below and send it to the developer.",
+ style = MaterialTheme.typography.bodySmall
+ )
+ }
+ Box(
+ modifier = Modifier
+ .weight(1f)
+ .fillMaxWidth()
+ .verticalScroll(rememberScrollState())
+ ) {
+ Text(
+ text = errorText,
+ fontFamily = FontFamily.Monospace,
+ fontSize = 12.sp
+ )
+ }
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.spacedBy(8.dp)
+ ) {
+ FilledTonalButton(
+ onClick = {
+ finish();
+ exitProcess(0)
+ },
+ modifier = Modifier.weight(1f)
+ ) {
+ Text("Exit")
+ }
+ Button(
+ onClick = {
+ val clipboard =
+ getSystemService(CLIPBOARD_SERVICE) as ClipboardManager
+ val data = ClipData.newPlainText("Crash Log", errorText)
+ clipboard.setPrimaryClip(data)
+ },
+ modifier = Modifier.weight(1f)
+ ) {
+ Text("Copy")
+ }
+ }
+ }
+ }
+ }
+ )
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/composeApp/src/androidMain/kotlin/su/reya/coop/MainActivity.kt b/composeApp/src/androidMain/kotlin/su/reya/coop/MainActivity.kt
index 0f7dffc..58a9af1 100644
--- a/composeApp/src/androidMain/kotlin/su/reya/coop/MainActivity.kt
+++ b/composeApp/src/androidMain/kotlin/su/reya/coop/MainActivity.kt
@@ -11,6 +11,7 @@ import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import su.reya.coop.coop.storage.SecretStore
+import kotlin.system.exitProcess
class MainActivity : ComponentActivity() {
private val viewModel: NostrViewModel by viewModels {
@@ -23,6 +24,26 @@ class MainActivity : ComponentActivity() {
}
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()
enableEdgeToEdge()
diff --git a/composeApp/src/androidMain/kotlin/su/reya/coop/NostrForegroundService.kt b/composeApp/src/androidMain/kotlin/su/reya/coop/NostrForegroundService.kt
index 41e99f8..03a45ee 100644
--- a/composeApp/src/androidMain/kotlin/su/reya/coop/NostrForegroundService.kt
+++ b/composeApp/src/androidMain/kotlin/su/reya/coop/NostrForegroundService.kt
@@ -6,8 +6,10 @@ import android.app.NotificationManager
import android.app.PendingIntent
import android.app.Service
import android.content.Intent
+import android.content.pm.ServiceInfo
import android.os.Build
import android.os.IBinder
+import android.util.Log
import androidx.annotation.RequiresApi
import androidx.core.app.NotificationCompat
import androidx.core.net.toUri
@@ -22,7 +24,7 @@ import java.io.File
class NostrForegroundService : Service() {
private val serviceScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
- private val nostr = NostrManager.instance
+ private val nostr by lazy { NostrManager.instance }
override fun onBind(intent: Intent?): IBinder? = null
@@ -30,18 +32,30 @@ class NostrForegroundService : Service() {
return ProcessLifecycleOwner.get().lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)
}
- override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
+ override fun onCreate() {
+ super.onCreate()
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
createNotificationChannel()
}
-
val notification = createNotification()
- startForeground(1, notification)
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
+ startForeground(1, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC)
+ } else {
+ startForeground(1, notification)
+ }
+ }
+
+ override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
serviceScope.launch {
try {
+ Log.d("Coop", "Starting Nostr in background")
+
+ // Create a database directory
val dbDir = File(filesDir, "nostr")
dbDir.mkdirs()
+
// Initialize Nostr client
nostr.init(dbDir.absolutePath)
// Connect to bootstrap relays
@@ -67,10 +81,9 @@ class NostrForegroundService : Service() {
}
)
} catch (e: Exception) {
- println("Failed to start Nostr in background: ${e.message}")
+ Log.e("Coop", "Failed to start Nostr", e)
}
}
-
return START_STICKY
}
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index a11816a..177bed6 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -1,8 +1,8 @@
[versions]
agp = "9.2.1"
-android-compileSdk = "36"
+android-compileSdk = "37"
android-minSdk = "24"
-android-targetSdk = "36"
+android-targetSdk = "37"
androidx-activity = "1.13.0"
androidx-appcompat = "1.7.1"
androidx-core = "1.18.0"