a lot of improvements for long polling service and notifications
This commit is contained in:
@@ -7,4 +7,7 @@ object AppConstants {
|
||||
const val API_VERSION = "5.173"
|
||||
const val URL_OAUTH = "https://oauth.vk.com"
|
||||
const val URL_API = "https://api.vk.com/method"
|
||||
|
||||
const val NOTIFICATION_CHANNEL_UNCATEGORIZED = "uncategorized"
|
||||
const val NOTIFICATION_CHANNEL_LONG_POLLING = "long_polling"
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import androidx.annotation.ChecksSdkIntAtLeast
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
@@ -141,6 +142,7 @@ fun <T> Any.toList(mapper: (old: Any) -> T): List<T> {
|
||||
}
|
||||
}
|
||||
|
||||
@ChecksSdkIntAtLeast(parameter = 0, lambda = 1)
|
||||
fun isSdkAtLeast(sdkInt: Int, action: (() -> Unit)? = null): Boolean {
|
||||
return if (Build.VERSION.SDK_INT >= sdkInt) {
|
||||
action?.invoke()
|
||||
|
||||
@@ -6,5 +6,7 @@ interface AccountsRepository {
|
||||
|
||||
suspend fun getAccounts(): List<AccountEntity>
|
||||
|
||||
suspend fun getAccountById(userId: Int): AccountEntity?
|
||||
|
||||
suspend fun storeAccounts(accounts: List<AccountEntity>)
|
||||
}
|
||||
|
||||
@@ -9,6 +9,9 @@ class AccountsRepositoryImpl(
|
||||
|
||||
override suspend fun getAccounts(): List<AccountEntity> = accountDao.getAll()
|
||||
|
||||
override suspend fun getAccountById(userId: Int): AccountEntity? =
|
||||
accountDao.getById(userId)
|
||||
|
||||
override suspend fun storeAccounts(
|
||||
accounts: List<AccountEntity>
|
||||
) = accountDao.insertAll(accounts)
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.meloda.app.fast.data.db
|
||||
|
||||
import com.meloda.app.fast.common.UserConfig
|
||||
import com.meloda.app.fast.model.database.AccountEntity
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
class GetCurrentAccountUseCase(private val accountsRepository: AccountsRepository) {
|
||||
|
||||
suspend operator fun invoke(): AccountEntity? = withContext(Dispatchers.IO) {
|
||||
accountsRepository.getAccountById(UserConfig.currentUserId)
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,7 @@ import com.meloda.app.fast.data.api.users.UsersUseCaseImpl
|
||||
import com.meloda.app.fast.data.api.videos.VideosRepository
|
||||
import com.meloda.app.fast.data.db.AccountsRepository
|
||||
import com.meloda.app.fast.data.db.AccountsRepositoryImpl
|
||||
import com.meloda.app.fast.data.db.GetCurrentAccountUseCase
|
||||
import com.meloda.app.fast.database.di.databaseModule
|
||||
import com.meloda.app.fast.datastore.di.dataStoreModule
|
||||
import com.meloda.app.fast.network.di.networkModule
|
||||
@@ -67,6 +68,7 @@ val dataModule = module {
|
||||
singleOf(::VideosRepository)
|
||||
|
||||
singleOf(::AccountsRepositoryImpl) bind AccountsRepository::class
|
||||
singleOf(::GetCurrentAccountUseCase)
|
||||
|
||||
singleOf(::FriendsRepositoryImpl) bind FriendsRepository::class
|
||||
}
|
||||
|
||||
@@ -10,6 +10,9 @@ abstract class AccountDao : EntityDao<AccountEntity> {
|
||||
@Query("SELECT * FROM accounts")
|
||||
abstract suspend fun getAll(): List<AccountEntity>
|
||||
|
||||
@Query("SELECT * FROM accounts WHERE userId = :userId")
|
||||
abstract suspend fun getById(userId: Int): AccountEntity?
|
||||
|
||||
@Query("DELETE FROM accounts WHERE userId = :userId")
|
||||
abstract suspend fun deleteById(userId: Int)
|
||||
}
|
||||
|
||||
@@ -50,4 +50,15 @@ object SettingsController {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var isLongPollInBackgroundEnabled: Boolean =
|
||||
SettingsKeys.DEFAULT_VALUE_FEATURES_LONG_POLL_IN_BACKGROUND
|
||||
get() = getBoolean(
|
||||
SettingsKeys.KEY_FEATURES_LONG_POLL_IN_BACKGROUND,
|
||||
SettingsKeys.DEFAULT_VALUE_FEATURES_LONG_POLL_IN_BACKGROUND
|
||||
)
|
||||
set(value) {
|
||||
field = value
|
||||
put(SettingsKeys.KEY_FEATURES_LONG_POLL_IN_BACKGROUND, value)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ package com.meloda.app.fast.datastore
|
||||
|
||||
import android.content.res.Resources
|
||||
import android.os.PowerManager
|
||||
import android.util.Log
|
||||
import com.meloda.app.fast.datastore.model.LongPollState
|
||||
import com.meloda.app.fast.datastore.model.ThemeConfig
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
@@ -9,7 +11,8 @@ import kotlinx.coroutines.flow.update
|
||||
|
||||
interface UserSettings {
|
||||
val theme: StateFlow<ThemeConfig>
|
||||
val longPollBackground: StateFlow<Boolean>
|
||||
val longPollStateToApply: StateFlow<LongPollState>
|
||||
val longPollCurrentState: StateFlow<LongPollState>
|
||||
val online: StateFlow<Boolean>
|
||||
val debugSettingsEnabled: StateFlow<Boolean>
|
||||
val useContactNames: StateFlow<Boolean>
|
||||
@@ -21,7 +24,8 @@ interface UserSettings {
|
||||
fun useDynamicColorsChanged(use: Boolean)
|
||||
fun useBlurChanged(use: Boolean)
|
||||
fun useMultiline(use: Boolean)
|
||||
fun setLongPollBackground(background: Boolean)
|
||||
fun setLongPollStateToApply(newState: LongPollState)
|
||||
fun updateLongPollCurrentState(currentState: LongPollState)
|
||||
fun setOnline(use: Boolean)
|
||||
fun enableDebugSettings(enable: Boolean)
|
||||
fun onUseContactNamesChanged(use: Boolean)
|
||||
@@ -45,12 +49,9 @@ class UserSettingsImpl(
|
||||
)
|
||||
)
|
||||
|
||||
override val longPollBackground = MutableStateFlow(
|
||||
SettingsController.getBoolean(
|
||||
SettingsKeys.KEY_FEATURES_LONG_POLL_IN_BACKGROUND,
|
||||
SettingsKeys.DEFAULT_VALUE_FEATURES_LONG_POLL_IN_BACKGROUND
|
||||
)
|
||||
)
|
||||
override val longPollStateToApply = MutableStateFlow<LongPollState>(LongPollState.Stopped)
|
||||
override val longPollCurrentState = MutableStateFlow<LongPollState>(LongPollState.Stopped)
|
||||
|
||||
override val online = MutableStateFlow(
|
||||
SettingsController.getBoolean(
|
||||
SettingsKeys.KEY_ACTIVITY_SEND_ONLINE_STATUS,
|
||||
@@ -107,8 +108,14 @@ class UserSettingsImpl(
|
||||
theme.value = theme.value.copy(multiline = use)
|
||||
}
|
||||
|
||||
override fun setLongPollBackground(background: Boolean) {
|
||||
longPollBackground.value = background
|
||||
override fun setLongPollStateToApply(newState: LongPollState) {
|
||||
longPollStateToApply.update { newState }
|
||||
Log.d("UserSettings", "setLongPollState: $newState")
|
||||
}
|
||||
|
||||
override fun updateLongPollCurrentState(currentState: LongPollState) {
|
||||
longPollCurrentState.update { currentState }
|
||||
Log.d("UserSettings", "updateLongPollCurrentState: $currentState")
|
||||
}
|
||||
|
||||
override fun setOnline(use: Boolean) {
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.meloda.app.fast.datastore.model
|
||||
|
||||
sealed class LongPollState {
|
||||
data object Stopped : LongPollState()
|
||||
|
||||
// TODO: 15/07/2024, Danil Nikolaev: support for android 15
|
||||
// data object Terminated : LongPollState()
|
||||
data object InApp : LongPollState()
|
||||
data object Background : LongPollState()
|
||||
data object Exception : LongPollState()
|
||||
|
||||
|
||||
fun isLaunched(): Boolean = this in listOf(InApp, Background)
|
||||
}
|
||||
@@ -4,15 +4,11 @@ import android.content.res.Configuration
|
||||
import android.view.KeyEvent
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.input.key.onKeyEvent
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.pluralStringResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
||||
import com.google.accompanist.permissions.PermissionState
|
||||
import com.google.accompanist.permissions.PermissionStatus
|
||||
import com.meloda.app.fast.common.UiText
|
||||
import com.meloda.app.fast.common.util.AndroidUtils
|
||||
import com.meloda.app.fast.datastore.SettingsController
|
||||
@@ -81,32 +77,3 @@ fun Modifier.handleEnterKey(
|
||||
action.invoke()
|
||||
} else false
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalPermissionsApi::class)
|
||||
@Composable
|
||||
fun CheckPermission(
|
||||
showRationale: @Composable () -> Unit,
|
||||
onDenied: @Composable () -> Unit,
|
||||
permission: PermissionState,
|
||||
) {
|
||||
when (val status = permission.status) {
|
||||
is PermissionStatus.Denied -> {
|
||||
if (status.shouldShowRationale) {
|
||||
showRationale()
|
||||
} else {
|
||||
onDenied()
|
||||
}
|
||||
}
|
||||
|
||||
is PermissionStatus.Granted -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalPermissionsApi::class)
|
||||
@Composable
|
||||
fun RequestPermission(
|
||||
permission: PermissionState
|
||||
) {
|
||||
LaunchedEffect(Unit) { permission.launchPermissionRequest() }
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,10 @@ class ImmutableList<T>(val values: List<T>) : Iterable<T> {
|
||||
return values.map(transform).toImmutableList()
|
||||
}
|
||||
|
||||
inline fun <R> mapNotNull(transform: (T) -> R?): ImmutableList<R> {
|
||||
return values.mapNotNull(transform).toImmutableList()
|
||||
}
|
||||
|
||||
inline fun <R> mapIndexed(transform: (index: Int, T) -> R): ImmutableList<R> {
|
||||
return values.mapIndexed(transform).toImmutableList()
|
||||
}
|
||||
|
||||
+28
-25
@@ -1,11 +1,5 @@
|
||||
package com.meloda.app.fast.designsystem
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.AnimatedVisibilityScope
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.scaleIn
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ColumnScope
|
||||
@@ -32,15 +26,19 @@ import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.DialogProperties
|
||||
import com.meloda.app.fast.common.UiText
|
||||
import com.meloda.app.fast.common.parseString
|
||||
import com.meloda.app.fast.designsystem.ImmutableList.Companion.toImmutableList
|
||||
|
||||
// TODO: 08.04.2023, Danil Nikolaev: review
|
||||
// TODO: 08.04.2023, Danil Nikolaev: refactor this
|
||||
@Deprecated("need refactoring")
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun MaterialDialog(
|
||||
@@ -58,17 +56,22 @@ fun MaterialDialog(
|
||||
items: ImmutableList<UiText> = ImmutableList.empty(),
|
||||
onItemClick: ((index: Int) -> Unit)? = null,
|
||||
buttonsInvokeDismiss: Boolean = true,
|
||||
properties: DialogProperties = DialogProperties(),
|
||||
customContent: (@Composable ColumnScope.() -> Unit)? = null,
|
||||
) {
|
||||
var isVisible by remember {
|
||||
var isVisible by rememberSaveable {
|
||||
mutableStateOf(true)
|
||||
}
|
||||
val onDismissRequest = {
|
||||
onDismissAction.invoke()
|
||||
isVisible = false
|
||||
val onDismissRequest = remember {
|
||||
{
|
||||
onDismissAction.invoke()
|
||||
isVisible = false
|
||||
}
|
||||
}
|
||||
|
||||
val stringTitles = items.map { it.getString().orEmpty() }
|
||||
val context = LocalContext.current
|
||||
|
||||
val stringTitles = items.mapNotNull { it.parseString(context.resources) }
|
||||
|
||||
var alertItems by remember {
|
||||
mutableStateOf(
|
||||
@@ -81,11 +84,10 @@ fun MaterialDialog(
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
if (isVisible) {
|
||||
// AlertAnimation(visible = isVisible) {
|
||||
AlertAnimation(visible = isVisible) {
|
||||
BasicAlertDialog(
|
||||
onDismissRequest = onDismissRequest
|
||||
onDismissRequest = onDismissRequest,
|
||||
properties = properties
|
||||
) {
|
||||
val scrollState = rememberScrollState()
|
||||
val canScrollBackward by remember { derivedStateOf { scrollState.value > 0 } }
|
||||
@@ -251,15 +253,16 @@ fun MaterialDialog(
|
||||
@Composable
|
||||
fun AlertAnimation(
|
||||
visible: Boolean,
|
||||
content: @Composable AnimatedVisibilityScope.() -> Unit
|
||||
content: @Composable () -> Unit
|
||||
) {
|
||||
AnimatedVisibility(
|
||||
visible = visible,
|
||||
enter = fadeIn(animationSpec = tween(400)) +
|
||||
scaleIn(animationSpec = tween(400)),
|
||||
exit = fadeOut(animationSpec = tween(150)),
|
||||
content = content
|
||||
)
|
||||
if (visible) content()
|
||||
// AnimatedVisibility(
|
||||
// visible = visible,
|
||||
// enter = fadeIn(animationSpec = tween(400)) +
|
||||
// scaleIn(animationSpec = tween(400)),
|
||||
// exit = fadeOut(animationSpec = tween(150)),
|
||||
// content = content
|
||||
// )
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
||||
@@ -188,4 +188,14 @@
|
||||
<string name="settings_activity_send_online_title">Быть «в сети»</string>
|
||||
<string name="settings_activity_send_online_summary">Статус «в сети» будет отправляться каждые 5 минут</string>
|
||||
<string name="settings_debug_title">Отладка</string>
|
||||
<string name="action_disable">Отключить</string>
|
||||
<string name="background_long_poll_rationale_text">Приложение не сможет обновлять сообщения в фоне без доступа к уведомлениям</string>
|
||||
<string name="background_long_poll_denied_text">Приложению нужен доступ к уведомлениям для обновления сообщений в фоне</string>
|
||||
<string name="action_request">Запросить</string>
|
||||
<string name="long_polling_service_notification_title">Обновление сообщений в фоне</string>
|
||||
<string name="long_polling_service_notification_content">Нажмите, чтобы скрыть</string>
|
||||
<string name="notification_channel_no_category_name">Без категории</string>
|
||||
<string name="notification_channel_no_category_description">Уведомления без категории</string>
|
||||
<string name="notification_channel_long_polling_service_name">Сервис обновления сообщений</string>
|
||||
<string name="notification_channel_long_polling_service_description">Уведомления сервиса обновлений сообщений</string>
|
||||
</resources>
|
||||
|
||||
@@ -246,5 +246,17 @@
|
||||
<string name="settings_activity_send_online_title">Send online status</string>
|
||||
<string name="settings_activity_send_online_summary">Online status will be sent every five minutes</string>
|
||||
<string name="settings_debug_title">Debug</string>
|
||||
<string name="background_long_poll_rationale_text">The app won\'t be able to update messages in the background without access to notifications</string>
|
||||
<string name="action_disable">Disable</string>
|
||||
<string name="background_long_poll_denied_text">The app needs access to notifications to update messages in the background</string>
|
||||
<string name="action_request">Request</string>
|
||||
|
||||
</resources>
|
||||
<string name="long_polling_service_notification_title">Updating messages in background</string>
|
||||
<string name="long_polling_service_notification_content">Tap to hide</string>
|
||||
|
||||
<string name="notification_channel_no_category_name">Uncategorized</string>
|
||||
<string name="notification_channel_no_category_description">Uncategorized notifications</string>
|
||||
|
||||
<string name="notification_channel_long_polling_service_name">Message update service</string>
|
||||
<string name="notification_channel_long_polling_service_description">Message update service notifications</string>
|
||||
</resources>
|
||||
|
||||
Reference in New Issue
Block a user