feat: reconnection, overlay UI, profile caching, history loading, log viewer, and about page
- Reconnection: unlimited retries with capped backoff, forceReconnect on foreground and manual refresh when offline - Overlay: fix status bar coverage, remove scrim, fix IME layout (messages fixed at top, only full-screen IME pushes input), handle process-kill by eager ViewModel resolution with try-catch - Profile: cache-first rendering, cloud refresh on each visit, silent fallback to cache on failure - Messages: fix message swallowing by tracking DB observer job and using atomic StateFlow.update(), add dedicated isAssistantStreaming state for reliable typing indicator - History: history_response handler now emits to live message stream, HTTP fallback waits for WS connection before requesting history - Foreground: always request history on foreground to catch cross-device messages - Log viewer: enhanced with All tab, auto-scroll, new categories (HTTP, voice, general), added log points for app lifecycle and overlay events - Settings: added About page with project link (https://git.yeij.top/AskaEth/Cyrene-For-Android) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -1,32 +1,74 @@
|
||||
package top.yeij.cyrene
|
||||
|
||||
import android.app.Activity
|
||||
import android.app.Application
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.android.ext.koin.androidContext
|
||||
import org.koin.core.context.GlobalContext
|
||||
import org.koin.core.context.startKoin
|
||||
import top.yeij.cyrene.data.local.PreferencesDataStore
|
||||
import top.yeij.cyrene.data.remote.AuthInterceptor
|
||||
import top.yeij.cyrene.data.remote.DynamicUrlInterceptor
|
||||
import top.yeij.cyrene.data.repository.ChatRepositoryImpl
|
||||
import top.yeij.cyrene.di.appModule
|
||||
import org.koin.android.ext.koin.androidContext
|
||||
import org.koin.core.context.startKoin
|
||||
import top.yeij.cyrene.util.NotificationHelper
|
||||
import top.yeij.cyrene.util.RuntimeLog
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
|
||||
class CyreneApplication : Application() {
|
||||
|
||||
private val initScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
|
||||
private val activityCount = AtomicInteger(0)
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
RuntimeLog.general("app", "Application onCreate")
|
||||
|
||||
startKoin {
|
||||
androidContext(this@CyreneApplication)
|
||||
modules(appModule)
|
||||
}
|
||||
|
||||
// Track foreground/background state
|
||||
registerActivityLifecycleCallbacks(object : ActivityLifecycleCallbacks {
|
||||
override fun onActivityStarted(activity: Activity) {
|
||||
if (activityCount.incrementAndGet() == 1) {
|
||||
RuntimeLog.general("app", "App in foreground")
|
||||
getRepo()?.onAppForeground()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onActivityStopped(activity: Activity) {
|
||||
if (activityCount.decrementAndGet() == 0) {
|
||||
RuntimeLog.general("app", "App in background")
|
||||
getRepo()?.onAppBackground()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {}
|
||||
override fun onActivityResumed(activity: Activity) {}
|
||||
override fun onActivityPaused(activity: Activity) {}
|
||||
override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {}
|
||||
override fun onActivityDestroyed(activity: Activity) {}
|
||||
})
|
||||
|
||||
// Set up background notification callback once Koin is ready
|
||||
initScope.launch {
|
||||
val koin = org.koin.core.context.GlobalContext.get()
|
||||
val notificationHelper = NotificationHelper(this@CyreneApplication)
|
||||
val repo = getRepo()
|
||||
repo?.setNotificationCallback { message ->
|
||||
notificationHelper.showMessageNotification(message)
|
||||
}
|
||||
}
|
||||
|
||||
initScope.launch {
|
||||
val koin = GlobalContext.get()
|
||||
val prefs: PreferencesDataStore = koin.get()
|
||||
val urlInterceptor: DynamicUrlInterceptor = koin.get()
|
||||
val authInterceptor: AuthInterceptor = koin.get()
|
||||
@@ -39,4 +81,16 @@ class CyreneApplication : Application() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getRepo(): ChatRepositoryImpl? {
|
||||
return try {
|
||||
GlobalContext.get().get()
|
||||
} catch (_: Exception) {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "CyreneApp"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user