9e2dd40af0
- Connect to the server's /ws multiplexed socket (chat/notifications/board) for the whole authenticated session. Auth rides the existing cookie jar; no server changes needed (the server allows clients that send no Origin header). - Chat now updates live: ConversationViewModel merges messages pushed over the socket for the open conversation (no more leaving/re-entering to see replies). - Notifications are now real-time in-app (bell updates instantly); when the app is backgrounded, incoming alerts and messages raise a system notification via the same socket. The 15-min WorkManager poll remains as a catch-up safety net. - Links are now clickable in chat messages, task comments, and descriptions via a new RichText component (handles <a> anchors and bare http/www URLs). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
71 lines
2.6 KiB
Kotlin
71 lines
2.6 KiB
Kotlin
package com.anypreta.bountyboard
|
|
|
|
import android.Manifest
|
|
import android.content.Intent
|
|
import android.content.pm.PackageManager
|
|
import android.os.Build
|
|
import android.os.Bundle
|
|
import androidx.activity.ComponentActivity
|
|
import androidx.activity.compose.setContent
|
|
import androidx.activity.enableEdgeToEdge
|
|
import androidx.activity.result.contract.ActivityResultContracts
|
|
import androidx.compose.runtime.CompositionLocalProvider
|
|
import androidx.compose.runtime.getValue
|
|
import androidx.core.content.ContextCompat
|
|
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
|
import com.anypreta.bountyboard.data.push.PushNotifier
|
|
import com.anypreta.bountyboard.di.LocalAppContainer
|
|
import com.anypreta.bountyboard.ui.nav.AppRoot
|
|
import com.anypreta.bountyboard.ui.theme.BBTheme
|
|
import com.anypreta.bountyboard.ui.theme.BountyBoardTheme
|
|
|
|
class MainActivity : ComponentActivity() {
|
|
|
|
private val requestNotifPermission =
|
|
registerForActivityResult(ActivityResultContracts.RequestPermission()) { /* no-op */ }
|
|
|
|
override fun onCreate(savedInstanceState: Bundle?) {
|
|
installSplashScreen()
|
|
super.onCreate(savedInstanceState)
|
|
enableEdgeToEdge()
|
|
val container = (application as BountyBoardApp).container
|
|
|
|
// Deep link from a tapped system notification.
|
|
intent?.getStringExtra(PushNotifier.EXTRA_ROUTE)?.let { container.setDeepLink(it) }
|
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU &&
|
|
ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) !=
|
|
PackageManager.PERMISSION_GRANTED
|
|
) {
|
|
requestNotifPermission.launch(Manifest.permission.POST_NOTIFICATIONS)
|
|
}
|
|
|
|
setContent {
|
|
val themeId by container.settings.theme.collectAsStateWithLifecycle(initialValue = "light")
|
|
CompositionLocalProvider(LocalAppContainer provides container) {
|
|
BountyBoardTheme(theme = BBTheme.from(themeId)) {
|
|
AppRoot()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
override fun onNewIntent(intent: Intent) {
|
|
super.onNewIntent(intent)
|
|
setIntent(intent)
|
|
val container = (application as BountyBoardApp).container
|
|
intent.getStringExtra(PushNotifier.EXTRA_ROUTE)?.let { container.setDeepLink(it) }
|
|
}
|
|
|
|
override fun onStart() {
|
|
super.onStart()
|
|
(application as BountyBoardApp).container.appForeground = true
|
|
}
|
|
|
|
override fun onStop() {
|
|
super.onStop()
|
|
(application as BountyBoardApp).container.appForeground = false
|
|
}
|
|
}
|