Files
BountyBoard_android/app/src/main/java/com/anypreta/bountyboard/MainActivity.kt
T
Vojtěch Maršál 9e2dd40af0 Live updates over WebSocket + clickable links
- 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>
2026-06-13 14:50:24 +02:00

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
}
}