Compare commits
5 Commits
7b2c102470
...
389d3f9e52
| Author | SHA1 | Date | |
|---|---|---|---|
| 389d3f9e52 | |||
| 69a50f8fcd | |||
| 8839015249 | |||
| 478639e427 | |||
| dcbfd43896 |
@@ -40,7 +40,7 @@ jobs:
|
|||||||
echo "APK_NAME=$(basename $APK_PATH)" >> $GITHUB_ENV
|
echo "APK_NAME=$(basename $APK_PATH)" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Upload APK with original name
|
- name: Upload APK with original name
|
||||||
uses: actions/upload-artifact@v5
|
uses: actions/upload-artifact@v6
|
||||||
with:
|
with:
|
||||||
name: ${{ env.APK_NAME }}
|
name: ${{ env.APK_NAME }}
|
||||||
path: ${{ env.APK_PATH }}
|
path: ${{ env.APK_PATH }}
|
||||||
@@ -56,7 +56,7 @@ jobs:
|
|||||||
echo "APK_NAME=$(basename $APK_PATH)" >> $GITHUB_ENV
|
echo "APK_NAME=$(basename $APK_PATH)" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Upload APK with original name
|
- name: Upload APK with original name
|
||||||
uses: actions/upload-artifact@v5
|
uses: actions/upload-artifact@v6
|
||||||
with:
|
with:
|
||||||
name: ${{ env.APK_NAME }}
|
name: ${{ env.APK_NAME }}
|
||||||
path: ${{ env.APK_PATH }}
|
path: ${{ env.APK_PATH }}
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ jobs:
|
|||||||
run: ./gradlew assembleRelease
|
run: ./gradlew assembleRelease
|
||||||
|
|
||||||
- name: Upload release APK
|
- name: Upload release APK
|
||||||
uses: actions/upload-artifact@v5
|
uses: actions/upload-artifact@v6
|
||||||
with:
|
with:
|
||||||
name: app-release.apk
|
name: app-release.apk
|
||||||
path: app/build/outputs/apk/release/app-release.apk
|
path: app/build/outputs/apk/release/app-release.apk
|
||||||
@@ -43,7 +43,7 @@ jobs:
|
|||||||
run: ./gradlew bundleRelease
|
run: ./gradlew bundleRelease
|
||||||
|
|
||||||
- name: Upload release Bundle
|
- name: Upload release Bundle
|
||||||
uses: actions/upload-artifact@v5
|
uses: actions/upload-artifact@v6
|
||||||
with:
|
with:
|
||||||
name: app-release.aab
|
name: app-release.aab
|
||||||
path: app/build/outputs/bundle/release/app-release.aab
|
path: app/build/outputs/bundle/release/app-release.aab
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
+1
-1
@@ -1231,7 +1231,7 @@ class MessagesHistoryViewModelImpl(
|
|||||||
val newUiMessages = messages.mapIndexed { index, message ->
|
val newUiMessages = messages.mapIndexed { index, message ->
|
||||||
message.asPresentation(
|
message.asPresentation(
|
||||||
resourceProvider = resourceProvider,
|
resourceProvider = resourceProvider,
|
||||||
showName = false,
|
showName = true,
|
||||||
prevMessage = messages.getOrNull(index + 1),
|
prevMessage = messages.getOrNull(index + 1),
|
||||||
nextMessage = messages.getOrNull(index - 1),
|
nextMessage = messages.getOrNull(index - 1),
|
||||||
showTimeInActionMessages = AppSettings.Experimental.showTimeInActionMessages,
|
showTimeInActionMessages = AppSettings.Experimental.showTimeInActionMessages,
|
||||||
|
|||||||
+5
-1
@@ -1,14 +1,17 @@
|
|||||||
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
|
||||||
|
import dev.meloda.fast.ui.util.ImmutableList
|
||||||
|
|
||||||
sealed class UiItem(
|
sealed class UiItem(
|
||||||
open val id: Long,
|
open val id: Long,
|
||||||
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,
|
||||||
@@ -29,12 +32,13 @@ sealed class UiItem(
|
|||||||
val isSelected: Boolean,
|
val isSelected: Boolean,
|
||||||
val isPinned: Boolean,
|
val isPinned: Boolean,
|
||||||
val isImportant: Boolean,
|
val isImportant: Boolean,
|
||||||
val attachments: List<VkAttachment>?,
|
val attachments: ImmutableList<VkAttachment>?,
|
||||||
val replyCmId: Long?,
|
val replyCmId: Long?,
|
||||||
val replyTitle: String?,
|
val replyTitle: String?,
|
||||||
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,
|
||||||
|
|||||||
+67
-34
@@ -16,6 +16,7 @@ import androidx.compose.material3.MaterialTheme
|
|||||||
import androidx.compose.material3.surfaceColorAtElevation
|
import androidx.compose.material3.surfaceColorAtElevation
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
|
import androidx.compose.runtime.Immutable
|
||||||
import androidx.compose.runtime.derivedStateOf
|
import androidx.compose.runtime.derivedStateOf
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableIntStateOf
|
import androidx.compose.runtime.mutableIntStateOf
|
||||||
@@ -27,6 +28,7 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.layout.onGloballyPositioned
|
import androidx.compose.ui.layout.onGloballyPositioned
|
||||||
|
import androidx.compose.ui.layout.onSizeChanged
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
import androidx.compose.ui.text.AnnotatedString
|
import androidx.compose.ui.text.AnnotatedString
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
@@ -58,7 +60,8 @@ fun MessageBubble(
|
|||||||
replySummary: String? = null,
|
replySummary: String? = null,
|
||||||
onClick: (VkAttachment) -> Unit = {},
|
onClick: (VkAttachment) -> Unit = {},
|
||||||
onLongClick: (VkAttachment) -> Unit = {},
|
onLongClick: (VkAttachment) -> Unit = {},
|
||||||
onReplyClick: () -> Unit = {}
|
onReplyClick: () -> Unit = {},
|
||||||
|
onBubbleWidthChange: (Int) -> Unit = {},
|
||||||
) {
|
) {
|
||||||
val density = LocalDensity.current
|
val density = LocalDensity.current
|
||||||
|
|
||||||
@@ -66,22 +69,7 @@ fun MessageBubble(
|
|||||||
val currentOnLongClick by rememberUpdatedState(onLongClick)
|
val currentOnLongClick by rememberUpdatedState(onLongClick)
|
||||||
|
|
||||||
val theme = LocalThemeConfig.current
|
val theme = LocalThemeConfig.current
|
||||||
val backgroundColor = if (!isOut) {
|
val colors = messageBubbleColors(isOut = isOut)
|
||||||
MaterialTheme.colorScheme.surfaceColorAtElevation(2.dp)
|
|
||||||
} else {
|
|
||||||
MaterialTheme.colorScheme.primaryContainer
|
|
||||||
}
|
|
||||||
val replyBackgroundColor = if (!isOut) {
|
|
||||||
MaterialTheme.colorScheme.primaryContainer
|
|
||||||
} else {
|
|
||||||
MaterialTheme.colorScheme.inversePrimary
|
|
||||||
}
|
|
||||||
|
|
||||||
val contentColor = if (!isOut) {
|
|
||||||
MaterialTheme.colorScheme.onSurface
|
|
||||||
} else {
|
|
||||||
MaterialTheme.colorScheme.onPrimaryContainer
|
|
||||||
}
|
|
||||||
|
|
||||||
val shouldShowBubble = !text.isNullOrEmpty()
|
val shouldShowBubble = !text.isNullOrEmpty()
|
||||||
|
|
||||||
@@ -115,7 +103,7 @@ fun MessageBubble(
|
|||||||
label = "dateContainerWidth"
|
label = "dateContainerWidth"
|
||||||
)
|
)
|
||||||
|
|
||||||
CompositionLocalProvider(LocalContentColor provides contentColor) {
|
CompositionLocalProvider(LocalContentColor provides colors.content) {
|
||||||
Column(
|
Column(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.wrapContentWidth()
|
.wrapContentWidth()
|
||||||
@@ -138,8 +126,8 @@ fun MessageBubble(
|
|||||||
onClick = onReplyClick,
|
onClick = onReplyClick,
|
||||||
title = replyTitle,
|
title = replyTitle,
|
||||||
summary = replySummary,
|
summary = replySummary,
|
||||||
backgroundColor = backgroundColor,
|
backgroundColor = colors.container,
|
||||||
innerBackgroundColor = replyBackgroundColor
|
innerBackgroundColor = colors.replyContainer
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,6 +137,9 @@ fun MessageBubble(
|
|||||||
.onGloballyPositioned {
|
.onGloballyPositioned {
|
||||||
bubbleContainerWidth = it.size.width
|
bubbleContainerWidth = it.size.width
|
||||||
}
|
}
|
||||||
|
.onSizeChanged {
|
||||||
|
onBubbleWidthChange(it.width)
|
||||||
|
}
|
||||||
.widthIn(min = if (shouldFill) attachmentsContainerWidth.dp else 56.dp)
|
.widthIn(min = if (shouldFill) attachmentsContainerWidth.dp else 56.dp)
|
||||||
.clip(
|
.clip(
|
||||||
RoundedCornerShape(
|
RoundedCornerShape(
|
||||||
@@ -158,7 +149,7 @@ fun MessageBubble(
|
|||||||
bottomEnd = if (attachments != null) 0.dp else 24.dp
|
bottomEnd = if (attachments != null) 0.dp else 24.dp
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.background(backgroundColor)
|
.background(colors.container)
|
||||||
.padding(
|
.padding(
|
||||||
start = 8.dp,
|
start = 8.dp,
|
||||||
end = 8.dp,
|
end = 8.dp,
|
||||||
@@ -199,6 +190,15 @@ fun MessageBubble(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (attachments != null) {
|
if (attachments != null) {
|
||||||
|
val firstAttachment = attachments.firstOrNull()
|
||||||
|
val isMediaAttachment = firstAttachment is VkStickerDomain ||
|
||||||
|
firstAttachment is VkVideoMessageDomain
|
||||||
|
val attachmentBackgroundColor = if (isMediaAttachment) {
|
||||||
|
Color.Transparent
|
||||||
|
} else {
|
||||||
|
colors.container
|
||||||
|
}
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.onGloballyPositioned {
|
.onGloballyPositioned {
|
||||||
@@ -213,18 +213,7 @@ fun MessageBubble(
|
|||||||
topEnd = 0.dp
|
topEnd = 0.dp
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.background(
|
.background(attachmentBackgroundColor)
|
||||||
backgroundColor.copy(
|
|
||||||
alpha = if ((attachments.firstOrNull()?.javaClass
|
|
||||||
?: Nothing::class.java)
|
|
||||||
in listOf(
|
|
||||||
VkStickerDomain::class.java,
|
|
||||||
VkVideoMessageDomain::class.java
|
|
||||||
)
|
|
||||||
) 0f
|
|
||||||
else 1f
|
|
||||||
)
|
|
||||||
)
|
|
||||||
) {
|
) {
|
||||||
Attachments(
|
Attachments(
|
||||||
modifier = Modifier,
|
modifier = Modifier,
|
||||||
@@ -236,7 +225,7 @@ fun MessageBubble(
|
|||||||
val dateStatusBackground = if (theme.darkMode) Color.Black.copy(alpha = 0.5f)
|
val dateStatusBackground = if (theme.darkMode) Color.Black.copy(alpha = 0.5f)
|
||||||
else Color.White.copy(alpha = 0.5f)
|
else Color.White.copy(alpha = 0.5f)
|
||||||
|
|
||||||
CompositionLocalProvider(LocalContentColor provides contentColor) {
|
CompositionLocalProvider(LocalContentColor provides colors.content) {
|
||||||
DateStatus(
|
DateStatus(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.align(Alignment.BottomEnd)
|
.align(Alignment.BottomEnd)
|
||||||
@@ -261,9 +250,34 @@ fun MessageBubble(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
private data class MessageBubbleColors(
|
||||||
|
val container: Color,
|
||||||
|
val content: Color,
|
||||||
|
val replyContainer: Color,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun messageBubbleColors(isOut: Boolean): MessageBubbleColors {
|
||||||
|
return if (isOut) {
|
||||||
|
MessageBubbleColors(
|
||||||
|
container = MaterialTheme.colorScheme.primaryContainer,
|
||||||
|
content = MaterialTheme.colorScheme.onPrimaryContainer,
|
||||||
|
replyContainer = MaterialTheme.colorScheme.inversePrimary
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
MessageBubbleColors(
|
||||||
|
container = MaterialTheme.colorScheme.surfaceContainer,
|
||||||
|
content = MaterialTheme.colorScheme.onSurface,
|
||||||
|
replyContainer = MaterialTheme.colorScheme.surfaceContainerHighest
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Preview
|
@Preview
|
||||||
@Composable
|
@Composable
|
||||||
private fun Bubble() {
|
private fun Bubble() {
|
||||||
|
Column {
|
||||||
MessageBubble(
|
MessageBubble(
|
||||||
modifier = Modifier,
|
modifier = Modifier,
|
||||||
text = AnnotatedString("Some cool text"),
|
text = AnnotatedString("Some cool text"),
|
||||||
@@ -281,4 +295,23 @@ private fun Bubble() {
|
|||||||
onClick = {},
|
onClick = {},
|
||||||
onLongClick = {},
|
onLongClick = {},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
MessageBubble(
|
||||||
|
modifier = Modifier,
|
||||||
|
text = AnnotatedString("Some cool text"),
|
||||||
|
isOut = false,
|
||||||
|
date = "19:01",
|
||||||
|
isEdited = true,
|
||||||
|
isRead = true,
|
||||||
|
sendingStatus = SendingStatus.SENT,
|
||||||
|
isPinned = true,
|
||||||
|
isImportant = true,
|
||||||
|
isSelected = false,
|
||||||
|
attachments = emptyImmutableList(),
|
||||||
|
replyTitle = "Danil Nikolaev",
|
||||||
|
replySummary = "2 photos",
|
||||||
|
onClick = {},
|
||||||
|
onLongClick = {},
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+20
-6
@@ -20,12 +20,16 @@ import androidx.compose.material3.MaterialTheme
|
|||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberUpdatedState
|
import androidx.compose.runtime.rememberUpdatedState
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.alpha
|
import androidx.compose.ui.draw.alpha
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.IntOffset
|
import androidx.compose.ui.unit.IntOffset
|
||||||
@@ -36,7 +40,6 @@ import com.conena.nanokt.android.content.dpInPx
|
|||||||
import dev.meloda.fast.messageshistory.model.UiItem
|
import dev.meloda.fast.messageshistory.model.UiItem
|
||||||
import dev.meloda.fast.model.api.domain.VkAttachment
|
import dev.meloda.fast.model.api.domain.VkAttachment
|
||||||
import dev.meloda.fast.ui.R
|
import dev.meloda.fast.ui.R
|
||||||
import dev.meloda.fast.ui.util.ImmutableList.Companion.toImmutableList
|
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -49,10 +52,16 @@ fun IncomingMessageBubble(
|
|||||||
onLongClick: (VkAttachment) -> Unit = {},
|
onLongClick: (VkAttachment) -> Unit = {},
|
||||||
onReplyClick: () -> Unit = {}
|
onReplyClick: () -> Unit = {}
|
||||||
) {
|
) {
|
||||||
|
val density = LocalDensity.current
|
||||||
|
|
||||||
val currentOnClick by rememberUpdatedState(onClick)
|
val currentOnClick by rememberUpdatedState(onClick)
|
||||||
val currentOnLongClick by rememberUpdatedState(onLongClick)
|
val currentOnLongClick by rememberUpdatedState(onLongClick)
|
||||||
val currentOnReplyClick by rememberUpdatedState(onReplyClick)
|
val currentOnReplyClick by rememberUpdatedState(onReplyClick)
|
||||||
|
|
||||||
|
var bubbleContainerWidth by remember {
|
||||||
|
mutableStateOf(0.dp)
|
||||||
|
}
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
@@ -103,9 +112,12 @@ fun IncomingMessageBubble(
|
|||||||
Text(
|
Text(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(start = 12.dp)
|
.padding(start = 12.dp)
|
||||||
.widthIn(max = 140.dp),
|
.widthIn(
|
||||||
|
max = (bubbleContainerWidth.takeIf { it > 0.dp }
|
||||||
|
?: 140.dp) - 24.dp
|
||||||
|
),
|
||||||
text = message.name,
|
text = message.name,
|
||||||
style = MaterialTheme.typography.bodySmall,
|
style = MaterialTheme.typography.labelMedium,
|
||||||
color = MaterialTheme.colorScheme.primary,
|
color = MaterialTheme.colorScheme.primary,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
@@ -123,14 +135,16 @@ fun IncomingMessageBubble(
|
|||||||
isPinned = message.isPinned,
|
isPinned = message.isPinned,
|
||||||
isImportant = message.isImportant,
|
isImportant = message.isImportant,
|
||||||
isSelected = message.isSelected,
|
isSelected = message.isSelected,
|
||||||
attachments = message.attachments?.toImmutableList(),
|
attachments = message.attachments,
|
||||||
replyTitle = message.replyTitle,
|
replyTitle = message.replyTitle,
|
||||||
replySummary = message.replySummary,
|
replySummary = message.replySummary,
|
||||||
onClick = currentOnClick,
|
onClick = currentOnClick,
|
||||||
onLongClick = currentOnLongClick,
|
onLongClick = currentOnLongClick,
|
||||||
onReplyClick = currentOnReplyClick
|
onReplyClick = currentOnReplyClick,
|
||||||
|
onBubbleWidthChange = {
|
||||||
|
bubbleContainerWidth = with(density) { it.toDp() }
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Spacer(modifier = Modifier.fillMaxWidth(0.25f))
|
Spacer(modifier = Modifier.fillMaxWidth(0.25f))
|
||||||
|
|||||||
+1
-1
@@ -80,7 +80,7 @@ fun OutgoingMessageBubble(
|
|||||||
isPinned = message.isPinned,
|
isPinned = message.isPinned,
|
||||||
isImportant = message.isImportant,
|
isImportant = message.isImportant,
|
||||||
isSelected = message.isSelected,
|
isSelected = message.isSelected,
|
||||||
attachments = message.attachments?.toImmutableList(),
|
attachments = message.attachments,
|
||||||
replyTitle = message.replyTitle,
|
replyTitle = message.replyTitle,
|
||||||
replySummary = message.replySummary,
|
replySummary = message.replySummary,
|
||||||
onClick = currentOnClick,
|
onClick = currentOnClick,
|
||||||
|
|||||||
+2
-1
@@ -24,6 +24,7 @@ import dev.meloda.fast.model.api.domain.FormatDataType
|
|||||||
import dev.meloda.fast.model.api.domain.VkConversation
|
import dev.meloda.fast.model.api.domain.VkConversation
|
||||||
import dev.meloda.fast.model.api.domain.VkMessage
|
import dev.meloda.fast.model.api.domain.VkMessage
|
||||||
import dev.meloda.fast.ui.R
|
import dev.meloda.fast.ui.R
|
||||||
|
import dev.meloda.fast.ui.util.ImmutableList.Companion.toImmutableList
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
@@ -156,7 +157,7 @@ fun VkMessage.asPresentation(
|
|||||||
isSelected = isSelected,
|
isSelected = isSelected,
|
||||||
isPinned = isPinned,
|
isPinned = isPinned,
|
||||||
isImportant = isImportant,
|
isImportant = isImportant,
|
||||||
attachments = attachments?.ifEmpty { null },
|
attachments = attachments?.ifEmpty { null }?.toImmutableList(),
|
||||||
replyCmId = replyMessage?.cmId,
|
replyCmId = replyMessage?.cmId,
|
||||||
replyTitle = extractReplyTitle(),
|
replyTitle = extractReplyTitle(),
|
||||||
replySummary = extractReplySummary()
|
replySummary = extractReplySummary()
|
||||||
|
|||||||
+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