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:
2025-03-23 09:22:41 +03:00
committed by GitHub
parent 30e132d418
commit b2879d8756
81 changed files with 1687 additions and 458 deletions
@@ -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(
@@ -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 {
@@ -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"
}
+1
View File
@@ -0,0 +1 @@
/build
+12
View File
@@ -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>
+1
View File
@@ -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>
+7 -1
View File
@@ -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>
+7 -2
View File
@@ -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>