Release 0.1.8 (#139)
* pagination in chat fixed * other fixes and improvements * fixed visual bug in progress bar in chat history * Refactor: Enhance conversations and friends features - In `ConversationsScreen`, removed `isNeedToScrollToTop` and `onScrolledToTop`, and refactored toolbar container color logic. Added `NoItemsView` for empty conversation lists. - In `MainGraph`, added `onMessageClicked` for navigation to message history. - In `ApiEvent`, introduced `parseOrNull` for handling unknown event types. - In `ConversationsViewModel`, removed `scrollToTop` logic and refactored error handling. - In `FriendsViewModel`, refactored error handling and introduced `onErrorConsumed` and `handleError`. - In `FriendItem`, added an icon button to initiate sending a message to a friend. - In `strings.xml`, added or updated strings for session expiration, log out, refreshing, and empty friend lists. - In `RootScreen`, added `onMessageClicked` for navigating to messages. - In `FriendsList`, added `onMessageClicked` for handling message clicks. - In `MainScreen`, removed unused `MutableSharedFlow`. - In `FriendsScreen`, added support for showing errors, added `onMessageClicked`, and replaced `hazeChild` with `hazeEffect` and `hazeSource`. - In `FriendsNavigation`, added `onMessageClicked` for handling message clicks. - In `ConversationsNavigation`, removed the unused `scrollToTopFlow` parameter. - In `ErrorView`, added text alignment. - In `NoItemsView`, added support for a button and custom text. - In `LongPollUpdatesParser`, replaced try-catch with `parseOrNull`. * Chat creation feature (#138) * - read indicator, edit status and time for message in messages history * message sending status
This commit is contained in:
@@ -1,10 +1,10 @@
|
||||
package dev.meloda.fast.data.api.messages
|
||||
|
||||
import com.slack.eithernet.ApiResult
|
||||
import dev.meloda.fast.model.api.domain.VkAttachment
|
||||
import dev.meloda.fast.model.api.domain.VkAttachmentHistoryMessage
|
||||
import dev.meloda.fast.model.api.domain.VkMessage
|
||||
import dev.meloda.fast.network.RestApiErrorDomain
|
||||
import com.slack.eithernet.ApiResult
|
||||
|
||||
interface MessagesRepository {
|
||||
|
||||
@@ -41,6 +41,11 @@ interface MessagesRepository {
|
||||
conversationMessageId: Int
|
||||
): ApiResult<List<VkAttachmentHistoryMessage>, RestApiErrorDomain>
|
||||
|
||||
suspend fun createChat(
|
||||
userIds: List<Int>?,
|
||||
title: String?
|
||||
): ApiResult<Int, RestApiErrorDomain>
|
||||
|
||||
suspend fun storeMessages(messages: List<VkMessage>)
|
||||
|
||||
// suspend fun markAsImportant(
|
||||
|
||||
+19
-1
@@ -1,5 +1,6 @@
|
||||
package dev.meloda.fast.data.api.messages
|
||||
|
||||
import com.slack.eithernet.ApiResult
|
||||
import dev.meloda.fast.common.VkConstants
|
||||
import dev.meloda.fast.data.VkGroupsMap
|
||||
import dev.meloda.fast.data.VkMemoryCache
|
||||
@@ -14,6 +15,7 @@ import dev.meloda.fast.model.api.domain.VkAttachment
|
||||
import dev.meloda.fast.model.api.domain.VkAttachmentHistoryMessage
|
||||
import dev.meloda.fast.model.api.domain.VkMessage
|
||||
import dev.meloda.fast.model.api.domain.asEntity
|
||||
import dev.meloda.fast.model.api.requests.MessagesCreateChatRequest
|
||||
import dev.meloda.fast.model.api.requests.MessagesGetByIdRequest
|
||||
import dev.meloda.fast.model.api.requests.MessagesGetHistoryAttachmentsRequest
|
||||
import dev.meloda.fast.model.api.requests.MessagesGetHistoryRequest
|
||||
@@ -23,7 +25,6 @@ import dev.meloda.fast.network.RestApiErrorDomain
|
||||
import dev.meloda.fast.network.mapApiDefault
|
||||
import dev.meloda.fast.network.mapApiResult
|
||||
import dev.meloda.fast.network.service.messages.MessagesService
|
||||
import com.slack.eithernet.ApiResult
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
@@ -198,6 +199,23 @@ class MessagesRepositoryImpl(
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun createChat(
|
||||
userIds: List<Int>?,
|
||||
title: String?
|
||||
): ApiResult<Int, RestApiErrorDomain> = withContext(Dispatchers.IO) {
|
||||
val requestModel = MessagesCreateChatRequest(
|
||||
userIds = userIds,
|
||||
title = title
|
||||
)
|
||||
|
||||
messagesService.createChat(requestModel.map).mapApiResult(
|
||||
successMapper = { apiResponse ->
|
||||
apiResponse.requireResponse().chatId
|
||||
},
|
||||
errorMapper = { error -> error?.toDomain() }
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun storeMessages(messages: List<VkMessage>) {
|
||||
messageDao.insertAll(messages.map(VkMessage::asEntity))
|
||||
}
|
||||
|
||||
@@ -20,4 +20,8 @@ class GetLocalUserByIdUseCase(private val repository: UsersRepository) {
|
||||
|
||||
emit(newState)
|
||||
}
|
||||
|
||||
suspend fun proceed(userId: Int): VkUser? {
|
||||
return repository.getLocalUsers(userIds = listOf(userId)).singleOrNull()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,15 +41,9 @@ class LongPollUpdatesParser(
|
||||
fun parseNextUpdate(event: List<Any>) {
|
||||
val eventId = event.first().asInt()
|
||||
|
||||
val eventType: ApiEvent = try {
|
||||
ApiEvent.parse(eventId)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
Log.d("LongPollUpdatesParser", "parseNextUpdate: unknownEvent: $event")
|
||||
return
|
||||
}
|
||||
when (val eventType = ApiEvent.parseOrNull(eventId)) {
|
||||
null -> Log.d("LongPollUpdatesParser", "parseNextUpdate: unknownEvent: $event")
|
||||
|
||||
when (eventType) {
|
||||
ApiEvent.MESSAGE_SET_FLAGS -> parseMessageSetFlags(eventType, event)
|
||||
ApiEvent.MESSAGE_CLEAR_FLAGS -> parseMessageClearFlags(eventType, event)
|
||||
ApiEvent.MESSAGE_NEW -> parseMessageNew(eventType, event)
|
||||
|
||||
@@ -42,6 +42,11 @@ interface MessagesUseCase {
|
||||
conversationMessageId: Int
|
||||
): Flow<State<List<VkAttachmentHistoryMessage>>>
|
||||
|
||||
fun createChat(
|
||||
userIds: List<Int>?,
|
||||
title: String?
|
||||
): Flow<State<Int>>
|
||||
|
||||
suspend fun storeMessage(message: VkMessage)
|
||||
suspend fun storeMessages(messages: List<VkMessage>)
|
||||
}
|
||||
|
||||
@@ -100,6 +100,14 @@ class MessagesUseCaseImpl(
|
||||
emit(newState)
|
||||
}
|
||||
|
||||
override fun createChat(userIds: List<Int>?, title: String?): Flow<State<Int>> = flow {
|
||||
emit(State.Loading)
|
||||
|
||||
val newState = repository.createChat(userIds, title).mapToState()
|
||||
|
||||
emit(newState)
|
||||
}
|
||||
|
||||
override suspend fun storeMessage(message: VkMessage) {
|
||||
repository.storeMessages(listOf(message))
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
package dev.meloda.fast.domain.util
|
||||
|
||||
import dev.meloda.fast.common.model.UiImage
|
||||
import dev.meloda.fast.data.VkMemoryCache
|
||||
import dev.meloda.fast.model.api.domain.VkUser
|
||||
import dev.meloda.fast.ui.model.api.UiFriend
|
||||
|
||||
fun VkUser.asPresentation(
|
||||
useContactNames: Boolean = false
|
||||
): UiFriend = UiFriend(
|
||||
userId = id,
|
||||
avatar = photo100?.let(UiImage::Url),
|
||||
title = if (useContactNames) {
|
||||
VkMemoryCache.getContact(id)?.name ?: fullName
|
||||
} else {
|
||||
fullName
|
||||
},
|
||||
onlineStatus = onlineStatus,
|
||||
photo400Orig = photo400Orig?.let(UiImage::Url),
|
||||
firstName = firstName,
|
||||
lastName = lastName
|
||||
)
|
||||
@@ -18,5 +18,6 @@ enum class ApiEvent(val value: Int) {
|
||||
|
||||
companion object {
|
||||
fun parse(value: Int) = entries.first { it.value == value }
|
||||
fun parseOrNull(value: Int) = entries.firstOrNull { it.value == value }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,6 +38,41 @@ data class VkConversation(
|
||||
fun isPinned(): Boolean = majorId > 0
|
||||
fun isInUnread() = inRead - (lastMessageId ?: 0) < 0
|
||||
fun isOutUnread() = outRead - (lastMessageId ?: 0) < 0
|
||||
|
||||
companion object {
|
||||
val EMPTY: VkConversation = VkConversation(
|
||||
id = -1,
|
||||
localId = -1,
|
||||
ownerId = null,
|
||||
title = "...",
|
||||
photo50 = null,
|
||||
photo100 = null,
|
||||
photo200 = null,
|
||||
isCallInProgress = false,
|
||||
isPhantom = false,
|
||||
lastConversationMessageId = -1,
|
||||
inReadCmId = -1,
|
||||
outReadCmId = -1,
|
||||
inRead = -1,
|
||||
outRead = -1,
|
||||
lastMessageId = null,
|
||||
unreadCount = -1,
|
||||
membersCount = null,
|
||||
canChangePin = false,
|
||||
canChangeInfo = false,
|
||||
majorId = -1,
|
||||
minorId = -1,
|
||||
pinnedMessageId = null,
|
||||
interactionType = -1,
|
||||
interactionIds = emptyList(),
|
||||
peerType = PeerType.USER,
|
||||
lastMessage = null,
|
||||
pinnedMessage = null,
|
||||
user = null,
|
||||
group = null
|
||||
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun VkConversation.asEntity(): VkConversationEntity = VkConversationEntity(
|
||||
|
||||
@@ -38,12 +38,11 @@ data class VkMessage(
|
||||
|
||||
fun isGroup() = fromId < 0
|
||||
|
||||
fun isRead(conversation: VkConversation) =
|
||||
if (isOut) {
|
||||
conversation.outRead - id >= 0
|
||||
} else {
|
||||
conversation.inRead - id >= 0
|
||||
}
|
||||
fun isRead(conversation: VkConversation): Boolean = when {
|
||||
id <= 0 -> false
|
||||
isOut -> conversation.outRead - id >= 0
|
||||
else -> conversation.inRead - id >= 0
|
||||
}
|
||||
|
||||
fun hasAttachments(): Boolean = attachments.orEmpty().isNotEmpty()
|
||||
|
||||
|
||||
@@ -267,3 +267,14 @@ data class MessagesGetHistoryAttachmentsRequest(
|
||||
fields?.let { this["fields"] = it }
|
||||
}
|
||||
}
|
||||
|
||||
data class MessagesCreateChatRequest(
|
||||
val userIds: List<Int>?,
|
||||
val title: String?
|
||||
) {
|
||||
|
||||
val map = mutableMapOf<String, String>().apply {
|
||||
userIds?.let { this["user_ids"] = it.joinToString(",") }
|
||||
title?.let { this["title"] = it }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package dev.meloda.fast.model.api.responses
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import dev.meloda.fast.model.api.data.VkAttachmentHistoryMessageData
|
||||
import dev.meloda.fast.model.api.data.VkChatMemberData
|
||||
import dev.meloda.fast.model.api.data.VkContactData
|
||||
@@ -7,8 +9,6 @@ import dev.meloda.fast.model.api.data.VkConversationData
|
||||
import dev.meloda.fast.model.api.data.VkGroupData
|
||||
import dev.meloda.fast.model.api.data.VkMessageData
|
||||
import dev.meloda.fast.model.api.data.VkUserData
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class MessagesGetHistoryResponse(
|
||||
@@ -44,3 +44,9 @@ data class MessagesGetHistoryAttachmentsResponse(
|
||||
@Json(name = "groups") val groups: List<VkGroupData>?,
|
||||
@Json(name = "contacts") val contacts: List<VkContactData>?
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class MessagesCreateChatResponse(
|
||||
@Json(name = "chat_id") val chatId: Int,
|
||||
@Json(name = "peer_ids") val peerIds: List<Int>
|
||||
)
|
||||
|
||||
@@ -43,6 +43,12 @@ class ResponseConverterFactory(private val converter: JsonConverter) : Converter
|
||||
converter.fromJson(successType, string)
|
||||
}.fold(
|
||||
onSuccess = { successModel ->
|
||||
if (successModel is ApiResponse<*>) {
|
||||
if (successModel.error != null) {
|
||||
throw ApiException(successModel.error)
|
||||
}
|
||||
}
|
||||
|
||||
return successModel
|
||||
},
|
||||
onFailure = { failure ->
|
||||
|
||||
@@ -42,6 +42,8 @@ enum class VkErrorCode(val code: Int) {
|
||||
ACCESS_TO_DOC_DENIED(1153),
|
||||
|
||||
SOME_AUTH_ERROR(104),
|
||||
|
||||
CANNOT_SEND_MESSAGE_DUE_TO_PRIVACY_SETTINGS(902),
|
||||
ACCESS_TOKEN_EXPIRED(1117);
|
||||
|
||||
companion object {
|
||||
|
||||
+8
-1
@@ -1,12 +1,13 @@
|
||||
package dev.meloda.fast.network.service.messages
|
||||
|
||||
import com.slack.eithernet.ApiResult
|
||||
import dev.meloda.fast.model.api.data.VkLongPollData
|
||||
import dev.meloda.fast.model.api.responses.MessagesCreateChatResponse
|
||||
import dev.meloda.fast.model.api.responses.MessagesGetByIdResponse
|
||||
import dev.meloda.fast.model.api.responses.MessagesGetHistoryAttachmentsResponse
|
||||
import dev.meloda.fast.model.api.responses.MessagesGetHistoryResponse
|
||||
import dev.meloda.fast.network.ApiResponse
|
||||
import dev.meloda.fast.network.RestApiError
|
||||
import com.slack.eithernet.ApiResult
|
||||
import retrofit2.http.FieldMap
|
||||
import retrofit2.http.FormUrlEncoded
|
||||
import retrofit2.http.POST
|
||||
@@ -49,6 +50,12 @@ interface MessagesService {
|
||||
@FieldMap params: Map<String, String>
|
||||
): ApiResult<ApiResponse<MessagesGetHistoryAttachmentsResponse>, RestApiError>
|
||||
|
||||
@FormUrlEncoded
|
||||
@POST(MessagesUrls.CREATE_CHAT)
|
||||
suspend fun createChat(
|
||||
@FieldMap params: Map<String, String>
|
||||
): ApiResult<ApiResponse<MessagesCreateChatResponse>, RestApiError>
|
||||
|
||||
// @FormUrlEncoded
|
||||
// @POST(MessagesUrls.MarkAsImportant)
|
||||
// suspend fun markAsImportant(
|
||||
|
||||
@@ -19,4 +19,5 @@ object MessagesUrls {
|
||||
const val GET_CONVERSATIONS_MEMBERS = "${AppConstants.URL_API}/messages.getConversationMembers"
|
||||
const val REMOVE_CHAT_USER = "${AppConstants.URL_API}/messages.removeChatUser"
|
||||
const val GET_HISTORY_ATTACHMENTS = "${AppConstants.URL_API}/messages.getHistoryAttachments"
|
||||
const val CREATE_CHAT = "${AppConstants.URL_API}/messages.createChat"
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
/build
|
||||
@@ -0,0 +1,12 @@
|
||||
plugins {
|
||||
alias(libs.plugins.fast.android.library)
|
||||
alias(libs.plugins.fast.android.library.compose)
|
||||
alias(libs.plugins.kotlin.serialization)
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "dev.meloda.fast.presentation"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
</manifest>
|
||||
@@ -11,6 +11,7 @@ android {
|
||||
dependencies {
|
||||
api(projects.core.common)
|
||||
api(projects.core.model)
|
||||
implementation(projects.core.presentation)
|
||||
|
||||
implementation(libs.haze)
|
||||
implementation(libs.haze.materials)
|
||||
|
||||
@@ -12,6 +12,7 @@ import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
@@ -31,7 +32,8 @@ fun ErrorView(
|
||||
) {
|
||||
Text(
|
||||
text = text,
|
||||
style = MaterialTheme.typography.titleLarge
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
|
||||
buttonText?.let {
|
||||
|
||||
@@ -29,9 +29,9 @@ import androidx.compose.ui.unit.dp
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun IconButton(
|
||||
modifier: Modifier = Modifier,
|
||||
onClick: () -> Unit = {},
|
||||
onLongClick: (() -> Unit)? = null,
|
||||
modifier: Modifier = Modifier,
|
||||
enabled: Boolean = true,
|
||||
colors: IconButtonColors = IconButtonDefaults.iconButtonColors(),
|
||||
interactionSource: MutableInteractionSource? = null,
|
||||
|
||||
@@ -1,29 +1,51 @@
|
||||
package dev.meloda.fast.ui.components
|
||||
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import dev.meloda.fast.ui.R
|
||||
|
||||
@Composable
|
||||
fun NoItemsView(
|
||||
modifier: Modifier = Modifier,
|
||||
customText: String? = null
|
||||
customText: String? = null,
|
||||
buttonText: String? = null,
|
||||
onButtonClick: (() -> Unit)? = null,
|
||||
) {
|
||||
Box(
|
||||
modifier = modifier.fillMaxSize(),
|
||||
contentAlignment = Alignment.Center
|
||||
Column(
|
||||
modifier = modifier
|
||||
.fillMaxSize()
|
||||
.padding(horizontal = 16.dp),
|
||||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Text(
|
||||
text = customText ?: stringResource(id = R.string.no_items),
|
||||
style = MaterialTheme.typography.titleLarge
|
||||
text = customText ?: stringResource(R.string.no_items),
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
|
||||
buttonText?.let {
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
Button(
|
||||
onClick = { onButtonClick?.invoke() }
|
||||
) {
|
||||
Text(text = buttonText)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +53,7 @@ fun NoItemsView(
|
||||
@Composable
|
||||
private fun NoItemsViewPreview() {
|
||||
NoItemsView(
|
||||
customText = "Nothing here..."
|
||||
customText = "Nothing here...",
|
||||
buttonText = "Refresh"
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package dev.meloda.fast.ui.model.api
|
||||
|
||||
enum class ActionState {
|
||||
PHANTOM, CALL_IN_PROGRESS, NONE;
|
||||
|
||||
// TODO: 11/04/2024, Danil Nikolaev: implement
|
||||
fun getResourceId(): Int {
|
||||
return -1
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun parse(isPhantom: Boolean, isCallInProgress: Boolean): ActionState {
|
||||
return when {
|
||||
isPhantom -> PHANTOM
|
||||
isCallInProgress -> CALL_IN_PROGRESS
|
||||
else -> NONE
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package dev.meloda.fast.ui.model.api
|
||||
|
||||
import dev.meloda.fast.common.model.UiImage
|
||||
import dev.meloda.fast.common.model.UiText
|
||||
import dev.meloda.fast.ui.R
|
||||
|
||||
sealed class ConversationOption(
|
||||
val title: UiText,
|
||||
val icon: UiImage
|
||||
) {
|
||||
|
||||
data object MarkAsRead : ConversationOption(
|
||||
title = UiText.Resource(R.string.action_mark_as_read),
|
||||
icon = UiImage.Resource(R.drawable.round_done_all_24)
|
||||
)
|
||||
|
||||
data object Pin : ConversationOption(
|
||||
title = UiText.Resource(R.string.action_pin),
|
||||
icon = UiImage.Resource(R.drawable.pin_outline_24)
|
||||
)
|
||||
|
||||
data object Unpin : ConversationOption(
|
||||
title = UiText.Resource(R.string.action_unpin),
|
||||
icon = UiImage.Resource(R.drawable.pin_off_outline_24)
|
||||
)
|
||||
|
||||
data object Delete : ConversationOption(
|
||||
title = UiText.Resource(R.string.action_delete),
|
||||
icon = UiImage.Resource(R.drawable.round_delete_outline_24)
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package dev.meloda.fast.ui.model.api
|
||||
|
||||
data class ConversationsShowOptions(
|
||||
val showDeleteDialog: Int?,
|
||||
val showPinDialog: UiConversation?
|
||||
) {
|
||||
|
||||
companion object {
|
||||
val EMPTY: ConversationsShowOptions = ConversationsShowOptions(
|
||||
showDeleteDialog = null,
|
||||
showPinDialog = null
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package dev.meloda.fast.ui.model.api
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import dev.meloda.fast.common.model.UiImage
|
||||
import dev.meloda.fast.model.api.PeerType
|
||||
import dev.meloda.fast.model.api.domain.VkMessage
|
||||
import dev.meloda.fast.ui.util.ImmutableList
|
||||
|
||||
@Immutable
|
||||
data class UiConversation(
|
||||
val id: Int,
|
||||
val lastMessageId: Int?,
|
||||
val avatar: UiImage?,
|
||||
val title: String,
|
||||
val unreadCount: String?,
|
||||
val date: String,
|
||||
val message: AnnotatedString,
|
||||
val attachmentImage: UiImage?,
|
||||
val isPinned: Boolean,
|
||||
val actionImageId: Int,
|
||||
val isBirthday: Boolean,
|
||||
val isUnread: Boolean,
|
||||
val isAccount: Boolean,
|
||||
val isOnline: Boolean,
|
||||
val lastMessage: VkMessage?,
|
||||
val peerType: PeerType,
|
||||
val interactionText: String?,
|
||||
val isExpanded: Boolean,
|
||||
val options: ImmutableList<ConversationOption>,
|
||||
)
|
||||
@@ -0,0 +1,16 @@
|
||||
package dev.meloda.fast.ui.model.api
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import dev.meloda.fast.common.model.UiImage
|
||||
import dev.meloda.fast.model.api.domain.OnlineStatus
|
||||
|
||||
@Immutable
|
||||
data class UiFriend(
|
||||
val userId: Int,
|
||||
val avatar: UiImage?,
|
||||
val firstName: String,
|
||||
val lastName: String,
|
||||
val title: String,
|
||||
val onlineStatus: OnlineStatus,
|
||||
val photo400Orig: UiImage?
|
||||
)
|
||||
@@ -1,7 +1,6 @@
|
||||
package dev.meloda.fast.ui.util
|
||||
|
||||
import android.content.res.Configuration
|
||||
import android.graphics.drawable.ColorDrawable
|
||||
import android.os.PowerManager
|
||||
import android.view.KeyEvent
|
||||
import androidx.compose.foundation.lazy.LazyListState
|
||||
@@ -21,6 +20,7 @@ import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.pluralStringResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.core.content.getSystemService
|
||||
import androidx.core.graphics.drawable.toDrawable
|
||||
import dev.meloda.fast.common.model.DarkMode
|
||||
import dev.meloda.fast.common.model.UiImage
|
||||
import dev.meloda.fast.common.model.UiText
|
||||
@@ -64,8 +64,8 @@ fun UiImage.getResourcePainter(): Painter? {
|
||||
@Composable
|
||||
fun UiImage.getImage(): Any {
|
||||
return when (this) {
|
||||
is UiImage.Color -> ColorDrawable(color)
|
||||
is UiImage.ColorResource -> ColorDrawable(colorResource(id = resId).toArgb())
|
||||
is UiImage.Color -> color.toDrawable()
|
||||
is UiImage.ColorResource -> colorResource(id = resId).toArgb().toDrawable()
|
||||
is UiImage.Resource -> painterResource(id = resId)
|
||||
is UiImage.Simple -> drawable
|
||||
is UiImage.Url -> url
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
|
||||
|
||||
<path android:fillColor="@android:color/white" android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10s10,-4.48 10,-10S17.52,2 12,2zM12,6c1.93,0 3.5,1.57 3.5,3.5S13.93,13 12,13s-3.5,-1.57 -3.5,-3.5S10.07,6 12,6zM12,20c-2.03,0 -4.43,-0.82 -6.14,-2.88C7.55,15.8 9.68,15 12,15s4.45,0.8 6.14,2.12C16.43,19.18 14.03,20 12,20z"/>
|
||||
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10s10,-4.48 10,-10S17.52,2 12,2zM12,6c1.93,0 3.5,1.57 3.5,3.5S13.93,13 12,13s-3.5,-1.57 -3.5,-3.5S10.07,6 12,6zM12,20c-2.03,0 -4.43,-0.82 -6.14,-2.88C7.55,15.8 9.68,15 12,15s4.45,0.8 6.14,2.12C16.43,19.18 14.03,20 12,20z" />
|
||||
|
||||
</vector>
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:autoMirrored="true" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
|
||||
|
||||
<path android:fillColor="@android:color/white" android:pathData="M20,2L4,2c-1.1,0 -1.99,0.9 -1.99,2L2,22l4,-4h14c1.1,0 2,-0.9 2,-2L22,4c0,-1.1 -0.9,-2 -2,-2zM6,9h12v2L6,11L6,9zM14,14L6,14v-2h8v2zM18,8L6,8L6,6h12v2z"/>
|
||||
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:autoMirrored="true"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M20,2L4,2c-1.1,0 -1.99,0.9 -1.99,2L2,22l4,-4h14c1.1,0 2,-0.9 2,-2L22,4c0,-1.1 -0.9,-2 -2,-2zM6,9h12v2L6,11L6,9zM14,14L6,14v-2h8v2zM18,8L6,8L6,6h12v2z" />
|
||||
|
||||
</vector>
|
||||
|
||||
@@ -1,11 +1,27 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
|
||||
|
||||
<path android:fillColor="@android:color/white" android:fillType="evenOdd" android:pathData="M16.67,13.13C18.04,14.06 19,15.32 19,17v3h4v-3C23,14.82 19.43,13.53 16.67,13.13z"/>
|
||||
|
||||
<path android:fillColor="@android:color/white" android:fillType="evenOdd" android:pathData="M9,8m-4,0a4,4 0,1 1,8 0a4,4 0,1 1,-8 0"/>
|
||||
|
||||
<path android:fillColor="@android:color/white" android:fillType="evenOdd" android:pathData="M15,12c2.21,0 4,-1.79 4,-4c0,-2.21 -1.79,-4 -4,-4c-0.47,0 -0.91,0.1 -1.33,0.24C14.5,5.27 15,6.58 15,8s-0.5,2.73 -1.33,3.76C14.09,11.9 14.53,12 15,12z"/>
|
||||
|
||||
<path android:fillColor="@android:color/white" android:fillType="evenOdd" android:pathData="M9,13c-2.67,0 -8,1.34 -8,4v3h16v-3C17,14.34 11.67,13 9,13z"/>
|
||||
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:fillType="evenOdd"
|
||||
android:pathData="M16.67,13.13C18.04,14.06 19,15.32 19,17v3h4v-3C23,14.82 19.43,13.53 16.67,13.13z" />
|
||||
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:fillType="evenOdd"
|
||||
android:pathData="M9,8m-4,0a4,4 0,1 1,8 0a4,4 0,1 1,-8 0" />
|
||||
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:fillType="evenOdd"
|
||||
android:pathData="M15,12c2.21,0 4,-1.79 4,-4c0,-2.21 -1.79,-4 -4,-4c-0.47,0 -0.91,0.1 -1.33,0.24C14.5,5.27 15,6.58 15,8s-0.5,2.73 -1.33,3.76C14.09,11.9 14.53,12 15,12z" />
|
||||
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:fillType="evenOdd"
|
||||
android:pathData="M9,13c-2.67,0 -8,1.34 -8,4v3h16v-3C17,14.34 11.67,13 9,13z" />
|
||||
|
||||
</vector>
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="28dp"
|
||||
android:height="28dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M12,7c-0.55,0 -1,0.45 -1,1v3L8,11c-0.55,0 -1,0.45 -1,1s0.45,1 1,1h3v3c0,0.55 0.45,1 1,1s1,-0.45 1,-1v-3h3c0.55,0 1,-0.45 1,-1s-0.45,-1 -1,-1h-3L13,8c0,-0.55 -0.45,-1 -1,-1zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8z" />
|
||||
</vector>
|
||||
@@ -1,9 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M18.3,5.71c-0.39,-0.39 -1.02,-0.39 -1.41,0L12,10.59 7.11,5.7c-0.39,-0.39 -1.02,-0.39 -1.41,0 -0.39,0.39 -0.39,1.02 0,1.41L10.59,12 5.7,16.89c-0.39,0.39 -0.39,1.02 0,1.41 0.39,0.39 1.02,0.39 1.41,0L12,13.41l4.89,4.89c0.39,0.39 1.02,0.39 1.41,0 0.39,-0.39 0.39,-1.02 0,-1.41L13.41,12l4.89,-4.89c0.38,-0.38 0.38,-1.02 0,-1.4z" />
|
||||
</vector>
|
||||
@@ -1,7 +1,15 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
|
||||
|
||||
<path android:fillColor="@android:color/white" android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10s10,-4.48 10,-10S17.52,2 12,2zM7.35,18.5C8.66,17.56 10.26,17 12,17s3.34,0.56 4.65,1.5C15.34,19.44 13.74,20 12,20S8.66,19.44 7.35,18.5zM18.14,17.12L18.14,17.12C16.45,15.8 14.32,15 12,15s-4.45,0.8 -6.14,2.12l0,0C4.7,15.73 4,13.95 4,12c0,-4.42 3.58,-8 8,-8s8,3.58 8,8C20,13.95 19.3,15.73 18.14,17.12z"/>
|
||||
|
||||
<path android:fillColor="@android:color/white" android:pathData="M12,6c-1.93,0 -3.5,1.57 -3.5,3.5S10.07,13 12,13s3.5,-1.57 3.5,-3.5S13.93,6 12,6zM12,11c-0.83,0 -1.5,-0.67 -1.5,-1.5S11.17,8 12,8s1.5,0.67 1.5,1.5S12.83,11 12,11z"/>
|
||||
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10s10,-4.48 10,-10S17.52,2 12,2zM7.35,18.5C8.66,17.56 10.26,17 12,17s3.34,0.56 4.65,1.5C15.34,19.44 13.74,20 12,20S8.66,19.44 7.35,18.5zM18.14,17.12L18.14,17.12C16.45,15.8 14.32,15 12,15s-4.45,0.8 -6.14,2.12l0,0C4.7,15.73 4,13.95 4,12c0,-4.42 3.58,-8 8,-8s8,3.58 8,8C20,13.95 19.3,15.73 18.14,17.12z" />
|
||||
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M12,6c-1.93,0 -3.5,1.57 -3.5,3.5S10.07,13 12,13s3.5,-1.57 3.5,-3.5S13.93,6 12,6zM12,11c-0.83,0 -1.5,-0.67 -1.5,-1.5S11.17,8 12,8s1.5,0.67 1.5,1.5S12.83,11 12,11z" />
|
||||
|
||||
</vector>
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:autoMirrored="true" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
|
||||
|
||||
<path android:fillColor="@android:color/white" android:pathData="M4,4h16v12L5.17,16L4,17.17L4,4m0,-2c-1.1,0 -1.99,0.9 -1.99,2L2,22l4,-4h14c1.1,0 2,-0.9 2,-2L22,4c0,-1.1 -0.9,-2 -2,-2L4,2zM6,12h8v2L6,14v-2zM6,9h12v2L6,11L6,9zM6,6h12v2L6,8L6,6z"/>
|
||||
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:autoMirrored="true"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M4,4h16v12L5.17,16L4,17.17L4,4m0,-2c-1.1,0 -1.99,0.9 -1.99,2L2,22l4,-4h14c1.1,0 2,-0.9 2,-2L22,4c0,-1.1 -0.9,-2 -2,-2L4,2zM6,12h8v2L6,14v-2zM6,9h12v2L6,11L6,9zM6,6h12v2L6,8L6,6z" />
|
||||
|
||||
</vector>
|
||||
|
||||
@@ -1,11 +1,23 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
|
||||
|
||||
<path android:fillColor="@android:color/white" android:pathData="M16.67,13.13C18.04,14.06 19,15.32 19,17v3h4v-3C23,14.82 19.43,13.53 16.67,13.13z"/>
|
||||
|
||||
<path android:fillColor="@android:color/white" android:pathData="M15,12c2.21,0 4,-1.79 4,-4c0,-2.21 -1.79,-4 -4,-4c-0.47,0 -0.91,0.1 -1.33,0.24C14.5,5.27 15,6.58 15,8s-0.5,2.73 -1.33,3.76C14.09,11.9 14.53,12 15,12z"/>
|
||||
|
||||
<path android:fillColor="@android:color/white" android:pathData="M9,12c2.21,0 4,-1.79 4,-4c0,-2.21 -1.79,-4 -4,-4S5,5.79 5,8C5,10.21 6.79,12 9,12zM9,6c1.1,0 2,0.9 2,2c0,1.1 -0.9,2 -2,2S7,9.1 7,8C7,6.9 7.9,6 9,6z"/>
|
||||
|
||||
<path android:fillColor="@android:color/white" android:pathData="M9,13c-2.67,0 -8,1.34 -8,4v3h16v-3C17,14.34 11.67,13 9,13zM15,18H3l0,-0.99C3.2,16.29 6.3,15 9,15s5.8,1.29 6,2V18z"/>
|
||||
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M16.67,13.13C18.04,14.06 19,15.32 19,17v3h4v-3C23,14.82 19.43,13.53 16.67,13.13z" />
|
||||
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M15,12c2.21,0 4,-1.79 4,-4c0,-2.21 -1.79,-4 -4,-4c-0.47,0 -0.91,0.1 -1.33,0.24C14.5,5.27 15,6.58 15,8s-0.5,2.73 -1.33,3.76C14.09,11.9 14.53,12 15,12z" />
|
||||
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M9,12c2.21,0 4,-1.79 4,-4c0,-2.21 -1.79,-4 -4,-4S5,5.79 5,8C5,10.21 6.79,12 9,12zM9,6c1.1,0 2,0.9 2,2c0,1.1 -0.9,2 -2,2S7,9.1 7,8C7,6.9 7.9,6 9,6z" />
|
||||
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M9,13c-2.67,0 -8,1.34 -8,4v3h16v-3C17,14.34 11.67,13 9,13zM15,18H3l0,-0.99C3.2,16.29 6.3,15 9,15s5.8,1.29 6,2V18z" />
|
||||
|
||||
</vector>
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8zM11.78,7h-0.06c-0.4,0 -0.72,0.32 -0.72,0.72v4.72c0,0.35 0.18,0.68 0.49,0.86l4.15,2.49c0.34,0.2 0.78,0.1 0.98,-0.24 0.21,-0.34 0.1,-0.79 -0.25,-0.99l-3.87,-2.3L12.5,7.72c0,-0.4 -0.32,-0.72 -0.72,-0.72z" />
|
||||
|
||||
</vector>
|
||||
@@ -0,0 +1,11 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M12,7c0.55,0 1,0.45 1,1v4c0,0.55 -0.45,1 -1,1s-1,-0.45 -1,-1L11,8c0,-0.55 0.45,-1 1,-1zM11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8zM13,17h-2v-2h2v2z" />
|
||||
|
||||
</vector>
|
||||
@@ -128,7 +128,7 @@
|
||||
<string name="post_type_community">Запись сообщества</string>
|
||||
<string name="post_type_user">Запись пользователя</string>
|
||||
<string name="post_type_unknown">Запись на стене</string>
|
||||
<string name="log_out">Выйти</string>
|
||||
<string name="action_log_out">Выйти</string>
|
||||
<string name="confirm">Подтверждение</string>
|
||||
<string name="message_attachment_story_your_story">Ваша история</string>
|
||||
<string name="settings_dynamic_colors">Динамические цвета</string>
|
||||
@@ -212,4 +212,10 @@
|
||||
<string name="captcha_exit_warning">Вы уверены? Процесс ввода капчи будет отменён</string>
|
||||
<string name="validation_exit_warning">Вы уверены? Процесс ввода кода-подтверждения будет отменён</string>
|
||||
<string name="action_authorize">Авторизоваться</string>
|
||||
<string name="no_online_friends">Никого в сети</string>
|
||||
<string name="try_again">Попробовать ещё раз</string>
|
||||
<string name="session_expired">Срок действия сессии истёк</string>
|
||||
<string name="title_create_chat">Создать чат</string>
|
||||
<string name="action_create">Создать</string>
|
||||
<string name="create_chat_title">Название</string>
|
||||
</resources>
|
||||
|
||||
@@ -119,7 +119,7 @@
|
||||
<string name="post_type_community">Community post</string>
|
||||
<string name="post_type_user">User post</string>
|
||||
<string name="post_type_unknown">Post</string>
|
||||
<string name="log_out">Log out</string>
|
||||
<string name="action_log_out">Log out</string>
|
||||
<string name="confirm">Confirmation</string>
|
||||
<string name="sign_out_confirm">Signing out will delete all data related to this account from this device. Continue?</string>
|
||||
<string name="yes">Yes</string>
|
||||
@@ -276,6 +276,11 @@
|
||||
<string name="warning_confirmation">Confirmation</string>
|
||||
<string name="captcha_exit_warning">Are you sure? Captcha process will be cancelled</string>
|
||||
<string name="validation_exit_warning">Are you sure? Validation process will be cancelled</string>
|
||||
<string name="settings_general_enable_pull_to_refresh_title">Enable pull to refresh</string>
|
||||
<string name="action_authorize">Authorize</string>
|
||||
<string name="no_online_friends">No one is online</string>
|
||||
<string name="try_again">Try again</string>
|
||||
<string name="session_expired">Session expired</string>
|
||||
<string name="title_create_chat">Create chat</string>
|
||||
<string name="action_create">Create</string>
|
||||
<string name="create_chat_title">Title</string>
|
||||
</resources>
|
||||
|
||||
Reference in New Issue
Block a user