forked from melod1n/fast-messenger
temp
This commit is contained in:
@@ -29,61 +29,30 @@ 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 currentUser: StateFlow<VkUser?>
|
||||
|
||||
val isNeedToShowNotificationsDeniedDialog: StateFlow<Boolean>
|
||||
val isNeedToShowNotificationsRationaleDialog: StateFlow<Boolean>
|
||||
val isNeedToCheckNotificationsPermission: StateFlow<Boolean>
|
||||
val isNeedToRequestNotifications: StateFlow<Boolean>
|
||||
|
||||
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()
|
||||
|
||||
fun onUserAuthenticated()
|
||||
}
|
||||
|
||||
class MainViewModelImpl(
|
||||
class MainViewModel(
|
||||
private val getCurrentAccountUseCase: GetCurrentAccountUseCase,
|
||||
private val loadUserByIdUseCase: LoadUserByIdUseCase,
|
||||
private val userSettings: UserSettings,
|
||||
private val longPollController: LongPollController,
|
||||
private val logger: FastLogger
|
||||
) : MainViewModel, ViewModel() {
|
||||
) : ViewModel() {
|
||||
|
||||
override val startDestination = MutableStateFlow<Any?>(null)
|
||||
override val isNeedToReplaceWithAuth = MutableStateFlow(false)
|
||||
override val currentUser = MutableStateFlow<VkUser?>(null)
|
||||
val startDestination = MutableStateFlow<Any?>(null)
|
||||
val isNeedToReplaceWithAuth = MutableStateFlow(false)
|
||||
val currentUser = MutableStateFlow<VkUser?>(null)
|
||||
|
||||
override val isNeedToShowNotificationsDeniedDialog = MutableStateFlow(false)
|
||||
override val isNeedToShowNotificationsRationaleDialog = MutableStateFlow(false)
|
||||
override val isNeedToCheckNotificationsPermission = MutableStateFlow(false)
|
||||
override val isNeedToRequestNotifications = MutableStateFlow(false)
|
||||
val isNeedToShowNotificationsDeniedDialog = MutableStateFlow(false)
|
||||
val isNeedToShowNotificationsRationaleDialog = MutableStateFlow(false)
|
||||
val isNeedToCheckNotificationsPermission = MutableStateFlow(false)
|
||||
val isNeedToRequestNotifications = MutableStateFlow(false)
|
||||
|
||||
private var openNotificationsSettings = false
|
||||
private var openAppSettings = false
|
||||
|
||||
override fun onError(error: BaseError) {
|
||||
fun onError(error: BaseError) {
|
||||
when (error) {
|
||||
BaseError.SessionExpired,
|
||||
BaseError.AccountBlocked -> {
|
||||
@@ -94,11 +63,11 @@ class MainViewModelImpl(
|
||||
}
|
||||
}
|
||||
|
||||
override fun onNavigatedToAuth() {
|
||||
fun onNavigatedToAuth() {
|
||||
isNeedToReplaceWithAuth.update { false }
|
||||
}
|
||||
|
||||
override fun onAppResumed(intent: Intent) {
|
||||
fun onAppResumed(intent: Intent) {
|
||||
openNotificationsSettings =
|
||||
intent.hasCategory(NotificationCompat.INTENT_CATEGORY_NOTIFICATION_PREFERENCES)
|
||||
openAppSettings =
|
||||
@@ -125,7 +94,7 @@ class MainViewModelImpl(
|
||||
}
|
||||
|
||||
@ExperimentalPermissionsApi
|
||||
override fun onPermissionCheckStatus(status: PermissionStatus) {
|
||||
fun onPermissionCheckStatus(status: PermissionStatus) {
|
||||
isNeedToCheckNotificationsPermission.update { false }
|
||||
|
||||
when (status) {
|
||||
@@ -147,33 +116,33 @@ class MainViewModelImpl(
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPermissionsRequested() {
|
||||
fun onPermissionsRequested() {
|
||||
isNeedToRequestNotifications.update { false }
|
||||
}
|
||||
|
||||
override fun onNotificationsDeniedDialogConfirmClicked() {
|
||||
fun onNotificationsDeniedDialogConfirmClicked() {
|
||||
isNeedToRequestNotifications.update { true }
|
||||
}
|
||||
|
||||
override fun onNotificationsDeniedDialogCancelClicked() {
|
||||
fun onNotificationsDeniedDialogCancelClicked() {
|
||||
isNeedToShowNotificationsDeniedDialog.update { false }
|
||||
disableBackgroundLongPoll()
|
||||
}
|
||||
|
||||
override fun onNotificationsDeniedDialogDismissed() {
|
||||
fun onNotificationsDeniedDialogDismissed() {
|
||||
isNeedToShowNotificationsDeniedDialog.update { false }
|
||||
}
|
||||
|
||||
override fun onNotificationsRationaleDialogDismissed() {
|
||||
fun onNotificationsRationaleDialogDismissed() {
|
||||
isNeedToShowNotificationsRationaleDialog.update { false }
|
||||
}
|
||||
|
||||
override fun onNotificationsRationaleDialogCancelClicked() {
|
||||
fun onNotificationsRationaleDialogCancelClicked() {
|
||||
isNeedToShowNotificationsRationaleDialog.update { false }
|
||||
disableBackgroundLongPoll()
|
||||
}
|
||||
|
||||
override fun onUserAuthenticated() {
|
||||
fun onUserAuthenticated() {
|
||||
loadProfile()
|
||||
}
|
||||
|
||||
@@ -202,11 +171,17 @@ class MainViewModelImpl(
|
||||
|
||||
private fun loadAccounts() {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
val currentAccount = getCurrentAccountUseCase()
|
||||
val currentAccount = getCurrentAccountUseCase()?.mapToDto()
|
||||
|
||||
logger.debug(
|
||||
this@MainViewModelImpl::class,
|
||||
"loadAccounts(): currentAccount: $currentAccount"
|
||||
this@MainViewModel::class,
|
||||
"loadAccounts(): currentAccount: %s"
|
||||
.format(
|
||||
currentAccount?.copy(
|
||||
accessToken = if (currentAccount.accessToken.isNotEmpty()) "<redacted>"
|
||||
else "null"
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
listenLongPollState()
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
package dev.meloda.fast.common.di
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.Resources
|
||||
import android.net.ConnectivityManager
|
||||
import android.os.PowerManager
|
||||
import androidx.preference.PreferenceManager
|
||||
import org.koin.android.ext.koin.androidContext
|
||||
import org.koin.core.module.dsl.factoryOf
|
||||
import org.koin.dsl.module
|
||||
|
||||
val androidModule = module {
|
||||
// TODO: 14/05/2024, Danil Nikolaev: extract all operations with preferences to standalone class
|
||||
factoryOf(PreferenceManager::getDefaultSharedPreferences)
|
||||
factory<Resources> { androidContext().resources }
|
||||
factory<PowerManager> { androidContext().getSystemService(Context.POWER_SERVICE) as PowerManager }
|
||||
factory<ConnectivityManager> { androidContext().getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager }
|
||||
}
|
||||
@@ -1,16 +1,13 @@
|
||||
package dev.meloda.fast.common.di
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.Resources
|
||||
import android.os.PowerManager
|
||||
import androidx.preference.PreferenceManager
|
||||
import coil.ImageLoader
|
||||
import coil.annotation.ExperimentalCoilApi
|
||||
import dev.meloda.fast.MainViewModelImpl
|
||||
import dev.meloda.fast.MainViewModel
|
||||
import dev.meloda.fast.auth.authModule
|
||||
import dev.meloda.fast.chatmaterials.di.chatMaterialsModule
|
||||
import dev.meloda.fast.common.LongPollController
|
||||
import dev.meloda.fast.common.LongPollControllerImpl
|
||||
import dev.meloda.fast.common.NetworkStateListener
|
||||
import dev.meloda.fast.common.provider.Provider
|
||||
import dev.meloda.fast.common.provider.ResourceProvider
|
||||
import dev.meloda.fast.common.provider.ResourceProviderImpl
|
||||
@@ -27,7 +24,7 @@ import dev.meloda.fast.profile.di.profileModule
|
||||
import dev.meloda.fast.provider.ApiLanguageProvider
|
||||
import dev.meloda.fast.service.longpolling.di.longPollModule
|
||||
import dev.meloda.fast.settings.di.settingsModule
|
||||
import org.koin.android.ext.koin.androidContext
|
||||
import org.koin.core.module.dsl.factoryOf
|
||||
import org.koin.core.module.dsl.singleOf
|
||||
import org.koin.core.module.dsl.viewModelOf
|
||||
import org.koin.core.qualifier.qualifier
|
||||
@@ -52,27 +49,24 @@ val applicationModule = module {
|
||||
)
|
||||
|
||||
includes(loggerModule)
|
||||
includes(androidModule)
|
||||
|
||||
// TODO: 14/05/2024, Danil Nikolaev: extract all operations with preferences to standalone class
|
||||
singleOf(PreferenceManager::getDefaultSharedPreferences)
|
||||
single<Resources> { androidContext().resources }
|
||||
factory<PowerManager> { androidContext().getSystemService(Context.POWER_SERVICE) as PowerManager }
|
||||
factoryOf(::ApiLanguageProvider) bind Provider::class
|
||||
|
||||
singleOf(::ApiLanguageProvider) bind Provider::class
|
||||
|
||||
viewModelOf(::MainViewModelImpl) {
|
||||
qualifier = qualifier("main")
|
||||
}
|
||||
viewModelOf(::MainViewModel) { qualifier = qualifier("main") }
|
||||
|
||||
single<ImageLoader> {
|
||||
ImageLoader.Builder(get())
|
||||
.crossfade(true)
|
||||
.build()
|
||||
.also { it.diskCache?.directory?.toFile()?.listFiles() }
|
||||
.also {
|
||||
it.diskCache?.directory?.toFile()?.listFiles()
|
||||
}
|
||||
}
|
||||
|
||||
singleOf(::LongPollControllerImpl) bind LongPollController::class
|
||||
singleOf(::ResourceProviderImpl) bind ResourceProvider::class
|
||||
|
||||
singleOf(::NetworkObserver)
|
||||
singleOf(::NetworkStateListener)
|
||||
singleOf(::NetworkObserver) { qualifier = qualifier("main") }
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ import androidx.core.content.ContextCompat
|
||||
import androidx.lifecycle.compose.LifecycleResumeEffect
|
||||
import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
||||
import dev.meloda.fast.MainViewModel
|
||||
import dev.meloda.fast.MainViewModelImpl
|
||||
import dev.meloda.fast.common.AppConstants
|
||||
import dev.meloda.fast.datastore.AppSettings
|
||||
import dev.meloda.fast.domain.LongPollEventsHandler
|
||||
@@ -70,7 +69,7 @@ class MainActivity : AppCompatActivity() {
|
||||
setContent {
|
||||
val logger: FastLogger = koinInject()
|
||||
|
||||
val viewModel: MainViewModel = koinViewModel<MainViewModelImpl>()
|
||||
val viewModel: MainViewModel = koinViewModel()
|
||||
LifecycleResumeEffect(true) {
|
||||
viewModel.onAppResumed(intent)
|
||||
onPauseOrDispose {}
|
||||
@@ -184,7 +183,6 @@ class MainActivity : AppCompatActivity() {
|
||||
super.onDestroy()
|
||||
stopServices()
|
||||
get<LongPollEventsHandler>().onDestroy()
|
||||
get<NetworkObserver>().onDestroy()
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
@@ -1,30 +1,24 @@
|
||||
package dev.meloda.fast.presentation
|
||||
|
||||
import android.content.Context
|
||||
import android.net.ConnectivityManager
|
||||
import android.net.LinkProperties
|
||||
import android.net.Network
|
||||
import android.net.NetworkCapabilities
|
||||
import android.net.NetworkRequest
|
||||
import android.os.Build
|
||||
import dev.meloda.fast.common.NetworkStateListener
|
||||
import dev.meloda.fast.common.model.NetworkState
|
||||
import dev.meloda.fast.common.model.NetworkStatus
|
||||
import dev.meloda.fast.common.model.NetworkType
|
||||
import dev.meloda.fast.logger.FastLogger
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
class NetworkObserver(
|
||||
context: Context,
|
||||
private val logger: FastLogger
|
||||
internal class NetworkObserver(
|
||||
private val connectivityManager: ConnectivityManager,
|
||||
private val logger: FastLogger,
|
||||
private val networkStateListener: NetworkStateListener
|
||||
) {
|
||||
|
||||
private val connectivityManager =
|
||||
context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
||||
|
||||
private val networkStatus = MutableStateFlow(NetworkStatus.UNAVAILABLE)
|
||||
val networkStatusFlow = networkStatus.asStateFlow()
|
||||
|
||||
private val networkState = MutableStateFlow(NetworkState.DISCONNECTED)
|
||||
val networkStateFlow = networkState.asStateFlow()
|
||||
|
||||
private val networks = ConcurrentHashMap<Network, NetworkModel>()
|
||||
|
||||
private var clearCallbacks: (() -> Unit)? = null
|
||||
@@ -40,12 +34,7 @@ class NetworkObserver(
|
||||
NetworkState.DISCONNECTED
|
||||
}
|
||||
|
||||
networkState.value = state
|
||||
networkStatus.value = when (state) {
|
||||
NetworkState.CONNECTED -> NetworkStatus.AVAILABLE
|
||||
NetworkState.DISCONNECTED -> NetworkStatus.UNAVAILABLE
|
||||
}
|
||||
|
||||
networkStateListener.updateNetworkState(state)
|
||||
log("STATE: $state")
|
||||
}
|
||||
|
||||
@@ -58,7 +47,8 @@ class NetworkObserver(
|
||||
network = network,
|
||||
capabilities = connectivityManager.getNetworkCapabilities(network),
|
||||
properties = connectivityManager.getLinkProperties(network),
|
||||
status = NetworkStatus.AVAILABLE
|
||||
status = NetworkStatus.AVAILABLE,
|
||||
assumeInternet = true
|
||||
)
|
||||
|
||||
syncNetworkState()
|
||||
@@ -156,6 +146,10 @@ class NetworkObserver(
|
||||
refreshActiveNetwork()
|
||||
}
|
||||
|
||||
private fun log(text: String) {
|
||||
logger.debug(this::class, text)
|
||||
}
|
||||
|
||||
private fun refreshActiveNetwork() {
|
||||
val network = connectivityManager.activeNetwork
|
||||
if (network == null) {
|
||||
@@ -177,17 +171,14 @@ class NetworkObserver(
|
||||
syncNetworkState()
|
||||
}
|
||||
|
||||
private fun log(text: String) {
|
||||
logger.debug(this::class, text)
|
||||
}
|
||||
|
||||
private fun mapNetworkModel(
|
||||
network: Network,
|
||||
capabilities: NetworkCapabilities? = null,
|
||||
properties: LinkProperties? = null,
|
||||
status: NetworkStatus? = null,
|
||||
maxMsToLive: Long? = null,
|
||||
from: NetworkModel? = null
|
||||
from: NetworkModel? = null,
|
||||
assumeInternet: Boolean = false
|
||||
): NetworkModel {
|
||||
val caps = capabilities
|
||||
?: from?.networkCapabilities
|
||||
@@ -199,12 +190,15 @@ class NetworkObserver(
|
||||
else -> from?.type ?: NetworkType.UNKNOWN
|
||||
}
|
||||
|
||||
val hasInternet = caps?.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
|
||||
?: from?.hasInternet
|
||||
?: false
|
||||
val hasInternet = if (caps != null) {
|
||||
caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) ||
|
||||
caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
|
||||
} else {
|
||||
from?.hasInternet ?: assumeInternet
|
||||
}
|
||||
|
||||
val signalStrength =
|
||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
caps?.signalStrength
|
||||
} else {
|
||||
null
|
||||
@@ -224,27 +218,8 @@ class NetworkObserver(
|
||||
?: connectivityManager.getLinkProperties(network)
|
||||
)
|
||||
}
|
||||
|
||||
fun onDestroy() {
|
||||
clearCallbacks?.let { unregisterCallback ->
|
||||
runCatching { unregisterCallback() }
|
||||
.onFailure { throwable ->
|
||||
logger.error(
|
||||
this::class.java,
|
||||
"Failed to unregister network callback",
|
||||
throwable
|
||||
)
|
||||
}
|
||||
}
|
||||
clearCallbacks = null
|
||||
networks.clear()
|
||||
syncNetworkState()
|
||||
}
|
||||
}
|
||||
|
||||
enum class NetworkType {
|
||||
CELLULAR, WIFI, UNKNOWN
|
||||
}
|
||||
|
||||
data class NetworkModel(
|
||||
val id: Int,
|
||||
@@ -261,14 +236,3 @@ data class NetworkModel(
|
||||
|
||||
fun isInternetAvailable(): Boolean = hasInternet && isStatusOk()
|
||||
}
|
||||
|
||||
enum class NetworkStatus {
|
||||
AVAILABLE, UNAVAILABLE, LOST, BLOCKED, UNBLOCKED;
|
||||
|
||||
fun isOk(): Boolean = when (this) {
|
||||
AVAILABLE, UNBLOCKED -> true
|
||||
UNAVAILABLE, LOST, BLOCKED -> false
|
||||
}
|
||||
}
|
||||
|
||||
enum class NetworkState { CONNECTED, DISCONNECTED }
|
||||
|
||||
@@ -38,13 +38,13 @@ 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.MainViewModelImpl
|
||||
import dev.meloda.fast.auth.authNavGraph
|
||||
import dev.meloda.fast.auth.captcha.presentation.CaptchaScreen
|
||||
import dev.meloda.fast.auth.navigateToAuth
|
||||
import dev.meloda.fast.chatmaterials.navigation.chatMaterialsScreen
|
||||
import dev.meloda.fast.chatmaterials.navigation.navigateToChatMaterials
|
||||
import dev.meloda.fast.common.LongPollController
|
||||
import dev.meloda.fast.common.NetworkStateListener
|
||||
import dev.meloda.fast.common.model.LongPollState
|
||||
import dev.meloda.fast.convos.navigation.createChatScreen
|
||||
import dev.meloda.fast.convos.navigation.navigateToCreateChat
|
||||
@@ -64,6 +64,7 @@ import dev.meloda.fast.settings.navigation.navigateToSettings
|
||||
import dev.meloda.fast.settings.navigation.settingsScreen
|
||||
import dev.meloda.fast.ui.R
|
||||
import dev.meloda.fast.ui.common.LocalLogger
|
||||
import dev.meloda.fast.ui.common.LocalNetworkState
|
||||
import dev.meloda.fast.ui.common.LocalSizeConfig
|
||||
import dev.meloda.fast.ui.model.DeviceSize
|
||||
import dev.meloda.fast.ui.model.SizeConfig
|
||||
@@ -93,7 +94,7 @@ fun RootScreen(
|
||||
val longPollCurrentState by longPollController.currentState.collectAsStateWithLifecycle()
|
||||
val longPollStateToApply by longPollController.stateToApply.collectAsStateWithLifecycle()
|
||||
|
||||
val viewModel: MainViewModel = koinViewModel<MainViewModelImpl>()
|
||||
val viewModel: MainViewModel = koinViewModel()
|
||||
val currentUser: VkUser? by viewModel.currentUser.collectAsStateWithLifecycle()
|
||||
|
||||
val permissionState =
|
||||
@@ -221,10 +222,17 @@ fun RootScreen(
|
||||
}
|
||||
}
|
||||
|
||||
val networkStateListener: NetworkStateListener = koinInject()
|
||||
val networkState by networkStateListener.networkStateFlow.collectAsStateWithLifecycle()
|
||||
LaunchedEffect(networkState) {
|
||||
logger.debug("RootScreen", "NetworkState: $networkState")
|
||||
}
|
||||
|
||||
CompositionLocalProvider(
|
||||
LocalThemeConfig provides themeConfig,
|
||||
LocalSizeConfig provides sizeConfig,
|
||||
LocalUser provides currentUser
|
||||
LocalUser provides currentUser,
|
||||
LocalNetworkState provides networkState
|
||||
) {
|
||||
AppTheme(
|
||||
useDarkTheme = themeConfig.darkMode,
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
package dev.meloda.fast.common
|
||||
|
||||
import dev.meloda.fast.common.model.NetworkState
|
||||
import dev.meloda.fast.common.model.NetworkStatus
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
|
||||
class NetworkStateListener {
|
||||
|
||||
private val networkStatus = MutableStateFlow(NetworkStatus.UNAVAILABLE)
|
||||
val networkStatusFlow = networkStatus.asStateFlow()
|
||||
|
||||
private val networkState = MutableStateFlow(NetworkState.DISCONNECTED)
|
||||
val networkStateFlow = networkState.asStateFlow()
|
||||
|
||||
fun updateNetworkState(state: NetworkState) {
|
||||
networkState.value = state
|
||||
networkStatus.value = when (state) {
|
||||
NetworkState.CONNECTED -> NetworkStatus.AVAILABLE
|
||||
NetworkState.DISCONNECTED -> NetworkStatus.UNAVAILABLE
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
package dev.meloda.fast.common.model
|
||||
|
||||
enum class NetworkState { CONNECTED, DISCONNECTED }
|
||||
@@ -0,0 +1,10 @@
|
||||
package dev.meloda.fast.common.model
|
||||
|
||||
enum class NetworkStatus {
|
||||
AVAILABLE, UNAVAILABLE, LOST, BLOCKED, UNBLOCKED;
|
||||
|
||||
fun isOk(): Boolean = when (this) {
|
||||
AVAILABLE, UNBLOCKED -> true
|
||||
UNAVAILABLE, LOST, BLOCKED -> false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package dev.meloda.fast.common.model
|
||||
|
||||
enum class NetworkType {
|
||||
CELLULAR, WIFI, UNKNOWN
|
||||
}
|
||||
@@ -3,9 +3,7 @@ package dev.meloda.fast.common.provider
|
||||
import android.content.res.Resources
|
||||
|
||||
interface ResourceProvider {
|
||||
|
||||
val resources: Resources
|
||||
|
||||
fun getString(resId: Int): String
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
package dev.meloda.fast.model
|
||||
|
||||
import dev.meloda.fast.model.database.AccountEntity
|
||||
|
||||
data class AccountDto(
|
||||
val userId: Long,
|
||||
val accessToken: String,
|
||||
val fastToken: String?,
|
||||
val trustedHash: String?,
|
||||
val exchangeToken: String?
|
||||
) {
|
||||
|
||||
fun mapToEntity(): AccountEntity = AccountEntity(
|
||||
userId = userId,
|
||||
accessToken = accessToken,
|
||||
fastToken = fastToken,
|
||||
trustedHash = trustedHash,
|
||||
exchangeToken = exchangeToken
|
||||
)
|
||||
|
||||
override fun toString(): String {
|
||||
return super.toString()
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package dev.meloda.fast.model.database
|
||||
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
import dev.meloda.fast.model.AccountDto
|
||||
|
||||
@Entity(tableName = "accounts")
|
||||
data class AccountEntity(
|
||||
@@ -11,4 +12,12 @@ data class AccountEntity(
|
||||
val fastToken: String?,
|
||||
val trustedHash: String?,
|
||||
val exchangeToken: String?
|
||||
)
|
||||
) {
|
||||
fun mapToDto(): AccountDto = AccountDto(
|
||||
userId = userId,
|
||||
accessToken = accessToken,
|
||||
fastToken = fastToken,
|
||||
trustedHash = trustedHash,
|
||||
exchangeToken = exchangeToken
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
package dev.meloda.fast.ui.common
|
||||
|
||||
import androidx.compose.runtime.compositionLocalOf
|
||||
import dev.meloda.fast.common.model.NetworkState
|
||||
|
||||
val LocalNetworkState = compositionLocalOf { NetworkState.DISCONNECTED }
|
||||
@@ -189,6 +189,7 @@
|
||||
<string name="title_application_language">Application language</string>
|
||||
<string name="action_refresh">Refresh</string>
|
||||
<string name="title_loading">Loading…</string>
|
||||
<string name="title_no_network">No network…</string>
|
||||
<string name="title_convos">Conversations</string>
|
||||
<string name="title_archive">Archive</string>
|
||||
<string name="title_friends">Friends</string>
|
||||
|
||||
@@ -28,7 +28,7 @@ import dev.meloda.fast.datastore.UserSettings
|
||||
import dev.meloda.fast.domain.LoadUserByIdUseCase
|
||||
import dev.meloda.fast.domain.OAuthUseCase
|
||||
import dev.meloda.fast.logger.FastLogger
|
||||
import dev.meloda.fast.model.database.AccountEntity
|
||||
import dev.meloda.fast.model.AccountDto
|
||||
import dev.meloda.fast.network.OAuthErrorDomain
|
||||
import kotlinx.coroutines.CoroutineExceptionHandler
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -236,7 +236,7 @@ class LoginViewModel(
|
||||
|
||||
// TODO: 30-Mar-25, Danil Nikolaev: get fast's app token
|
||||
|
||||
val currentAccount = AccountEntity(
|
||||
val currentAccount = AccountDto(
|
||||
userId = userId,
|
||||
accessToken = accessToken,
|
||||
fastToken = null,
|
||||
@@ -251,7 +251,7 @@ class LoginViewModel(
|
||||
UserConfig.exchangeToken = account.exchangeToken
|
||||
}
|
||||
|
||||
accountsRepository.storeAccounts(listOf(currentAccount))
|
||||
accountsRepository.storeAccounts(listOf(currentAccount.mapToEntity()))
|
||||
|
||||
startLongPoll()
|
||||
|
||||
|
||||
@@ -9,12 +9,14 @@ import androidx.lifecycle.viewModelScope
|
||||
import coil.ImageLoader
|
||||
import coil.request.ImageRequest
|
||||
import com.conena.nanokt.collections.indexOfFirstOrNull
|
||||
import dev.meloda.fast.common.NetworkStateListener
|
||||
import dev.meloda.fast.common.VkConstants
|
||||
import dev.meloda.fast.common.extensions.createTimerFlow
|
||||
import dev.meloda.fast.common.extensions.findWithIndex
|
||||
import dev.meloda.fast.common.extensions.listenValue
|
||||
import dev.meloda.fast.common.extensions.setValue
|
||||
import dev.meloda.fast.common.extensions.updateValue
|
||||
import dev.meloda.fast.common.model.NetworkState
|
||||
import dev.meloda.fast.convos.model.ConvoDialog
|
||||
import dev.meloda.fast.convos.model.ConvoIntent
|
||||
import dev.meloda.fast.convos.model.ConvoNavigationIntent
|
||||
@@ -31,6 +33,7 @@ import dev.meloda.fast.domain.LongPollEventsHandler
|
||||
import dev.meloda.fast.domain.MessagesUseCase
|
||||
import dev.meloda.fast.domain.util.asPresentation
|
||||
import dev.meloda.fast.domain.util.extractAvatar
|
||||
import dev.meloda.fast.logger.FastLogger
|
||||
import dev.meloda.fast.model.ConvosFilter
|
||||
import dev.meloda.fast.model.InteractionType
|
||||
import dev.meloda.fast.model.LongPollParsedEvent
|
||||
@@ -54,7 +57,9 @@ class ConvosViewModel(
|
||||
private val userSettings: UserSettings,
|
||||
private val imageLoader: ImageLoader,
|
||||
private val applicationContext: Context,
|
||||
private val loadConvosByIdUseCase: LoadConvosByIdUseCase
|
||||
private val loadConvosByIdUseCase: LoadConvosByIdUseCase,
|
||||
private val networkStateListener: NetworkStateListener,
|
||||
private val logger: FastLogger
|
||||
) : ViewModel() {
|
||||
|
||||
private val screenState = MutableStateFlow(ConvosScreenState.EMPTY)
|
||||
@@ -87,6 +92,16 @@ class ConvosViewModel(
|
||||
userSettings.useContactNames.listenValue(viewModelScope) {
|
||||
syncUiConvos()
|
||||
}
|
||||
|
||||
networkStateListener.networkStateFlow.listenValue { state ->
|
||||
logger.debug(this@ConvosViewModel::class, "network state changed: $state")
|
||||
|
||||
if (state == NetworkState.CONNECTED) {
|
||||
if (screenState.value.error != null) {
|
||||
onRefresh()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun handleIntent(intent: ConvoIntent) {
|
||||
@@ -193,12 +208,12 @@ class ConvosViewModel(
|
||||
loadConvos()
|
||||
}
|
||||
|
||||
private fun onErrorConsumed() {
|
||||
private fun clearError() {
|
||||
screenState.updateValue { copy(error = null) }
|
||||
}
|
||||
|
||||
private fun onRefresh() {
|
||||
onErrorConsumed()
|
||||
clearError()
|
||||
loadConvos(offset = 0)
|
||||
}
|
||||
|
||||
|
||||
@@ -32,6 +32,8 @@ private fun Scope.createConvosViewModel(filter: ConvosFilter): ConvosViewModel {
|
||||
userSettings = get(),
|
||||
imageLoader = get(),
|
||||
applicationContext = get(),
|
||||
loadConvosByIdUseCase = get()
|
||||
loadConvosByIdUseCase = get(),
|
||||
networkStateListener = get(),
|
||||
logger = get()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -56,11 +56,13 @@ import dev.chrisbanes.haze.hazeEffect
|
||||
import dev.chrisbanes.haze.hazeSource
|
||||
import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi
|
||||
import dev.chrisbanes.haze.materials.HazeMaterials
|
||||
import dev.meloda.fast.common.model.NetworkState
|
||||
import dev.meloda.fast.convos.model.ConvoIntent
|
||||
import dev.meloda.fast.convos.model.ConvosScreenState
|
||||
import dev.meloda.fast.convos.navigation.ConvoGraph
|
||||
import dev.meloda.fast.datastore.AppSettings
|
||||
import dev.meloda.fast.ui.R
|
||||
import dev.meloda.fast.ui.common.LocalNetworkState
|
||||
import dev.meloda.fast.ui.components.FullScreenContainedLoader
|
||||
import dev.meloda.fast.ui.components.NoItemsView
|
||||
import dev.meloda.fast.ui.components.SegmentedButtonItem
|
||||
@@ -156,6 +158,8 @@ fun ConvosScreen(
|
||||
animationSpec = tween(durationMillis = 50)
|
||||
)
|
||||
|
||||
val networkState = LocalNetworkState.current
|
||||
|
||||
Scaffold(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentWindowInsets = WindowInsets.statusBars,
|
||||
@@ -166,6 +170,7 @@ fun ConvosScreen(
|
||||
Text(
|
||||
text = stringResource(
|
||||
id = when {
|
||||
networkState == NetworkState.DISCONNECTED -> R.string.title_no_network
|
||||
screenState.isLoading -> R.string.title_loading
|
||||
isArchive -> R.string.title_archive
|
||||
else -> R.string.title_convos
|
||||
|
||||
@@ -26,6 +26,7 @@ import dev.meloda.fast.datastore.UserSettings
|
||||
import dev.meloda.fast.domain.GetCurrentAccountUseCase
|
||||
import dev.meloda.fast.domain.LoadUserByIdUseCase
|
||||
import dev.meloda.fast.logger.FastLogger
|
||||
import dev.meloda.fast.model.AccountDto
|
||||
import dev.meloda.fast.model.database.AccountEntity
|
||||
import dev.meloda.fast.settings.model.HapticType
|
||||
import dev.meloda.fast.settings.model.SettingsDialog
|
||||
@@ -149,13 +150,14 @@ class SettingsViewModel(
|
||||
UserConfig.currentUserId = user.id
|
||||
|
||||
val account = getCurrentAccountUseCase()
|
||||
?.mapToDto()
|
||||
?.copy(
|
||||
userId = user.id,
|
||||
accessToken = accessToken,
|
||||
fastToken = null,
|
||||
exchangeToken = exchangeToken,
|
||||
trustedHash = trustedHash
|
||||
) ?: AccountEntity(
|
||||
) ?: AccountDto(
|
||||
userId = user.id,
|
||||
accessToken = accessToken,
|
||||
fastToken = null,
|
||||
@@ -163,7 +165,7 @@ class SettingsViewModel(
|
||||
exchangeToken = exchangeToken
|
||||
)
|
||||
|
||||
accountsRepository.storeAccounts(listOf(account))
|
||||
accountsRepository.storeAccounts(listOf(account.mapToEntity()))
|
||||
|
||||
screenEffect.tryEmit(
|
||||
SettingsEffect.Navigate(SettingsNavigationIntent.Restart)
|
||||
|
||||
Reference in New Issue
Block a user