forked from melod1n/fast-messenger
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.
This commit is contained in:
@@ -4,7 +4,6 @@ import android.Manifest
|
|||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.app.NotificationChannel
|
import android.app.NotificationChannel
|
||||||
import android.app.NotificationManager
|
import android.app.NotificationManager
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
@@ -15,43 +14,20 @@ import androidx.activity.SystemBarStyle
|
|||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
import androidx.activity.enableEdgeToEdge
|
import androidx.activity.enableEdgeToEdge
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.derivedStateOf
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.toArgb
|
import androidx.compose.ui.graphics.toArgb
|
||||||
import androidx.compose.ui.platform.LocalResources
|
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.lifecycle.compose.LifecycleResumeEffect
|
import androidx.lifecycle.compose.LifecycleResumeEffect
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
|
||||||
import com.conena.nanokt.android.content.pxToDp
|
|
||||||
import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
||||||
import com.google.accompanist.permissions.isGranted
|
|
||||||
import com.google.accompanist.permissions.rememberPermissionState
|
|
||||||
import dev.meloda.fast.MainViewModel
|
import dev.meloda.fast.MainViewModel
|
||||||
import dev.meloda.fast.MainViewModelImpl
|
import dev.meloda.fast.MainViewModelImpl
|
||||||
import dev.meloda.fast.common.AppConstants
|
import dev.meloda.fast.common.AppConstants
|
||||||
import dev.meloda.fast.common.LongPollController
|
|
||||||
import dev.meloda.fast.common.model.LongPollState
|
|
||||||
import dev.meloda.fast.datastore.AppSettings
|
import dev.meloda.fast.datastore.AppSettings
|
||||||
import dev.meloda.fast.datastore.UserSettings
|
|
||||||
import dev.meloda.fast.model.api.domain.VkUser
|
|
||||||
import dev.meloda.fast.service.OnlineService
|
import dev.meloda.fast.service.OnlineService
|
||||||
import dev.meloda.fast.service.longpolling.LongPollingService
|
import dev.meloda.fast.service.longpolling.LongPollingService
|
||||||
import dev.meloda.fast.ui.R
|
import dev.meloda.fast.ui.R
|
||||||
import dev.meloda.fast.ui.common.LocalSizeConfig
|
|
||||||
import dev.meloda.fast.ui.model.DeviceSize
|
|
||||||
import dev.meloda.fast.ui.model.SizeConfig
|
|
||||||
import dev.meloda.fast.ui.model.ThemeConfig
|
|
||||||
import dev.meloda.fast.ui.theme.AppTheme
|
|
||||||
import dev.meloda.fast.ui.theme.LocalThemeConfig
|
|
||||||
import dev.meloda.fast.ui.theme.LocalUser
|
|
||||||
import dev.meloda.fast.ui.util.isNeedToEnableDarkMode
|
|
||||||
import org.koin.androidx.compose.koinViewModel
|
import org.koin.androidx.compose.koinViewModel
|
||||||
import org.koin.compose.koinInject
|
|
||||||
|
|
||||||
class MainActivity : AppCompatActivity() {
|
class MainActivity : AppCompatActivity() {
|
||||||
|
|
||||||
@@ -88,168 +64,26 @@ class MainActivity : AppCompatActivity() {
|
|||||||
requestNotificationPermissions()
|
requestNotificationPermissions()
|
||||||
|
|
||||||
setContent {
|
setContent {
|
||||||
val resources = LocalResources.current
|
|
||||||
|
|
||||||
val userSettings: UserSettings = koinInject()
|
|
||||||
val longPollController: LongPollController = koinInject()
|
|
||||||
|
|
||||||
val longPollCurrentState by longPollController.currentState.collectAsStateWithLifecycle()
|
|
||||||
val longPollStateToApply by longPollController.stateToApply.collectAsStateWithLifecycle()
|
|
||||||
|
|
||||||
val viewModel: MainViewModel = koinViewModel<MainViewModelImpl>()
|
val viewModel: MainViewModel = koinViewModel<MainViewModelImpl>()
|
||||||
|
LaunchedEffect(viewModel) {
|
||||||
val currentUser: VkUser? by viewModel.currentUser.collectAsStateWithLifecycle()
|
Log.d("VM_CREATE", "onCreate: viewModel: $viewModel")
|
||||||
|
}
|
||||||
|
|
||||||
LifecycleResumeEffect(true) {
|
LifecycleResumeEffect(true) {
|
||||||
viewModel.onAppResumed(intent)
|
viewModel.onAppResumed(intent)
|
||||||
onPauseOrDispose {}
|
onPauseOrDispose {}
|
||||||
}
|
}
|
||||||
|
|
||||||
val permissionState =
|
RootScreen(
|
||||||
rememberPermissionState(permission = Manifest.permission.POST_NOTIFICATIONS)
|
toggleLongPollService = { enable, inBackground ->
|
||||||
|
|
||||||
val isNeedToCheckPermission by viewModel.isNeedToCheckNotificationsPermission.collectAsStateWithLifecycle()
|
|
||||||
val isNeedToRequestPermission by viewModel.isNeedToRequestNotifications.collectAsStateWithLifecycle()
|
|
||||||
|
|
||||||
LaunchedEffect(isNeedToCheckPermission) {
|
|
||||||
if (isNeedToCheckPermission) {
|
|
||||||
viewModel.onPermissionCheckStatus(permissionState.status)
|
|
||||||
|
|
||||||
if (permissionState.status.isGranted) {
|
|
||||||
if (longPollCurrentState == LongPollState.InApp) {
|
|
||||||
toggleLongPollService(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
toggleLongPollService(
|
toggleLongPollService(
|
||||||
enable = true,
|
enable = enable,
|
||||||
inBackground = true
|
inBackground = inBackground ?: AppSettings.Experimental.longPollInBackground
|
||||||
)
|
)
|
||||||
}
|
},
|
||||||
}
|
toggleOnlineService = ::toggleOnlineService
|
||||||
}
|
|
||||||
|
|
||||||
LaunchedEffect(isNeedToRequestPermission) {
|
|
||||||
if (isNeedToRequestPermission) {
|
|
||||||
viewModel.onPermissionsRequested()
|
|
||||||
permissionState.launchPermissionRequest()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
LifecycleResumeEffect(longPollStateToApply) {
|
|
||||||
Log.d("LongPollMainActivity", "longPollStateToApply: $longPollStateToApply")
|
|
||||||
if (longPollStateToApply != LongPollState.Background) {
|
|
||||||
if (longPollStateToApply.isLaunched() && longPollCurrentState.isLaunched()
|
|
||||||
&& longPollCurrentState != longPollStateToApply
|
|
||||||
) {
|
|
||||||
toggleLongPollService(false)
|
|
||||||
Log.d("LongPoll", "recreate()")
|
|
||||||
}
|
|
||||||
|
|
||||||
toggleLongPollService(
|
|
||||||
enable = longPollStateToApply.isLaunched(),
|
|
||||||
inBackground = longPollStateToApply == LongPollState.Background
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
onPauseOrDispose {}
|
|
||||||
}
|
|
||||||
|
|
||||||
val sendOnline by userSettings.sendOnlineStatus.collectAsStateWithLifecycle()
|
|
||||||
LifecycleResumeEffect(sendOnline) {
|
|
||||||
toggleOnlineService(sendOnline)
|
|
||||||
|
|
||||||
onPauseOrDispose {
|
|
||||||
toggleOnlineService(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val deviceWidthDp = remember(true) {
|
|
||||||
resources.displayMetrics.widthPixels.pxToDp()
|
|
||||||
}
|
|
||||||
val deviceHeightDp = remember(true) {
|
|
||||||
resources.displayMetrics.heightPixels.pxToDp()
|
|
||||||
}
|
|
||||||
|
|
||||||
val deviceWidthSize by remember(deviceWidthDp) {
|
|
||||||
derivedStateOf {
|
|
||||||
when {
|
|
||||||
deviceWidthDp <= 360 -> DeviceSize.Small
|
|
||||||
deviceWidthDp <= 600 -> DeviceSize.Compact
|
|
||||||
deviceWidthDp <= 840 -> DeviceSize.Medium
|
|
||||||
else -> DeviceSize.Expanded
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val deviceHeightSize by remember(deviceHeightDp) {
|
|
||||||
derivedStateOf {
|
|
||||||
when {
|
|
||||||
deviceHeightDp <= 480 -> DeviceSize.Small
|
|
||||||
deviceHeightDp <= 700 -> DeviceSize.Compact
|
|
||||||
deviceHeightDp <= 900 -> DeviceSize.Medium
|
|
||||||
else -> DeviceSize.Expanded
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val sizeConfig by remember(deviceWidthSize, deviceHeightSize) {
|
|
||||||
mutableStateOf(
|
|
||||||
SizeConfig(
|
|
||||||
widthSize = deviceWidthSize,
|
|
||||||
heightSize = deviceHeightSize
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
val darkMode by userSettings.darkMode.collectAsStateWithLifecycle()
|
|
||||||
val dynamicColors by userSettings.enableDynamicColors.collectAsStateWithLifecycle()
|
|
||||||
val amoledDark by userSettings.enableAmoledDark.collectAsStateWithLifecycle()
|
|
||||||
val enableBlur by userSettings.useBlur.collectAsStateWithLifecycle()
|
|
||||||
val enableMultiline by userSettings.enableMultiline.collectAsStateWithLifecycle()
|
|
||||||
val useSystemFont by userSettings.useSystemFont.collectAsStateWithLifecycle()
|
|
||||||
val enableAnimations by userSettings.enableAnimations.collectAsStateWithLifecycle()
|
|
||||||
|
|
||||||
val setDarkMode = isNeedToEnableDarkMode(darkMode = darkMode)
|
|
||||||
|
|
||||||
val themeConfig by remember(
|
|
||||||
darkMode,
|
|
||||||
dynamicColors,
|
|
||||||
amoledDark,
|
|
||||||
enableBlur,
|
|
||||||
enableMultiline,
|
|
||||||
setDarkMode,
|
|
||||||
useSystemFont
|
|
||||||
) {
|
|
||||||
derivedStateOf {
|
|
||||||
ThemeConfig(
|
|
||||||
darkMode = setDarkMode,
|
|
||||||
dynamicColors = dynamicColors,
|
|
||||||
selectedColorScheme = 0,
|
|
||||||
amoledDark = amoledDark,
|
|
||||||
enableBlur = enableBlur,
|
|
||||||
enableMultiline = enableMultiline,
|
|
||||||
useSystemFont = useSystemFont,
|
|
||||||
enableAnimations = enableAnimations
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
CompositionLocalProvider(
|
|
||||||
LocalThemeConfig provides themeConfig,
|
|
||||||
LocalSizeConfig provides sizeConfig,
|
|
||||||
LocalUser provides currentUser
|
|
||||||
) {
|
|
||||||
AppTheme(
|
|
||||||
useDarkTheme = themeConfig.darkMode,
|
|
||||||
useDynamicColors = themeConfig.dynamicColors,
|
|
||||||
selectedColorScheme = themeConfig.selectedColorScheme,
|
|
||||||
useAmoledBackground = themeConfig.amoledDark,
|
|
||||||
useSystemFont = themeConfig.useSystemFont
|
|
||||||
) {
|
|
||||||
RootScreen(viewModel = viewModel)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createNotificationChannels() {
|
private fun createNotificationChannels() {
|
||||||
@@ -279,7 +113,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val notificationManager: NotificationManager =
|
val notificationManager: NotificationManager =
|
||||||
getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
getSystemService(NOTIFICATION_SERVICE) as NotificationManager
|
||||||
|
|
||||||
notificationManager.createNotificationChannels(
|
notificationManager.createNotificationChannels(
|
||||||
listOf(
|
listOf(
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
package dev.meloda.fast.presentation
|
package dev.meloda.fast.presentation
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.provider.Settings
|
import android.provider.Settings
|
||||||
|
import android.util.Log
|
||||||
import androidx.activity.compose.LocalActivity
|
import androidx.activity.compose.LocalActivity
|
||||||
import androidx.compose.animation.core.tween
|
import androidx.compose.animation.core.tween
|
||||||
import androidx.compose.animation.fadeIn
|
import androidx.compose.animation.fadeIn
|
||||||
@@ -15,44 +17,225 @@ import androidx.compose.material3.TextButton
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.derivedStateOf
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.platform.LocalResources
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.window.DialogProperties
|
import androidx.compose.ui.window.DialogProperties
|
||||||
|
import androidx.lifecycle.compose.LifecycleResumeEffect
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import androidx.navigation.NavController
|
import androidx.navigation.NavController
|
||||||
import androidx.navigation.NavHostController
|
import androidx.navigation.NavHostController
|
||||||
import androidx.navigation.compose.NavHost
|
import androidx.navigation.compose.NavHost
|
||||||
import androidx.navigation.compose.rememberNavController
|
import androidx.navigation.compose.rememberNavController
|
||||||
|
import com.conena.nanokt.android.content.pxToDp
|
||||||
|
import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
||||||
|
import com.google.accompanist.permissions.isGranted
|
||||||
|
import com.google.accompanist.permissions.rememberPermissionState
|
||||||
import dev.meloda.fast.MainViewModel
|
import dev.meloda.fast.MainViewModel
|
||||||
|
import dev.meloda.fast.MainViewModelImpl
|
||||||
import dev.meloda.fast.auth.authNavGraph
|
import dev.meloda.fast.auth.authNavGraph
|
||||||
import dev.meloda.fast.auth.navigateToAuth
|
import dev.meloda.fast.auth.navigateToAuth
|
||||||
import dev.meloda.fast.chatmaterials.navigation.chatMaterialsScreen
|
import dev.meloda.fast.chatmaterials.navigation.chatMaterialsScreen
|
||||||
import dev.meloda.fast.chatmaterials.navigation.navigateToChatMaterials
|
import dev.meloda.fast.chatmaterials.navigation.navigateToChatMaterials
|
||||||
|
import dev.meloda.fast.common.LongPollController
|
||||||
|
import dev.meloda.fast.common.model.LongPollState
|
||||||
import dev.meloda.fast.conversations.navigation.createChatScreen
|
import dev.meloda.fast.conversations.navigation.createChatScreen
|
||||||
import dev.meloda.fast.conversations.navigation.navigateToCreateChat
|
import dev.meloda.fast.conversations.navigation.navigateToCreateChat
|
||||||
|
import dev.meloda.fast.datastore.UserSettings
|
||||||
import dev.meloda.fast.languagepicker.navigation.languagePickerScreen
|
import dev.meloda.fast.languagepicker.navigation.languagePickerScreen
|
||||||
import dev.meloda.fast.languagepicker.navigation.navigateToLanguagePicker
|
import dev.meloda.fast.languagepicker.navigation.navigateToLanguagePicker
|
||||||
import dev.meloda.fast.messageshistory.navigation.messagesHistoryScreen
|
import dev.meloda.fast.messageshistory.navigation.messagesHistoryScreen
|
||||||
import dev.meloda.fast.messageshistory.navigation.navigateToMessagesHistory
|
import dev.meloda.fast.messageshistory.navigation.navigateToMessagesHistory
|
||||||
|
import dev.meloda.fast.model.api.domain.VkUser
|
||||||
import dev.meloda.fast.navigation.Main
|
import dev.meloda.fast.navigation.Main
|
||||||
import dev.meloda.fast.navigation.mainScreen
|
import dev.meloda.fast.navigation.mainScreen
|
||||||
import dev.meloda.fast.photoviewer.presentation.PhotoViewDialog
|
import dev.meloda.fast.photoviewer.presentation.PhotoViewDialog
|
||||||
import dev.meloda.fast.settings.navigation.navigateToSettings
|
import dev.meloda.fast.settings.navigation.navigateToSettings
|
||||||
import dev.meloda.fast.settings.navigation.settingsScreen
|
import dev.meloda.fast.settings.navigation.settingsScreen
|
||||||
import dev.meloda.fast.ui.R
|
import dev.meloda.fast.ui.R
|
||||||
|
import dev.meloda.fast.ui.common.LocalSizeConfig
|
||||||
|
import dev.meloda.fast.ui.model.DeviceSize
|
||||||
|
import dev.meloda.fast.ui.model.SizeConfig
|
||||||
|
import dev.meloda.fast.ui.model.ThemeConfig
|
||||||
|
import dev.meloda.fast.ui.theme.AppTheme
|
||||||
import dev.meloda.fast.ui.theme.LocalNavController
|
import dev.meloda.fast.ui.theme.LocalNavController
|
||||||
import dev.meloda.fast.ui.theme.LocalNavRootController
|
import dev.meloda.fast.ui.theme.LocalNavRootController
|
||||||
|
import dev.meloda.fast.ui.theme.LocalThemeConfig
|
||||||
|
import dev.meloda.fast.ui.theme.LocalUser
|
||||||
|
import dev.meloda.fast.ui.util.ImmutableList
|
||||||
|
import dev.meloda.fast.ui.util.ImmutableList.Companion.toImmutableList
|
||||||
|
import dev.meloda.fast.ui.util.immutableListOf
|
||||||
|
import dev.meloda.fast.ui.util.isNeedToEnableDarkMode
|
||||||
|
import org.koin.androidx.compose.koinViewModel
|
||||||
|
import org.koin.compose.koinInject
|
||||||
|
|
||||||
|
@OptIn(ExperimentalPermissionsApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun RootScreen(
|
fun RootScreen(
|
||||||
navController: NavHostController = rememberNavController(),
|
toggleLongPollService: (enable: Boolean, inBackground: Boolean?) -> Unit,
|
||||||
viewModel: MainViewModel
|
toggleOnlineService: (enable: Boolean) -> Unit
|
||||||
) {
|
) {
|
||||||
|
val resources = LocalResources.current
|
||||||
|
|
||||||
|
val userSettings: UserSettings = koinInject()
|
||||||
|
val longPollController: LongPollController = koinInject()
|
||||||
|
|
||||||
|
val longPollCurrentState by longPollController.currentState.collectAsStateWithLifecycle()
|
||||||
|
val longPollStateToApply by longPollController.stateToApply.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
|
val viewModel: MainViewModel = koinViewModel<MainViewModelImpl>()
|
||||||
|
LaunchedEffect(viewModel) {
|
||||||
|
Log.d("VM_CREATE", "RootScreen(): viewModel: $viewModel")
|
||||||
|
}
|
||||||
|
|
||||||
|
val currentUser: VkUser? by viewModel.currentUser.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
|
val permissionState =
|
||||||
|
rememberPermissionState(permission = Manifest.permission.POST_NOTIFICATIONS)
|
||||||
|
|
||||||
|
val isNeedToCheckPermission by viewModel.isNeedToCheckNotificationsPermission.collectAsStateWithLifecycle()
|
||||||
|
val isNeedToRequestPermission by viewModel.isNeedToRequestNotifications.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
|
LaunchedEffect(isNeedToCheckPermission) {
|
||||||
|
if (isNeedToCheckPermission) {
|
||||||
|
viewModel.onPermissionCheckStatus(permissionState.status)
|
||||||
|
|
||||||
|
if (permissionState.status.isGranted) {
|
||||||
|
if (longPollCurrentState == LongPollState.InApp) {
|
||||||
|
toggleLongPollService(false, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleLongPollService(true, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(isNeedToRequestPermission) {
|
||||||
|
if (isNeedToRequestPermission) {
|
||||||
|
viewModel.onPermissionsRequested()
|
||||||
|
permissionState.launchPermissionRequest()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LifecycleResumeEffect(longPollStateToApply) {
|
||||||
|
Log.d("LongPollMainActivity", "longPollStateToApply: $longPollStateToApply")
|
||||||
|
if (longPollStateToApply != LongPollState.Background) {
|
||||||
|
if (longPollStateToApply.isLaunched() && longPollCurrentState.isLaunched()
|
||||||
|
&& longPollCurrentState != longPollStateToApply
|
||||||
|
) {
|
||||||
|
toggleLongPollService(false, null)
|
||||||
|
Log.d("LongPoll", "recreate()")
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleLongPollService(
|
||||||
|
longPollStateToApply.isLaunched(),
|
||||||
|
longPollStateToApply == LongPollState.Background
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
onPauseOrDispose {}
|
||||||
|
}
|
||||||
|
|
||||||
|
val sendOnline by userSettings.sendOnlineStatus.collectAsStateWithLifecycle()
|
||||||
|
LifecycleResumeEffect(sendOnline) {
|
||||||
|
toggleOnlineService(sendOnline)
|
||||||
|
|
||||||
|
onPauseOrDispose {
|
||||||
|
toggleOnlineService(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val deviceWidthDp = remember(true) {
|
||||||
|
resources.displayMetrics.widthPixels.pxToDp()
|
||||||
|
}
|
||||||
|
val deviceHeightDp = remember(true) {
|
||||||
|
resources.displayMetrics.heightPixels.pxToDp()
|
||||||
|
}
|
||||||
|
|
||||||
|
val deviceWidthSize by remember(deviceWidthDp) {
|
||||||
|
derivedStateOf {
|
||||||
|
when {
|
||||||
|
deviceWidthDp <= 360 -> DeviceSize.Small
|
||||||
|
deviceWidthDp <= 600 -> DeviceSize.Compact
|
||||||
|
deviceWidthDp <= 840 -> DeviceSize.Medium
|
||||||
|
else -> DeviceSize.Expanded
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val deviceHeightSize by remember(deviceHeightDp) {
|
||||||
|
derivedStateOf {
|
||||||
|
when {
|
||||||
|
deviceHeightDp <= 480 -> DeviceSize.Small
|
||||||
|
deviceHeightDp <= 700 -> DeviceSize.Compact
|
||||||
|
deviceHeightDp <= 900 -> DeviceSize.Medium
|
||||||
|
else -> DeviceSize.Expanded
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val sizeConfig by remember(deviceWidthSize, deviceHeightSize) {
|
||||||
|
mutableStateOf(
|
||||||
|
SizeConfig(
|
||||||
|
widthSize = deviceWidthSize,
|
||||||
|
heightSize = deviceHeightSize
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val darkMode by userSettings.darkMode.collectAsStateWithLifecycle()
|
||||||
|
val dynamicColors by userSettings.enableDynamicColors.collectAsStateWithLifecycle()
|
||||||
|
val amoledDark by userSettings.enableAmoledDark.collectAsStateWithLifecycle()
|
||||||
|
val enableBlur by userSettings.useBlur.collectAsStateWithLifecycle()
|
||||||
|
val enableMultiline by userSettings.enableMultiline.collectAsStateWithLifecycle()
|
||||||
|
val useSystemFont by userSettings.useSystemFont.collectAsStateWithLifecycle()
|
||||||
|
val enableAnimations by userSettings.enableAnimations.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
|
val setDarkMode = isNeedToEnableDarkMode(darkMode = darkMode)
|
||||||
|
|
||||||
|
val themeConfig by remember(
|
||||||
|
darkMode,
|
||||||
|
dynamicColors,
|
||||||
|
amoledDark,
|
||||||
|
enableBlur,
|
||||||
|
enableMultiline,
|
||||||
|
setDarkMode,
|
||||||
|
useSystemFont
|
||||||
|
) {
|
||||||
|
derivedStateOf {
|
||||||
|
ThemeConfig(
|
||||||
|
darkMode = setDarkMode,
|
||||||
|
dynamicColors = dynamicColors,
|
||||||
|
selectedColorScheme = 0,
|
||||||
|
amoledDark = amoledDark,
|
||||||
|
enableBlur = enableBlur,
|
||||||
|
enableMultiline = enableMultiline,
|
||||||
|
useSystemFont = useSystemFont,
|
||||||
|
enableAnimations = enableAnimations
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CompositionLocalProvider(
|
||||||
|
LocalThemeConfig provides themeConfig,
|
||||||
|
LocalSizeConfig provides sizeConfig,
|
||||||
|
LocalUser provides currentUser
|
||||||
|
) {
|
||||||
|
AppTheme(
|
||||||
|
useDarkTheme = themeConfig.darkMode,
|
||||||
|
useDynamicColors = themeConfig.dynamicColors,
|
||||||
|
selectedColorScheme = themeConfig.selectedColorScheme,
|
||||||
|
useAmoledBackground = themeConfig.amoledDark,
|
||||||
|
useSystemFont = themeConfig.useSystemFont
|
||||||
|
) {
|
||||||
|
val navController: NavHostController = rememberNavController()
|
||||||
val activity = LocalActivity.current
|
val activity = LocalActivity.current
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val startDestination by viewModel.startDestination.collectAsStateWithLifecycle()
|
val startDestination by viewModel.startDestination.collectAsStateWithLifecycle()
|
||||||
@@ -126,7 +309,7 @@ fun RootScreen(
|
|||||||
LocalNavController provides navController
|
LocalNavController provides navController
|
||||||
) {
|
) {
|
||||||
var photoViewerInfo by rememberSaveable {
|
var photoViewerInfo by rememberSaveable {
|
||||||
mutableStateOf<Pair<List<String>, Int?>?>(null)
|
mutableStateOf<Pair<ImmutableList<String>, Int?>?>(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
Box(modifier = Modifier.fillMaxSize()) {
|
Box(modifier = Modifier.fillMaxSize()) {
|
||||||
@@ -149,7 +332,9 @@ fun RootScreen(
|
|||||||
onError = viewModel::onError,
|
onError = viewModel::onError,
|
||||||
onSettingsButtonClicked = navController::navigateToSettings,
|
onSettingsButtonClicked = navController::navigateToSettings,
|
||||||
onNavigateToMessagesHistory = navController::navigateToMessagesHistory,
|
onNavigateToMessagesHistory = navController::navigateToMessagesHistory,
|
||||||
onPhotoClicked = { url -> photoViewerInfo = listOf(url) to null },
|
onPhotoClicked = { url ->
|
||||||
|
photoViewerInfo = immutableListOf(url) to null
|
||||||
|
},
|
||||||
onMessageClicked = navController::navigateToMessagesHistory,
|
onMessageClicked = navController::navigateToMessagesHistory,
|
||||||
onNavigateToCreateChat = navController::navigateToCreateChat
|
onNavigateToCreateChat = navController::navigateToCreateChat
|
||||||
)
|
)
|
||||||
@@ -159,12 +344,14 @@ fun RootScreen(
|
|||||||
onBack = navController::navigateUp,
|
onBack = navController::navigateUp,
|
||||||
onNavigateToChatMaterials = navController::navigateToChatMaterials,
|
onNavigateToChatMaterials = navController::navigateToChatMaterials,
|
||||||
onNavigateToPhotoViewer = { photos, index ->
|
onNavigateToPhotoViewer = { photos, index ->
|
||||||
photoViewerInfo = photos to index
|
photoViewerInfo = photos.toImmutableList() to index
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
chatMaterialsScreen(
|
chatMaterialsScreen(
|
||||||
onBack = navController::navigateUp,
|
onBack = navController::navigateUp,
|
||||||
onPhotoClicked = { url -> photoViewerInfo = listOf(url) to null }
|
onPhotoClicked = { url ->
|
||||||
|
photoViewerInfo = immutableListOf(url) to null
|
||||||
|
}
|
||||||
)
|
)
|
||||||
createChatScreen(
|
createChatScreen(
|
||||||
onChatCreated = { conversationId ->
|
onChatCreated = { conversationId ->
|
||||||
@@ -198,6 +385,8 @@ fun RootScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun NavController.navigateToMain() {
|
fun NavController.navigateToMain() {
|
||||||
this.navigate(Main) {
|
this.navigate(Main) {
|
||||||
|
|||||||
+14
-10
@@ -92,12 +92,14 @@ class MessagesRepositoryImpl(
|
|||||||
group = groupsMap.messageGroup(message),
|
group = groupsMap.messageGroup(message),
|
||||||
actionUser = usersMap.messageActionUser(message),
|
actionUser = usersMap.messageActionUser(message),
|
||||||
actionGroup = groupsMap.messageActionGroup(message),
|
actionGroup = groupsMap.messageActionGroup(message),
|
||||||
replyMessage = message.replyMessage?.copy(
|
replyMessage = message.replyMessage.let { replyMessage ->
|
||||||
user = usersMap.messageUser(message),
|
replyMessage?.copy(
|
||||||
group = groupsMap.messageGroup(message),
|
user = usersMap.messageUser(replyMessage),
|
||||||
actionUser = usersMap.messageActionUser(message),
|
group = groupsMap.messageGroup(replyMessage),
|
||||||
actionGroup = groupsMap.messageActionGroup(message),
|
actionUser = usersMap.messageActionUser(replyMessage),
|
||||||
|
actionGroup = groupsMap.messageActionGroup(replyMessage),
|
||||||
)
|
)
|
||||||
|
}
|
||||||
).also { VkMemoryCache[message.id] = it }
|
).also { VkMemoryCache[message.id] = it }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -167,12 +169,14 @@ class MessagesRepositoryImpl(
|
|||||||
group = groupsMap.messageGroup(message),
|
group = groupsMap.messageGroup(message),
|
||||||
actionUser = usersMap.messageActionUser(message),
|
actionUser = usersMap.messageActionUser(message),
|
||||||
actionGroup = groupsMap.messageActionGroup(message),
|
actionGroup = groupsMap.messageActionGroup(message),
|
||||||
replyMessage = message.replyMessage?.asDomain()?.copy(
|
replyMessage = message.replyMessage?.asDomain().let { replyMessage ->
|
||||||
user = usersMap.messageUser(message),
|
replyMessage?.copy(
|
||||||
group = groupsMap.messageGroup(message),
|
user = usersMap.messageUser(replyMessage),
|
||||||
actionUser = usersMap.messageActionUser(message),
|
group = groupsMap.messageGroup(replyMessage),
|
||||||
actionGroup = groupsMap.messageActionGroup(message),
|
actionUser = usersMap.messageActionUser(replyMessage),
|
||||||
|
actionGroup = groupsMap.messageActionGroup(replyMessage),
|
||||||
)
|
)
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+5
-4
@@ -5,6 +5,7 @@ import androidx.navigation.NavGraphBuilder
|
|||||||
import androidx.navigation.compose.composable
|
import androidx.navigation.compose.composable
|
||||||
import dev.meloda.fast.friends.FriendsViewModel
|
import dev.meloda.fast.friends.FriendsViewModel
|
||||||
import dev.meloda.fast.friends.FriendsViewModelImpl
|
import dev.meloda.fast.friends.FriendsViewModelImpl
|
||||||
|
import dev.meloda.fast.friends.OnlineFriendsViewModelImpl
|
||||||
import dev.meloda.fast.friends.presentation.FriendsRoute
|
import dev.meloda.fast.friends.presentation.FriendsRoute
|
||||||
import dev.meloda.fast.model.BaseError
|
import dev.meloda.fast.model.BaseError
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
@@ -20,14 +21,14 @@ fun NavGraphBuilder.friendsScreen(
|
|||||||
onMessageClicked: (userId: Long) -> Unit,
|
onMessageClicked: (userId: Long) -> Unit,
|
||||||
onScrolledToTop: () -> Unit
|
onScrolledToTop: () -> Unit
|
||||||
) {
|
) {
|
||||||
val friendsViewModel: FriendsViewModel = with(activity) {
|
val friendsViewModel: FriendsViewModel = activity.getViewModel<FriendsViewModelImpl>()
|
||||||
getViewModel<FriendsViewModelImpl>()
|
val onlineFriendsViewModel =
|
||||||
}
|
activity.getViewModel<OnlineFriendsViewModelImpl>()
|
||||||
|
|
||||||
composable<Friends> {
|
composable<Friends> {
|
||||||
FriendsRoute(
|
FriendsRoute(
|
||||||
activity = activity,
|
|
||||||
friendsViewModel = friendsViewModel,
|
friendsViewModel = friendsViewModel,
|
||||||
|
onlineFriendsViewModel = onlineFriendsViewModel,
|
||||||
onError = onError,
|
onError = onError,
|
||||||
onPhotoClicked = onPhotoClicked,
|
onPhotoClicked = onPhotoClicked,
|
||||||
onMessageClicked = onMessageClicked,
|
onMessageClicked = onMessageClicked,
|
||||||
|
|||||||
+3
-7
@@ -1,6 +1,5 @@
|
|||||||
package dev.meloda.fast.friends.presentation
|
package dev.meloda.fast.friends.presentation
|
||||||
|
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
|
||||||
import androidx.compose.animation.animateColorAsState
|
import androidx.compose.animation.animateColorAsState
|
||||||
import androidx.compose.animation.core.FastOutLinearInEasing
|
import androidx.compose.animation.core.FastOutLinearInEasing
|
||||||
import androidx.compose.animation.core.animateFloatAsState
|
import androidx.compose.animation.core.animateFloatAsState
|
||||||
@@ -54,17 +53,16 @@ import dev.meloda.fast.ui.theme.LocalHazeState
|
|||||||
import dev.meloda.fast.ui.theme.LocalThemeConfig
|
import dev.meloda.fast.ui.theme.LocalThemeConfig
|
||||||
import dev.meloda.fast.ui.util.ImmutableList
|
import dev.meloda.fast.ui.util.ImmutableList
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.koin.androidx.viewmodel.ext.android.getViewModel
|
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalHazeMaterialsApi::class)
|
@OptIn(ExperimentalMaterial3Api::class, ExperimentalHazeMaterialsApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun FriendsRoute(
|
fun FriendsRoute(
|
||||||
activity: AppCompatActivity,
|
|
||||||
friendsViewModel: FriendsViewModel,
|
friendsViewModel: FriendsViewModel,
|
||||||
|
onlineFriendsViewModel: OnlineFriendsViewModelImpl,
|
||||||
onError: (BaseError) -> Unit,
|
onError: (BaseError) -> Unit,
|
||||||
onPhotoClicked: (url: String) -> Unit,
|
onPhotoClicked: (url: String) -> Unit,
|
||||||
onMessageClicked: (userid: Long) -> Unit,
|
onMessageClicked: (userid: Long) -> Unit,
|
||||||
onScrolledToTop: () -> Unit
|
onScrolledToTop: () -> Unit,
|
||||||
) {
|
) {
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
val currentTheme = LocalThemeConfig.current
|
val currentTheme = LocalThemeConfig.current
|
||||||
@@ -232,9 +230,7 @@ fun FriendsRoute(
|
|||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
) { index ->
|
) { index ->
|
||||||
FriendsScreen(
|
FriendsScreen(
|
||||||
viewModel = if (index == 0) friendsViewModel else with(activity) {
|
viewModel = if (index == 0) friendsViewModel else onlineFriendsViewModel,
|
||||||
getViewModel<OnlineFriendsViewModelImpl>()
|
|
||||||
},
|
|
||||||
orderType = orderType,
|
orderType = orderType,
|
||||||
padding = padding,
|
padding = padding,
|
||||||
tabIndex = index,
|
tabIndex = index,
|
||||||
|
|||||||
+3
@@ -1,5 +1,6 @@
|
|||||||
package dev.meloda.fast.messageshistory.model
|
package dev.meloda.fast.messageshistory.model
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Stable
|
||||||
import androidx.compose.ui.text.AnnotatedString
|
import androidx.compose.ui.text.AnnotatedString
|
||||||
import dev.meloda.fast.common.model.UiImage
|
import dev.meloda.fast.common.model.UiImage
|
||||||
import dev.meloda.fast.model.api.domain.VkAttachment
|
import dev.meloda.fast.model.api.domain.VkAttachment
|
||||||
@@ -9,6 +10,7 @@ sealed class UiItem(
|
|||||||
open val cmId: Long
|
open val cmId: Long
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
@Stable
|
||||||
data class Message(
|
data class Message(
|
||||||
override val id: Long,
|
override val id: Long,
|
||||||
override val cmId: Long,
|
override val cmId: Long,
|
||||||
@@ -35,6 +37,7 @@ sealed class UiItem(
|
|||||||
val replySummary: String?
|
val replySummary: String?
|
||||||
) : UiItem(id, cmId)
|
) : UiItem(id, cmId)
|
||||||
|
|
||||||
|
@Stable
|
||||||
data class ActionMessage(
|
data class ActionMessage(
|
||||||
override val id: Long,
|
override val id: Long,
|
||||||
override val cmId: Long,
|
override val cmId: Long,
|
||||||
|
|||||||
+3
-4
@@ -1,6 +1,5 @@
|
|||||||
package dev.meloda.fast.photoviewer.presentation
|
package dev.meloda.fast.photoviewer.presentation
|
||||||
|
|
||||||
import android.content.Intent
|
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
import androidx.compose.animation.core.Animatable
|
import androidx.compose.animation.core.Animatable
|
||||||
@@ -59,9 +58,9 @@ import dev.meloda.fast.photoviewer.model.PhotoViewScreenState
|
|||||||
import dev.meloda.fast.ui.R
|
import dev.meloda.fast.ui.R
|
||||||
import dev.meloda.fast.ui.components.FullScreenDialog
|
import dev.meloda.fast.ui.components.FullScreenDialog
|
||||||
import dev.meloda.fast.ui.components.Loader
|
import dev.meloda.fast.ui.components.Loader
|
||||||
|
import dev.meloda.fast.ui.util.ImmutableList
|
||||||
import dev.meloda.fast.ui.util.getImage
|
import dev.meloda.fast.ui.util.getImage
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.koin.androidx.compose.koinViewModel
|
import org.koin.androidx.compose.koinViewModel
|
||||||
import java.net.URLEncoder
|
import java.net.URLEncoder
|
||||||
@@ -69,7 +68,7 @@ import kotlin.math.abs
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun PhotoViewDialog(
|
fun PhotoViewDialog(
|
||||||
photoViewerInfo: Pair<List<String>, Int?>?,
|
photoViewerInfo: Pair<ImmutableList<String>, Int?>?,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
onDismiss: () -> Unit
|
onDismiss: () -> Unit
|
||||||
) {
|
) {
|
||||||
@@ -85,7 +84,7 @@ fun PhotoViewDialog(
|
|||||||
arguments = PhotoViewArguments(
|
arguments = PhotoViewArguments(
|
||||||
imageUrls = photoViewerInfo.first.map {
|
imageUrls = photoViewerInfo.first.map {
|
||||||
URLEncoder.encode(it, "utf-8")
|
URLEncoder.encode(it, "utf-8")
|
||||||
},
|
}.toList(),
|
||||||
selectedIndex = photoViewerInfo.second
|
selectedIndex = photoViewerInfo.second
|
||||||
),
|
),
|
||||||
applicationContext = applicationContext
|
applicationContext = applicationContext
|
||||||
|
|||||||
Reference in New Issue
Block a user