Files
fast-messenger/app/src/main/kotlin/dev/meloda/fast/presentation/MainActivity.kt
T
melod1n 478639e427 Refactor: Extract RootScreen from MainActivity and fix reply message user
This commit refactors the UI composition logic by extracting it from `MainActivity` into a new, dedicated `RootScreen` composable. This improves the separation of concerns and simplifies `MainActivity`.

Additionally, a bug has been fixed where a replied-to message would incorrectly display the author of the parent message instead of its own author.

Key changes:
- Moved theme setup, permission handling, Long-Poll/Online service management, and navigation graph hosting into the new `RootScreen.kt`.
- `MainActivity` is now significantly simplified, delegating its UI composition to `RootScreen`.
- Corrected the user and group assignment for `replyMessage` in `MessagesRepositoryImpl` to ensure the correct author is displayed.
- Introduced `OnlineFriendsViewModel` to the `FriendsRoute` to separate the logic for online friends.
- Replaced `List` with a custom `ImmutableList` for `photoViewerInfo` state to improve Compose stability.
2025-12-15 22:24:17 +03:00

187 lines
6.3 KiB
Kotlin

package dev.meloda.fast.presentation
import android.Manifest
import android.annotation.SuppressLint
import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Intent
import android.content.res.Configuration
import android.os.Build
import android.os.Bundle
import android.provider.Settings
import android.util.Log
import androidx.activity.SystemBarStyle
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb
import androidx.core.content.ContextCompat
import androidx.lifecycle.compose.LifecycleResumeEffect
import com.google.accompanist.permissions.ExperimentalPermissionsApi
import dev.meloda.fast.MainViewModel
import dev.meloda.fast.MainViewModelImpl
import dev.meloda.fast.common.AppConstants
import dev.meloda.fast.datastore.AppSettings
import dev.meloda.fast.service.OnlineService
import dev.meloda.fast.service.longpolling.LongPollingService
import dev.meloda.fast.ui.R
import org.koin.androidx.compose.koinViewModel
class MainActivity : AppCompatActivity() {
@SuppressLint("HardwareIds", "InlinedApi")
@OptIn(ExperimentalPermissionsApi::class)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
AppSettings.deviceId = Settings.Secure.getString(
contentResolver,
Settings.Secure.ANDROID_ID
)
val currentNightMode = resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
val systemBarStyle = when (currentNightMode) {
Configuration.UI_MODE_NIGHT_NO -> SystemBarStyle.light(
Color.Transparent.toArgb(),
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
Color.Gray.copy(alpha = 0.85f).toArgb()
} else {
Color.Transparent.toArgb()
}
)
Configuration.UI_MODE_NIGHT_YES -> SystemBarStyle.dark(Color.Transparent.toArgb())
else -> error("Illegal State, current mode is $currentNightMode")
}
enableEdgeToEdge(
statusBarStyle = systemBarStyle,
navigationBarStyle = systemBarStyle,
)
createNotificationChannels()
requestNotificationPermissions()
setContent {
val viewModel: MainViewModel = koinViewModel<MainViewModelImpl>()
LaunchedEffect(viewModel) {
Log.d("VM_CREATE", "onCreate: viewModel: $viewModel")
}
LifecycleResumeEffect(true) {
viewModel.onAppResumed(intent)
onPauseOrDispose {}
}
RootScreen(
toggleLongPollService = { enable, inBackground ->
toggleLongPollService(
enable = enable,
inBackground = inBackground ?: AppSettings.Experimental.longPollInBackground
)
},
toggleOnlineService = ::toggleOnlineService
)
}
}
private fun createNotificationChannels() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val noCategoryName = getString(R.string.notification_channel_no_category_name)
val noCategoryDescriptionText =
getString(R.string.notification_channel_no_category_description)
val noCategoryChannel =
NotificationChannel(
AppConstants.NOTIFICATION_CHANNEL_UNCATEGORIZED,
noCategoryName,
NotificationManager.IMPORTANCE_HIGH
).apply {
description = noCategoryDescriptionText
}
val longPollName = getString(R.string.notification_channel_long_polling_service_name)
val longPollDescriptionText =
getString(R.string.notification_channel_long_polling_service_description)
val longPollChannel =
NotificationChannel(
AppConstants.NOTIFICATION_CHANNEL_LONG_POLLING,
longPollName,
NotificationManager.IMPORTANCE_NONE
).apply {
description = longPollDescriptionText
}
val notificationManager: NotificationManager =
getSystemService(NOTIFICATION_SERVICE) as NotificationManager
notificationManager.createNotificationChannels(
listOf(
noCategoryChannel,
longPollChannel
)
)
}
}
private fun requestNotificationPermissions() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
requestPermissions(
arrayOf(Manifest.permission.POST_NOTIFICATIONS),
REQUEST_NOTIFICATION_PERMISSION_CODE
)
}
}
private val longPollingServiceIntent by lazy {
Intent(this, LongPollingService::class.java)
}
private val onlineServiceIntent by lazy {
Intent(this, OnlineService::class.java)
}
private fun toggleLongPollService(
enable: Boolean,
inBackground: Boolean = AppSettings.Experimental.longPollInBackground
) {
if (enable) {
val longPollIntent = longPollingServiceIntent
if (inBackground) {
ContextCompat.startForegroundService(this, longPollIntent)
} else {
startService(longPollIntent)
}
} else {
stopService(longPollingServiceIntent)
}
}
private fun toggleOnlineService(enable: Boolean) {
if (enable) {
startService(onlineServiceIntent)
} else {
stopService(onlineServiceIntent)
}
}
private fun stopServices() {
toggleOnlineService(enable = false)
val asForeground = AppSettings.Experimental.longPollInBackground
if (!asForeground) {
toggleLongPollService(enable = false)
}
}
override fun onDestroy() {
super.onDestroy()
stopServices()
}
companion object {
private const val REQUEST_NOTIFICATION_PERMISSION_CODE = 1
}
}