Files
fast-messenger/app/src/main/kotlin/dev/meloda/fast/MainViewModel.kt
T
melod1n 30e132d418 Release 0.1.7 (#136)
* Bump haze from 1.1.1 to 1.2.0 (#105)

* Bump org.jetbrains.kotlinx:kotlinx-serialization-json from 1.7.3 to 1.8.0 (#104)

* update gradle wrapper

* Bump agp from 8.7.3 to 8.8.0 (#106)

* Bump com.jraska.module.graph.assertion from 2.7.1 to 2.7.3 (#109)

* Bump haze from 1.2.0 to 1.2.2 (#111)

* Bump koin from 4.0.1 to 4.0.2 (#112)

* little improvement

* Bump kotlin from 2.1.0 to 2.1.10 (#113)

* Bump androidx.compose:compose-bom from 2024.12.01 to 2025.02.00 (#115)

* Bump androidx.navigation:navigation-compose from 2.8.5 to 2.8.7 (#119)

* Bump haze from 1.2.2 to 1.3.1 (#118)

* Bump ksp from 2.1.0-1.0.29 to 2.1.10-1.0.30 (#116)

* Bump agp from 8.8.0 to 8.8.1 (#117)

* Bump com.google.accompanist:accompanist-permissions (#121)

* Rename the app's namespace and applicationId to `dev.meloda.fastvk`, and update the package name in `ACTION_MANAGE_UNKNOWN_APP_SOURCES` intent. Remove unnecessary `onLowMemory` method in the `OnlineService`.

* Bump com.jraska.module.graph.assertion from 2.7.3 to 2.8.0 (#126)

* Bump ksp from 2.1.10-1.0.30 to 2.1.10-1.0.31 (#125)

* Bump haze from 1.3.1 to 1.4.0 (#124)

* Bump agp from 8.8.1 to 8.8.2 (#123)

* Bump androidx.navigation:navigation-compose from 2.8.7 to 2.8.8 (#122)

* Bump haze from 1.4.0 to 1.5.0 (#128)

* Bump agp from 8.8.2 to 8.9.0 (#127)

* Bump androidx.navigation:navigation-compose from 2.8.8 to 2.8.9 (#130)

* Bump androidx.compose:compose-bom from 2025.02.00 to 2025.03.00 (#129)

* revert agp version to 8.8.2

* fix issues with package names

* Bump haze from 1.5.0 to 1.5.1 (#133)

* Bump com.google.guava:guava from 33.4.0-jre to 33.4.5-jre (#132)

* russian translations

* fixes and improvements

---------

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-21 03:13:17 +03:00

242 lines
8.2 KiB
Kotlin

package dev.meloda.fast
import android.content.Intent
import android.os.Build
import android.util.Log
import androidx.appcompat.app.AppCompatDelegate
import androidx.core.app.NotificationCompat
import androidx.core.os.LocaleListCompat
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.conena.nanokt.android.os.isMinSdk
import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.PermissionStatus
import dev.meloda.fast.auth.AuthGraph
import dev.meloda.fast.common.LongPollController
import dev.meloda.fast.common.extensions.ifEmpty
import dev.meloda.fast.common.extensions.listenValue
import dev.meloda.fast.common.extensions.setValue
import dev.meloda.fast.common.model.LongPollState
import dev.meloda.fast.data.UserConfig
import dev.meloda.fast.data.processState
import dev.meloda.fast.datastore.AppSettings
import dev.meloda.fast.datastore.UserSettings
import dev.meloda.fast.domain.GetCurrentAccountUseCase
import dev.meloda.fast.domain.LoadUserByIdUseCase
import dev.meloda.fast.model.BaseError
import dev.meloda.fast.navigation.Main
import dev.meloda.fast.settings.navigation.Settings
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
interface MainViewModel {
val startDestination: StateFlow<Any?>
val isNeedToReplaceWithAuth: StateFlow<Boolean>
val isNeedToShowNotificationsDeniedDialog: StateFlow<Boolean>
val isNeedToShowNotificationsRationaleDialog: StateFlow<Boolean>
val isNeedToCheckNotificationsPermission: StateFlow<Boolean>
val isNeedToRequestNotifications: StateFlow<Boolean>
val profileImageUrl: StateFlow<String?>
fun onError(error: BaseError)
fun onNavigatedToAuth()
fun onAppResumed(intent: Intent)
@OptIn(ExperimentalPermissionsApi::class)
fun onPermissionCheckStatus(status: PermissionStatus)
fun onPermissionsRequested()
fun onNotificationsDeniedDialogConfirmClicked()
fun onNotificationsDeniedDialogCancelClicked()
fun onNotificationsDeniedDialogDismissed()
fun onNotificationsRationaleDialogDismissed()
fun onNotificationsRationaleDialogCancelClicked()
}
class MainViewModelImpl(
private val getCurrentAccountUseCase: GetCurrentAccountUseCase,
private val loadUserByIdUseCase: LoadUserByIdUseCase,
private val userSettings: UserSettings,
private val longPollController: LongPollController
) : MainViewModel, ViewModel() {
override val startDestination = MutableStateFlow<Any?>(null)
override val isNeedToReplaceWithAuth = MutableStateFlow(false)
override val isNeedToShowNotificationsDeniedDialog = MutableStateFlow(false)
override val isNeedToShowNotificationsRationaleDialog = MutableStateFlow(false)
override val isNeedToCheckNotificationsPermission = MutableStateFlow(false)
override val isNeedToRequestNotifications = MutableStateFlow(false)
override val profileImageUrl = MutableStateFlow<String?>(null)
private var openNotificationsSettings = false
private var openAppSettings = false
override fun onError(error: BaseError) {
when (error) {
BaseError.SessionExpired -> {
isNeedToReplaceWithAuth.update { true }
}
is BaseError.SimpleError -> Unit // TODO: 21-Mar-25, Danil Nikolaev: show error in ui
}
}
override fun onNavigatedToAuth() {
isNeedToReplaceWithAuth.update { false }
}
override fun onAppResumed(intent: Intent) {
openNotificationsSettings =
intent.hasCategory(NotificationCompat.INTENT_CATEGORY_NOTIFICATION_PREFERENCES)
openAppSettings =
isMinSdk(Build.VERSION_CODES.N) && intent.action == Intent.ACTION_APPLICATION_PREFERENCES
if (isNeedToShowNotificationsRationaleDialog.value) {
isNeedToShowNotificationsRationaleDialog.update { false }
isNeedToCheckNotificationsPermission.update { true }
}
val newLanguage = AppCompatDelegate.getApplicationLocales()
.toLanguageTags()
.ifEmpty { null }
?: LocaleListCompat.getDefault()
.toLanguageTags()
.split(",")
.firstOrNull()
.orEmpty()
.take(5)
userSettings.onAppLanguageChanged(newLanguage)
loadAccounts()
}
@ExperimentalPermissionsApi
override fun onPermissionCheckStatus(status: PermissionStatus) {
isNeedToCheckNotificationsPermission.update { false }
when (status) {
is PermissionStatus.Denied -> {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) return
if (status.shouldShowRationale) {
isNeedToShowNotificationsRationaleDialog.update { true }
} else {
isNeedToShowNotificationsDeniedDialog.update { true }
}
}
PermissionStatus.Granted -> {
if (isNeedToShowNotificationsRationaleDialog.value) {
isNeedToShowNotificationsRationaleDialog.update { false }
}
}
}
}
override fun onPermissionsRequested() {
isNeedToRequestNotifications.update { false }
}
override fun onNotificationsDeniedDialogConfirmClicked() {
isNeedToRequestNotifications.update { true }
}
override fun onNotificationsDeniedDialogCancelClicked() {
isNeedToShowNotificationsDeniedDialog.update { false }
disableBackgroundLongPoll()
}
override fun onNotificationsDeniedDialogDismissed() {
isNeedToShowNotificationsDeniedDialog.update { false }
}
override fun onNotificationsRationaleDialogDismissed() {
isNeedToShowNotificationsRationaleDialog.update { false }
}
override fun onNotificationsRationaleDialogCancelClicked() {
isNeedToShowNotificationsRationaleDialog.update { false }
disableBackgroundLongPoll()
}
private fun loadProfile() {
loadUserByIdUseCase(userId = null)
.listenValue(viewModelScope) { state ->
state.processState(
error = { error ->
profileImageUrl.emit(null)
},
success = { response ->
val user = response ?: return@listenValue
profileImageUrl.emit(user.photo100)
}
)
}
}
private fun listenLongPollState() {
longPollController.stateToApply.listenValue(viewModelScope) { newState ->
if (newState == LongPollState.Background) {
isNeedToCheckNotificationsPermission.update { true }
}
}
}
private fun loadAccounts() {
viewModelScope.launch(Dispatchers.IO) {
val currentAccount = getCurrentAccountUseCase()
Log.d("MainViewModel", "currentAccount: $currentAccount")
listenLongPollState()
if (currentAccount != null) {
UserConfig.apply {
this.userId = currentAccount.userId
this.accessToken = currentAccount.accessToken
this.fastToken = currentAccount.fastToken
this.trustedHash = currentAccount.trustedHash
}
longPollController.setStateToApply(
if (AppSettings.Experimental.longPollInBackground) {
LongPollState.Background
} else {
LongPollState.InApp
}
)
}
if (currentAccount != null) {
loadProfile()
}
startDestination.setValue {
when {
openAppSettings -> Settings
openNotificationsSettings -> Settings
currentAccount == null -> AuthGraph
else -> Main
}
}
}
}
private fun disableBackgroundLongPoll() {
AppSettings.Experimental.longPollInBackground = false
longPollController.setStateToApply(LongPollState.InApp)
}
}