forked from melod1n/fast-messenger
30e132d418
* 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>
242 lines
8.2 KiB
Kotlin
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)
|
|
}
|
|
}
|