twoFa -> validation naming; fixes for preview for screens (separating view model from ui); some improvements & fixes

This commit is contained in:
2024-07-13 22:45:49 +03:00
parent dfdc48b682
commit 733627f935
98 changed files with 1611 additions and 1637 deletions
@@ -4,6 +4,7 @@ import android.content.SharedPreferences
import android.content.res.Resources
import android.util.Log
import androidx.core.content.edit
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.conena.nanokt.collections.indexOfFirstOrNull
@@ -20,11 +21,10 @@ import com.meloda.app.fast.data.api.messages.MessagesUseCase
import com.meloda.app.fast.data.processState
import com.meloda.app.fast.datastore.SettingsKeys
import com.meloda.app.fast.messageshistory.model.ActionMode
import com.meloda.app.fast.messageshistory.model.MessagesHistoryArguments
import com.meloda.app.fast.messageshistory.model.MessagesHistoryScreenState
import com.meloda.app.fast.messageshistory.navigation.MessagesHistory
import com.meloda.app.fast.messageshistory.util.asPresentation
import com.meloda.app.fast.messageshistory.util.extractAvatar
import com.meloda.app.fast.messageshistory.util.extractShowName
import com.meloda.app.fast.messageshistory.util.extractTitle
import com.meloda.app.fast.model.BaseError
import com.meloda.app.fast.model.LongPollEvent
@@ -48,17 +48,14 @@ interface MessagesHistoryViewModel {
val canPaginate: StateFlow<Boolean>
fun onRefresh()
fun onAttachmentButtonClicked()
fun onInputChanged(newText: String)
fun onMessageInputChanged(newText: String)
fun onEmojiButtonClicked()
fun onActionButtonClicked()
fun onTopAppBarMenuClicked(id: Int)
fun setArguments(arguments: MessagesHistoryArguments)
fun onMetPaginationCondition()
fun onShowDatesClicked(showDates: Boolean)
fun onShowNamesClicked(showNames: Boolean)
fun onEnableAnimationsClicked(enableAnimations: Boolean)
fun onPaginationConditionsMet()
fun onToggleAnimationsDropdownItemClicked(enableAnimations: Boolean)
}
class MessagesHistoryViewModelImpl(
@@ -67,6 +64,7 @@ class MessagesHistoryViewModelImpl(
private val preferences: SharedPreferences,
private val resources: Resources,
updatesParser: LongPollUpdatesParser,
savedStateHandle: SavedStateHandle
) : MessagesHistoryViewModel, ViewModel() {
override val screenState = MutableStateFlow(MessagesHistoryScreenState.EMPTY)
@@ -85,17 +83,26 @@ class MessagesHistoryViewModelImpl(
private val sendingMessages: MutableList<VkMessage> = mutableListOf()
init {
val arguments = MessagesHistory.from(savedStateHandle).arguments
screenState.setValue { old -> old.copy(conversationId = arguments.conversationId) }
loadMessagesHistory()
updatesParser.onNewMessage(::handleNewMessage)
updatesParser.onMessageEdited(::handleEditedMessage)
updatesParser.onMessageIncomingRead(::handleReadIncomingEvent)
updatesParser.onMessageOutgoingRead(::handleReadOutgoingEvent)
}
override fun onRefresh() {
loadMessagesHistory(offset = 0)
}
override fun onAttachmentButtonClicked() {
}
override fun onInputChanged(newText: String) {
override fun onMessageInputChanged(newText: String) {
screenState.setValue { old ->
old.copy(
message = newText,
@@ -131,58 +138,12 @@ class MessagesHistoryViewModelImpl(
}
}
override fun onTopAppBarMenuClicked(id: Int) {
when (id) {
0 -> loadMessagesHistory(0)
else -> Unit
}
}
override fun setArguments(arguments: MessagesHistoryArguments) {
if (arguments.conversationId == screenState.value.conversationId) return
screenState.setValue { old -> old.copy(conversationId = arguments.conversationId) }
loadMessagesHistory()
}
override fun onMetPaginationCondition() {
override fun onPaginationConditionsMet() {
currentOffset.update { screenState.value.messages.size }
loadMessagesHistory()
}
override fun onShowDatesClicked(showDates: Boolean) {
preferences.edit { putBoolean(SettingsKeys.KEY_SHOW_DATE_UNDER_BUBBLES, showDates) }
screenState.setValue { old ->
old.copy(
messages = old.messages.map { message ->
message.copy(showDate = showDates)
}
)
}
}
override fun onShowNamesClicked(showNames: Boolean) {
preferences.edit { putBoolean(SettingsKeys.KEY_SHOW_NAME_IN_BUBBLES, showNames) }
screenState.setValue { old ->
old.copy(
messages = old.messages.map { message ->
message.copy(
showName = if (showNames) {
val index = messages.value.indexOfFirst { it.id == message.id }
val domainMessage = messages.value[index]
val prevMessage = messages.value.getOrNull(index + 1)
domainMessage.extractShowName(prevMessage)
} else false
)
}
)
}
}
override fun onEnableAnimationsClicked(enableAnimations: Boolean) {
override fun onToggleAnimationsDropdownItemClicked(enableAnimations: Boolean) {
preferences.edit {
putBoolean(
SettingsKeys.KEY_ENABLE_ANIMATIONS_IN_MESSAGES,
@@ -285,15 +246,10 @@ class MessagesHistoryViewModelImpl(
messagesUseCase.storeMessages(messages)
conversationsUseCase.storeConversations(conversations)
val showDate =
preferences.getBoolean(SettingsKeys.KEY_SHOW_DATE_UNDER_BUBBLES, false)
val showName =
preferences.getBoolean(SettingsKeys.KEY_SHOW_NAME_IN_BUBBLES, false)
val loadedMessages = fullMessages.mapIndexed { index, message ->
message.asPresentation(
showDate = showDate,
showName = showName,
showDate = false,
showName = false,
prevMessage = messages.getOrNull(index + 1),
nextMessage = messages.getOrNull(index - 1),
)
@@ -0,0 +1,43 @@
package com.meloda.app.fast.messageshistory.navigation
import androidx.lifecycle.SavedStateHandle
import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder
import androidx.navigation.compose.composable
import androidx.navigation.toRoute
import com.meloda.app.fast.common.customNavType
import com.meloda.app.fast.messageshistory.model.MessagesHistoryArguments
import com.meloda.app.fast.messageshistory.presentation.MessagesHistoryRoute
import com.meloda.app.fast.model.BaseError
import kotlinx.serialization.Serializable
import kotlin.reflect.typeOf
@Serializable
data class MessagesHistory(val arguments: MessagesHistoryArguments) {
companion object {
val typeMap =
mapOf(typeOf<MessagesHistoryArguments>() to customNavType<MessagesHistoryArguments>())
fun from(savedStateHandle: SavedStateHandle) =
savedStateHandle.toRoute<MessagesHistory>(typeMap)
}
}
fun NavGraphBuilder.messagesHistoryScreen(
onError: (BaseError) -> Unit,
onBack: () -> Unit,
onChatMaterialsDropdownItemClicked: (peerId: Int, conversationMessageId: Int) -> Unit
) {
composable<MessagesHistory>(typeMap = MessagesHistory.typeMap) {
MessagesHistoryRoute(
onError = onError,
onBack = onBack,
onChatMaterialsDropdownItemClicked = onChatMaterialsDropdownItemClicked,
)
}
}
fun NavController.navigateToMessagesHistory(conversationId: Int) {
this.navigate(MessagesHistory(MessagesHistoryArguments(conversationId)))
}
@@ -1,64 +0,0 @@
package com.meloda.app.fast.messageshistory.navigation
import android.os.Bundle
import androidx.core.os.BundleCompat
import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavType
import androidx.navigation.compose.composable
import androidx.navigation.toRoute
import com.meloda.app.fast.messageshistory.MessagesHistoryViewModel
import com.meloda.app.fast.messageshistory.MessagesHistoryViewModelImpl
import com.meloda.app.fast.messageshistory.model.MessagesHistoryArguments
import com.meloda.app.fast.messageshistory.presentation.MessagesHistoryScreen
import com.meloda.app.fast.model.BaseError
import kotlinx.serialization.Serializable
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import org.koin.androidx.compose.koinViewModel
import kotlin.reflect.typeOf
@Serializable
data class MessagesHistory(val arguments: MessagesHistoryArguments)
val MessagesHistoryNavType = object : NavType<MessagesHistoryArguments>(isNullableAllowed = false) {
override fun get(bundle: Bundle, key: String): MessagesHistoryArguments? =
BundleCompat.getParcelable(bundle, key, MessagesHistoryArguments::class.java)
override fun parseValue(value: String): MessagesHistoryArguments = Json.decodeFromString(value)
override fun serializeAsValue(value: MessagesHistoryArguments): String =
Json.encodeToString(value)
override fun put(bundle: Bundle, key: String, value: MessagesHistoryArguments) {
bundle.putParcelable(key, value)
}
override val name: String = "MessagesHistoryArguments"
}
fun NavGraphBuilder.messagesHistoryRoute(
onError: (BaseError) -> Unit,
onBack: () -> Unit,
onNavigateToChatAttachments: (peerId: Int, conversationMessageId: Int) -> Unit
) {
composable<MessagesHistory>(
typeMap = mapOf(typeOf<MessagesHistoryArguments>() to MessagesHistoryNavType)
) { backStackEntry ->
val arguments: MessagesHistory = backStackEntry.toRoute()
val viewModel: MessagesHistoryViewModel = koinViewModel<MessagesHistoryViewModelImpl>()
viewModel.setArguments(arguments.arguments)
MessagesHistoryScreen(
onError = onError,
onBack = onBack,
onNavigateToChatMaterials = onNavigateToChatAttachments,
viewModel = viewModel
)
}
}
fun NavController.navigateToMessagesHistory(conversationId: Int) {
this.navigate(MessagesHistory(MessagesHistoryArguments(conversationId)))
}
@@ -71,6 +71,7 @@ import com.meloda.app.fast.designsystem.LocalTheme
import com.meloda.app.fast.messageshistory.MessagesHistoryViewModel
import com.meloda.app.fast.messageshistory.MessagesHistoryViewModelImpl
import com.meloda.app.fast.messageshistory.model.ActionMode
import com.meloda.app.fast.messageshistory.model.MessagesHistoryScreenState
import com.meloda.app.fast.model.BaseError
import dev.chrisbanes.haze.HazeState
import dev.chrisbanes.haze.hazeChild
@@ -81,6 +82,32 @@ import org.koin.androidx.compose.koinViewModel
import org.koin.compose.koinInject
import com.meloda.app.fast.designsystem.R as UiR
@Composable
fun MessagesHistoryRoute(
onError: (BaseError) -> Unit,
onBack: () -> Unit,
onChatMaterialsDropdownItemClicked: (peerId: Int, conversationMessageId: Int) -> Unit,
viewModel: MessagesHistoryViewModel = koinViewModel<MessagesHistoryViewModelImpl>()
) {
val screenState by viewModel.screenState.collectAsStateWithLifecycle()
val baseError by viewModel.baseError.collectAsStateWithLifecycle()
val canPaginate by viewModel.canPaginate.collectAsStateWithLifecycle()
MessagesHistoryScreen(
screenState = screenState,
baseError = baseError,
canPaginate = canPaginate,
onBack = onBack,
onChatMaterialsDropdownItemClicked = onChatMaterialsDropdownItemClicked,
onRefreshDropdownItemClicked = viewModel::onRefresh,
onToggleAnimationsDropdownItemClicked = viewModel::onToggleAnimationsDropdownItemClicked,
onPaginationConditionsMet = viewModel::onPaginationConditionsMet,
onMessageInputChanged = viewModel::onMessageInputChanged,
onAttachmentButtonClicked = viewModel::onAttachmentButtonClicked,
onActionButtonClicked = viewModel::onActionButtonClicked
)
}
@OptIn(
ExperimentalMaterial3Api::class,
ExperimentalHazeMaterialsApi::class,
@@ -88,21 +115,23 @@ import com.meloda.app.fast.designsystem.R as UiR
)
@Composable
fun MessagesHistoryScreen(
onError: (BaseError) -> Unit,
onBack: () -> Unit,
onNavigateToChatMaterials: (peerId: Int, conversationMessageId: Int) -> Unit,
viewModel: MessagesHistoryViewModel = koinViewModel<MessagesHistoryViewModelImpl>()
screenState: MessagesHistoryScreenState = MessagesHistoryScreenState.EMPTY,
baseError: BaseError? = null,
canPaginate: Boolean = false,
onBack: () -> Unit = {},
onChatMaterialsDropdownItemClicked: (peerId: Int, conversationMessageId: Int) -> Unit = { _, _ -> },
onRefreshDropdownItemClicked: () -> Unit = {},
onToggleAnimationsDropdownItemClicked: (Boolean) -> Unit = {},
onPaginationConditionsMet: () -> Unit = {},
onMessageInputChanged: (String) -> Unit = {},
onAttachmentButtonClicked: () -> Unit = {},
onActionButtonClicked: () -> Unit = {}
) {
val view = LocalView.current
val preferences: SharedPreferences = koinInject()
val currentTheme = LocalTheme.current
val screenState by viewModel.screenState.collectAsStateWithLifecycle()
val canPaginate by viewModel.canPaginate.collectAsStateWithLifecycle()
val messages = screenState.messages
val listState = rememberLazyListState()
val paginationConditionMet by remember {
@@ -115,7 +144,7 @@ fun MessagesHistoryScreen(
LaunchedEffect(paginationConditionMet) {
if (paginationConditionMet && !screenState.isPaginating) {
viewModel.onMetPaginationCondition()
onPaginationConditionsMet()
}
}
@@ -125,24 +154,6 @@ fun MessagesHistoryScreen(
val hazeSate = remember { HazeState() }
var datesShown by remember {
mutableStateOf(
preferences.getBoolean(
SettingsKeys.KEY_SHOW_DATE_UNDER_BUBBLES,
false
)
)
}
var namesShown by remember {
mutableStateOf(
preferences.getBoolean(
SettingsKeys.KEY_SHOW_NAME_IN_BUBBLES,
false
)
)
}
var animationsEnabled by remember {
mutableStateOf(
preferences.getBoolean(
@@ -217,7 +228,8 @@ fun MessagesHistoryScreen(
dropDownMenuExpanded = false
// TODO: 11/07/2024, Danil Nikolaev: to VM
onNavigateToChatMaterials(
onChatMaterialsDropdownItemClicked(
screenState.conversationId,
screenState.messages.first().conversationMessageId
)
@@ -228,7 +240,7 @@ fun MessagesHistoryScreen(
)
DropdownMenuItem(
onClick = {
viewModel.onTopAppBarMenuClicked(0)
onRefreshDropdownItemClicked()
dropDownMenuExpanded = false
},
text = {
@@ -248,27 +260,6 @@ fun MessagesHistoryScreen(
)
) {
HorizontalDivider()
DropdownMenuItem(
text = {
Text(text = if (datesShown) "Hide dates" else "Show dates")
},
onClick = {
dropDownMenuExpanded = false
datesShown = !datesShown
viewModel.onShowDatesClicked(datesShown)
}
)
DropdownMenuItem(
text = {
Text(text = if (namesShown) "Hide names" else "Show names")
},
onClick = {
dropDownMenuExpanded = false
namesShown = !namesShown
viewModel.onShowNamesClicked(namesShown)
}
)
DropdownMenuItem(
text = {
@@ -277,14 +268,14 @@ fun MessagesHistoryScreen(
onClick = {
dropDownMenuExpanded = false
animationsEnabled = !animationsEnabled
viewModel.onEnableAnimationsClicked(animationsEnabled)
onToggleAnimationsDropdownItemClicked(animationsEnabled)
}
)
}
}
}
)
if (screenState.isLoading && messages.isNotEmpty()) {
if (screenState.isLoading && screenState.messages.isNotEmpty()) {
LinearProgressIndicator(modifier = Modifier.fillMaxWidth())
}
}
@@ -300,7 +291,7 @@ fun MessagesHistoryScreen(
MessagesList(
hazeState = hazeSate,
listState = listState,
immutableMessages = ImmutableList.copyOf(messages),
immutableMessages = ImmutableList.copyOf(screenState.messages),
isPaginating = screenState.isPaginating,
enableAnimations = animationsEnabled
)
@@ -372,7 +363,7 @@ fun MessagesHistoryScreen(
TextField(
modifier = Modifier.weight(1f),
value = screenState.message,
onValueChange = viewModel::onInputChanged,
onValueChange = onMessageInputChanged,
colors = TextFieldDefaults.colors(
unfocusedContainerColor = Color.Transparent,
focusedContainerColor = Color.Transparent,
@@ -382,7 +373,7 @@ fun MessagesHistoryScreen(
placeholder = { Text(text = stringResource(id = UiR.string.message_input_hint)) }
)
IconButton(onClick = viewModel::onAttachmentButtonClicked) {
IconButton(onClick = onAttachmentButtonClicked) {
Icon(
painter = painterResource(id = UiR.drawable.round_attach_file_24),
contentDescription = "Add attachment button",
@@ -414,7 +405,7 @@ fun MessagesHistoryScreen(
}
}
} else {
viewModel.onActionButtonClicked()
onActionButtonClicked()
}
},
modifier = Modifier.rotate(rotation.value)
@@ -445,7 +436,7 @@ fun MessagesHistoryScreen(
}
}
if (screenState.isLoading && messages.isEmpty()) {
if (screenState.isLoading && screenState.messages.isEmpty()) {
CircularProgressIndicator(modifier = Modifier.align(Alignment.Center))
}
}