Merge pull request #4056 from element-hq/feature/bma/messageActionList

Update message action list
This commit is contained in:
Benoit Marty
2024-12-18 16:47:51 +01:00
committed by GitHub
42 changed files with 150 additions and 157 deletions

View File

@@ -274,7 +274,8 @@ class MessagesPresenter @AssistedInject constructor(
TimelineItemAction.CopyCaption -> handleCopyCaption(targetEvent)
TimelineItemAction.CopyLink -> handleCopyLink(targetEvent)
TimelineItemAction.Redact -> handleActionRedact(targetEvent)
TimelineItemAction.Edit -> handleActionEdit(targetEvent, composerState, enableTextFormatting)
TimelineItemAction.Edit,
TimelineItemAction.EditPoll -> handleActionEdit(targetEvent, composerState, enableTextFormatting)
TimelineItemAction.AddCaption -> handleActionAddCaption(targetEvent, composerState)
TimelineItemAction.EditCaption -> handleActionEditCaption(targetEvent, composerState)
TimelineItemAction.RemoveCaption -> handleRemoveCaption(targetEvent)

View File

@@ -178,6 +178,8 @@ class DefaultActionListPresenter @AssistedInject constructor(
add(TimelineItemAction.EditCaption)
add(TimelineItemAction.RemoveCaption)
}
} else if (timelineItem.content is TimelineItemPollContent) {
add(TimelineItemAction.EditPoll)
} else {
add(TimelineItemAction.Edit)
}

View File

@@ -203,6 +203,7 @@ fun aTimelineItemActionList(
fun aTimelineItemPollActionList(): ImmutableList<TimelineItemAction> {
return setOf(
TimelineItemAction.EndPoll,
TimelineItemAction.EditPoll,
TimelineItemAction.Reply,
TimelineItemAction.Pin,
TimelineItemAction.CopyLink,

View File

@@ -11,30 +11,30 @@ import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import androidx.compose.runtime.Immutable
import io.element.android.libraries.designsystem.icons.CompoundDrawables
import io.element.android.libraries.designsystem.utils.CommonDrawables
import io.element.android.libraries.ui.strings.CommonStrings
@Immutable
sealed class TimelineItemAction(
enum class TimelineItemAction(
@StringRes val titleRes: Int,
@DrawableRes val icon: Int,
val destructive: Boolean = false
) {
data object ViewInTimeline : TimelineItemAction(CommonStrings.action_view_in_timeline, CompoundDrawables.ic_compound_visibility_on)
data object Forward : TimelineItemAction(CommonStrings.action_forward, CompoundDrawables.ic_compound_forward)
data object CopyText : TimelineItemAction(CommonStrings.action_copy_text, CompoundDrawables.ic_compound_copy)
data object CopyCaption : TimelineItemAction(CommonStrings.action_copy_caption, CompoundDrawables.ic_compound_copy)
data object CopyLink : TimelineItemAction(CommonStrings.action_copy_link_to_message, CompoundDrawables.ic_compound_link)
data object Redact : TimelineItemAction(CommonStrings.action_remove, CompoundDrawables.ic_compound_delete, destructive = true)
data object Reply : TimelineItemAction(CommonStrings.action_reply, CompoundDrawables.ic_compound_reply)
data object ReplyInThread : TimelineItemAction(CommonStrings.action_reply_in_thread, CompoundDrawables.ic_compound_reply)
data object Edit : TimelineItemAction(CommonStrings.action_edit, CompoundDrawables.ic_compound_edit)
data object EditCaption : TimelineItemAction(CommonStrings.action_edit_caption, CompoundDrawables.ic_compound_edit)
data object AddCaption : TimelineItemAction(CommonStrings.action_add_caption, CompoundDrawables.ic_compound_edit)
data object RemoveCaption : TimelineItemAction(CommonStrings.action_remove_caption, CompoundDrawables.ic_compound_delete, destructive = true)
data object ViewSource : TimelineItemAction(CommonStrings.action_view_source, CommonDrawables.ic_developer_options)
data object ReportContent : TimelineItemAction(CommonStrings.action_report_content, CompoundDrawables.ic_compound_chat_problem, destructive = true)
data object EndPoll : TimelineItemAction(CommonStrings.action_end_poll, CompoundDrawables.ic_compound_polls_end)
data object Pin : TimelineItemAction(CommonStrings.action_pin, CompoundDrawables.ic_compound_pin)
data object Unpin : TimelineItemAction(CommonStrings.action_unpin, CompoundDrawables.ic_compound_unpin)
ViewInTimeline(CommonStrings.action_view_in_timeline, CompoundDrawables.ic_compound_visibility_on),
Forward(CommonStrings.action_forward, CompoundDrawables.ic_compound_forward),
CopyText(CommonStrings.action_copy_text, CompoundDrawables.ic_compound_copy),
CopyCaption(CommonStrings.action_copy_caption, CompoundDrawables.ic_compound_copy),
CopyLink(CommonStrings.action_copy_link_to_message, CompoundDrawables.ic_compound_link),
Redact(CommonStrings.action_remove, CompoundDrawables.ic_compound_delete, destructive = true),
Reply(CommonStrings.action_reply, CompoundDrawables.ic_compound_reply),
ReplyInThread(CommonStrings.action_reply_in_thread, CompoundDrawables.ic_compound_reply),
Edit(CommonStrings.action_edit, CompoundDrawables.ic_compound_edit),
EditPoll(CommonStrings.action_edit_poll, CompoundDrawables.ic_compound_edit),
EditCaption(CommonStrings.action_edit_caption, CompoundDrawables.ic_compound_edit),
AddCaption(CommonStrings.action_add_caption, CompoundDrawables.ic_compound_edit),
RemoveCaption(CommonStrings.action_remove_caption, CompoundDrawables.ic_compound_close, destructive = true),
ViewSource(CommonStrings.action_view_source, CompoundDrawables.ic_compound_code),
ReportContent(CommonStrings.action_report_content, CompoundDrawables.ic_compound_chat_problem, destructive = true),
EndPoll(CommonStrings.action_end_poll, CompoundDrawables.ic_compound_polls_end),
Pin(CommonStrings.action_pin, CompoundDrawables.ic_compound_pin),
Unpin(CommonStrings.action_unpin, CompoundDrawables.ic_compound_unpin),
}

View File

@@ -7,21 +7,25 @@
package io.element.android.features.messages.impl.actionlist.model
import androidx.annotation.VisibleForTesting
class TimelineItemActionComparator : Comparator<TimelineItemAction> {
// See order in https://www.figma.com/design/ux3tYoZV9WghC7hHT9Fhk0/Compound-iOS-Components?node-id=2946-2392
private val orderedList = listOf(
@VisibleForTesting
val orderedList = listOf(
TimelineItemAction.EndPoll,
TimelineItemAction.ViewInTimeline,
TimelineItemAction.Reply,
TimelineItemAction.ReplyInThread,
TimelineItemAction.Forward,
TimelineItemAction.Pin,
TimelineItemAction.Unpin,
TimelineItemAction.CopyLink,
TimelineItemAction.Edit,
TimelineItemAction.CopyText,
TimelineItemAction.EditPoll,
TimelineItemAction.AddCaption,
TimelineItemAction.EditCaption,
TimelineItemAction.CopyLink,
TimelineItemAction.Pin,
TimelineItemAction.Unpin,
TimelineItemAction.CopyText,
TimelineItemAction.CopyCaption,
TimelineItemAction.RemoveCaption,
TimelineItemAction.ViewSource,

View File

@@ -14,9 +14,9 @@ class PinnedMessagesListTimelineActionPostProcessor : TimelineItemActionPostProc
override fun process(actions: List<TimelineItemAction>): List<TimelineItemAction> {
return buildList {
add(TimelineItemAction.ViewInTimeline)
actions.firstOrNull { it is TimelineItemAction.Unpin }?.let(::add)
actions.firstOrNull { it is TimelineItemAction.Forward }?.let(::add)
actions.firstOrNull { it is TimelineItemAction.ViewSource }?.let(::add)
actions.firstOrNull { it == TimelineItemAction.Unpin }?.let(::add)
actions.firstOrNull { it == TimelineItemAction.Forward }?.let(::add)
actions.firstOrNull { it == TimelineItemAction.ViewSource }?.let(::add)
}
}
}

View File

@@ -467,7 +467,7 @@ class MessagesPresenterTest {
presenter.present()
}.test {
val initialState = awaitItem()
initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.Edit, aMessageEvent(content = aTimelineItemPollContent())))
initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.EditPoll, aMessageEvent(content = aTimelineItemPollContent())))
awaitItem()
onEditPollClickLambda.assertions().isCalledOnce().with(value(AN_EVENT_ID))
}

View File

@@ -179,8 +179,8 @@ class ActionListPresenterTest {
actions = persistentListOf(
TimelineItemAction.Reply,
TimelineItemAction.Forward,
TimelineItemAction.Pin,
TimelineItemAction.CopyLink,
TimelineItemAction.Pin,
TimelineItemAction.CopyText,
TimelineItemAction.ViewSource,
TimelineItemAction.ReportContent,
@@ -225,8 +225,8 @@ class ActionListPresenterTest {
actions = persistentListOf(
TimelineItemAction.ReplyInThread,
TimelineItemAction.Forward,
TimelineItemAction.Pin,
TimelineItemAction.CopyLink,
TimelineItemAction.Pin,
TimelineItemAction.CopyText,
TimelineItemAction.ViewSource,
TimelineItemAction.ReportContent,
@@ -273,8 +273,8 @@ class ActionListPresenterTest {
verifiedUserSendFailure = VerifiedUserSendFailure.None,
actions = persistentListOf(
TimelineItemAction.Forward,
TimelineItemAction.Pin,
TimelineItemAction.CopyLink,
TimelineItemAction.Pin,
TimelineItemAction.CopyText,
TimelineItemAction.ViewSource,
TimelineItemAction.ReportContent,
@@ -320,8 +320,8 @@ class ActionListPresenterTest {
actions = persistentListOf(
TimelineItemAction.Reply,
TimelineItemAction.Forward,
TimelineItemAction.Pin,
TimelineItemAction.CopyLink,
TimelineItemAction.Pin,
TimelineItemAction.CopyText,
TimelineItemAction.ViewSource,
TimelineItemAction.ReportContent,
@@ -368,8 +368,8 @@ class ActionListPresenterTest {
actions = persistentListOf(
TimelineItemAction.Reply,
TimelineItemAction.Forward,
TimelineItemAction.Pin,
TimelineItemAction.CopyLink,
TimelineItemAction.Pin,
TimelineItemAction.CopyText,
TimelineItemAction.ViewSource,
TimelineItemAction.ReportContent,
@@ -417,9 +417,9 @@ class ActionListPresenterTest {
actions = persistentListOf(
TimelineItemAction.Reply,
TimelineItemAction.Forward,
TimelineItemAction.Pin,
TimelineItemAction.CopyLink,
TimelineItemAction.Edit,
TimelineItemAction.CopyLink,
TimelineItemAction.Pin,
TimelineItemAction.CopyText,
TimelineItemAction.ViewSource,
TimelineItemAction.Redact,
@@ -463,9 +463,9 @@ class ActionListPresenterTest {
actions = persistentListOf(
TimelineItemAction.ReplyInThread,
TimelineItemAction.Forward,
TimelineItemAction.Pin,
TimelineItemAction.CopyLink,
TimelineItemAction.Edit,
TimelineItemAction.CopyLink,
TimelineItemAction.Pin,
TimelineItemAction.CopyText,
TimelineItemAction.ViewSource,
TimelineItemAction.Redact,
@@ -512,9 +512,9 @@ class ActionListPresenterTest {
actions = persistentListOf(
TimelineItemAction.Reply,
TimelineItemAction.Forward,
TimelineItemAction.Pin,
TimelineItemAction.CopyLink,
TimelineItemAction.Edit,
TimelineItemAction.CopyLink,
TimelineItemAction.Pin,
TimelineItemAction.CopyText,
TimelineItemAction.ViewSource,
)
@@ -559,9 +559,9 @@ class ActionListPresenterTest {
actions = persistentListOf(
TimelineItemAction.Reply,
TimelineItemAction.Forward,
TimelineItemAction.Pin,
TimelineItemAction.CopyLink,
TimelineItemAction.AddCaption,
TimelineItemAction.CopyLink,
TimelineItemAction.Pin,
TimelineItemAction.ViewSource,
TimelineItemAction.Redact,
)
@@ -612,8 +612,8 @@ class ActionListPresenterTest {
TimelineItemAction.Forward,
// Not here
// TimelineItemAction.AddCaption,
TimelineItemAction.Pin,
TimelineItemAction.CopyLink,
TimelineItemAction.Pin,
TimelineItemAction.ViewSource,
TimelineItemAction.Redact,
)
@@ -660,9 +660,9 @@ class ActionListPresenterTest {
actions = persistentListOf(
TimelineItemAction.Reply,
TimelineItemAction.Forward,
TimelineItemAction.Pin,
TimelineItemAction.CopyLink,
TimelineItemAction.EditCaption,
TimelineItemAction.CopyLink,
TimelineItemAction.Pin,
TimelineItemAction.CopyCaption,
TimelineItemAction.RemoveCaption,
TimelineItemAction.ViewSource,
@@ -711,8 +711,8 @@ class ActionListPresenterTest {
actions = persistentListOf(
TimelineItemAction.Reply,
TimelineItemAction.Forward,
TimelineItemAction.Pin,
TimelineItemAction.CopyLink,
TimelineItemAction.Pin,
TimelineItemAction.CopyCaption,
TimelineItemAction.ViewSource,
TimelineItemAction.ReportContent,
@@ -830,9 +830,9 @@ class ActionListPresenterTest {
actions = persistentListOf(
TimelineItemAction.Reply,
TimelineItemAction.Forward,
TimelineItemAction.Pin,
TimelineItemAction.CopyLink,
TimelineItemAction.Edit,
TimelineItemAction.CopyLink,
TimelineItemAction.Pin,
TimelineItemAction.CopyText,
TimelineItemAction.Redact,
)
@@ -878,8 +878,8 @@ class ActionListPresenterTest {
actions = persistentListOf(
TimelineItemAction.Reply,
TimelineItemAction.Forward,
TimelineItemAction.CopyLink,
TimelineItemAction.Edit,
TimelineItemAction.CopyLink,
TimelineItemAction.CopyText,
TimelineItemAction.ViewSource,
TimelineItemAction.Redact,
@@ -933,9 +933,9 @@ class ActionListPresenterTest {
actions = persistentListOf(
TimelineItemAction.Reply,
TimelineItemAction.Forward,
TimelineItemAction.Unpin,
TimelineItemAction.CopyLink,
TimelineItemAction.Edit,
TimelineItemAction.CopyLink,
TimelineItemAction.Unpin,
TimelineItemAction.CopyText,
TimelineItemAction.ViewSource,
TimelineItemAction.Redact,
@@ -1072,9 +1072,9 @@ class ActionListPresenterTest {
actions = persistentListOf(
TimelineItemAction.EndPoll,
TimelineItemAction.Reply,
TimelineItemAction.Pin,
TimelineItemAction.EditPoll,
TimelineItemAction.CopyLink,
TimelineItemAction.Edit,
TimelineItemAction.Pin,
TimelineItemAction.Redact,
)
)
@@ -1116,8 +1116,8 @@ class ActionListPresenterTest {
actions = persistentListOf(
TimelineItemAction.EndPoll,
TimelineItemAction.Reply,
TimelineItemAction.Pin,
TimelineItemAction.CopyLink,
TimelineItemAction.Pin,
TimelineItemAction.Redact,
)
)
@@ -1158,8 +1158,8 @@ class ActionListPresenterTest {
verifiedUserSendFailure = VerifiedUserSendFailure.None,
actions = persistentListOf(
TimelineItemAction.Reply,
TimelineItemAction.Pin,
TimelineItemAction.CopyLink,
TimelineItemAction.Pin,
TimelineItemAction.Redact,
)
)
@@ -1203,8 +1203,8 @@ class ActionListPresenterTest {
actions = persistentListOf(
TimelineItemAction.Reply,
TimelineItemAction.Forward,
TimelineItemAction.Pin,
TimelineItemAction.CopyLink,
TimelineItemAction.Pin,
TimelineItemAction.Redact,
)
)

View File

@@ -0,0 +1,28 @@
/*
* Copyright 2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only
* Please see LICENSE in the repository root for full details.
*/
package io.element.android.features.messages.impl.actionlist.model
import org.junit.Test
class TimelineItemActionComparatorTest {
@Test
fun `check that the list in the comparator only contain each item once`() {
val sut = TimelineItemActionComparator()
sut.orderedList.forEach {
require(sut.orderedList.count { item -> item == it } == 1, { "Duplicate ${it::class.java}.$it" })
}
}
@Test
fun `check that the list in the comparator contains all the items`() {
val sut = TimelineItemActionComparator()
TimelineItemAction.entries.forEach {
require(it in sut.orderedList, { "Missing ${it::class.simpleName}.$it in orderedList" })
}
}
}

View File

@@ -27,24 +27,7 @@ class PinnedMessagesListTimelineActionPostProcessorTest {
fun `ensure that some actions are kept and some other are filtered out`() {
val sut = PinnedMessagesListTimelineActionPostProcessor()
val result = sut.process(
listOf(
TimelineItemAction.Forward,
TimelineItemAction.CopyText,
TimelineItemAction.CopyCaption,
TimelineItemAction.CopyLink,
TimelineItemAction.Redact,
TimelineItemAction.Reply,
TimelineItemAction.ReplyInThread,
TimelineItemAction.Edit,
TimelineItemAction.EditCaption,
TimelineItemAction.AddCaption,
TimelineItemAction.RemoveCaption,
TimelineItemAction.ViewSource,
TimelineItemAction.ReportContent,
TimelineItemAction.EndPoll,
TimelineItemAction.Pin,
TimelineItemAction.Unpin,
)
TimelineItemAction.entries.toList()
)
assertThat(result).isEqualTo(
listOf(

View File

@@ -25,6 +25,7 @@ import io.element.android.features.preferences.impl.user.UserPreferences
import io.element.android.libraries.architecture.coverage.ExcludeFromCoverage
import io.element.android.libraries.designsystem.components.list.ListItemContent
import io.element.android.libraries.designsystem.components.preferences.PreferencePage
import io.element.android.libraries.designsystem.icons.CompoundDrawables
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.designsystem.preview.PreviewWithLargeHeight
@@ -33,7 +34,6 @@ import io.element.android.libraries.designsystem.theme.components.IconSource
import io.element.android.libraries.designsystem.theme.components.ListItem
import io.element.android.libraries.designsystem.theme.components.ListItemStyle
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.designsystem.utils.CommonDrawables
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarHost
import io.element.android.libraries.designsystem.utils.snackbar.rememberSnackbarHostState
import io.element.android.libraries.matrix.api.core.DeviceId
@@ -270,7 +270,7 @@ private fun ColumnScope.Footer(
private fun DeveloperPreferencesView(onOpenDeveloperSettings: () -> Unit) {
ListItem(
headlineContent = { Text(stringResource(id = CommonStrings.common_developer_options)) },
leadingContent = ListItemContent.Icon(IconSource.Resource(CommonDrawables.ic_developer_options)),
leadingContent = ListItemContent.Icon(IconSource.Resource(CompoundDrawables.ic_compound_code)),
onClick = onOpenDeveloperSettings
)
}

View File

@@ -13,9 +13,7 @@ import io.element.android.libraries.designsystem.R
// All the icons should be defined in Compound.
internal val iconsOther = listOf(
R.drawable.ic_cancel,
R.drawable.ic_developer_options,
R.drawable.ic_encryption_enabled,
R.drawable.ic_groups,
R.drawable.ic_notification_small,
R.drawable.ic_plus_composer,
R.drawable.ic_stop,

View File

@@ -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:pathData="M8.825,12L10.3,10.525C10.5,10.325 10.6,10.092 10.6,9.825C10.6,9.558 10.5,9.325 10.3,9.125C10.1,8.925 9.863,8.825 9.587,8.825C9.313,8.825 9.075,8.925 8.875,9.125L6.7,11.3C6.6,11.4 6.529,11.508 6.488,11.625C6.446,11.742 6.425,11.867 6.425,12C6.425,12.133 6.446,12.258 6.488,12.375C6.529,12.492 6.6,12.6 6.7,12.7L8.875,14.875C9.075,15.075 9.313,15.175 9.587,15.175C9.863,15.175 10.1,15.075 10.3,14.875C10.5,14.675 10.6,14.442 10.6,14.175C10.6,13.908 10.5,13.675 10.3,13.475L8.825,12ZM15.175,12L13.7,13.475C13.5,13.675 13.4,13.908 13.4,14.175C13.4,14.442 13.5,14.675 13.7,14.875C13.9,15.075 14.137,15.175 14.413,15.175C14.688,15.175 14.925,15.075 15.125,14.875L17.3,12.7C17.4,12.6 17.471,12.492 17.513,12.375C17.554,12.258 17.575,12.133 17.575,12C17.575,11.867 17.554,11.742 17.513,11.625C17.471,11.508 17.4,11.4 17.3,11.3L15.125,9.125C15.025,9.025 14.913,8.95 14.788,8.9C14.663,8.85 14.538,8.825 14.413,8.825C14.288,8.825 14.163,8.85 14.038,8.9C13.913,8.95 13.8,9.025 13.7,9.125C13.5,9.325 13.4,9.558 13.4,9.825C13.4,10.092 13.5,10.325 13.7,10.525L15.175,12ZM5,21C4.45,21 3.979,20.804 3.588,20.413C3.196,20.021 3,19.55 3,19V5C3,4.45 3.196,3.979 3.588,3.588C3.979,3.196 4.45,3 5,3H19C19.55,3 20.021,3.196 20.413,3.588C20.804,3.979 21,4.45 21,5V19C21,19.55 20.804,20.021 20.413,20.413C20.021,20.804 19.55,21 19,21H5ZM5,19H19V5H5V19Z"
android:fillColor="#1B1D22"/>
</vector>

View File

@@ -1,15 +0,0 @@
<!--
~ Copyright 2023 New Vector Ltd.
~
~ SPDX-License-Identifier: AGPL-3.0-only
~ Please see LICENSE in the repository root for full details.
-->
<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="M1.365,17.788C1.12,17.788 0.915,17.705 0.749,17.54C0.583,17.374 0.5,17.168 0.5,16.923V16.569C0.5,15.894 0.844,15.346 1.531,14.927C2.219,14.508 3.126,14.298 4.251,14.298C4.454,14.298 4.647,14.305 4.829,14.318C5.011,14.332 5.187,14.355 5.358,14.389C5.168,14.681 5.028,14.995 4.94,15.332C4.852,15.668 4.808,16.024 4.808,16.4V17.788H1.365ZM7.408,17.788C7.147,17.788 6.931,17.702 6.759,17.529C6.586,17.355 6.5,17.141 6.5,16.885V16.438C6.5,15.483 7.006,14.713 8.018,14.128C9.03,13.543 10.356,13.25 11.996,13.25C13.651,13.25 14.982,13.543 15.989,14.128C16.996,14.713 17.5,15.483 17.5,16.438V16.885C17.5,17.141 17.413,17.355 17.24,17.529C17.067,17.702 16.852,17.788 16.596,17.788H7.408ZM19.192,17.788V16.4C19.192,16.024 19.145,15.668 19.05,15.332C18.955,14.995 18.819,14.681 18.642,14.389C18.813,14.355 18.989,14.332 19.17,14.318C19.352,14.305 19.545,14.298 19.75,14.298C20.875,14.298 21.781,14.508 22.469,14.927C23.156,15.346 23.5,15.894 23.5,16.569V16.923C23.5,17.168 23.417,17.374 23.251,17.54C23.085,17.705 22.88,17.788 22.635,17.788H19.192ZM12,14.75C10.954,14.75 10.059,14.892 9.315,15.176C8.572,15.46 8.159,15.799 8.077,16.192V16.288H15.923V16.192C15.831,15.788 15.415,15.447 14.677,15.168C13.938,14.889 13.046,14.75 12,14.75ZM4.25,13.327C3.777,13.327 3.375,13.159 3.044,12.824C2.713,12.489 2.548,12.086 2.548,11.615C2.548,11.145 2.713,10.742 3.044,10.407C3.375,10.071 3.777,9.904 4.25,9.904C4.723,9.904 5.127,10.071 5.461,10.407C5.794,10.742 5.961,11.145 5.961,11.615C5.961,12.086 5.794,12.489 5.459,12.824C5.124,13.159 4.721,13.327 4.25,13.327ZM19.75,13.327C19.277,13.327 18.873,13.159 18.539,12.824C18.205,12.489 18.038,12.086 18.038,11.615C18.038,11.145 18.206,10.742 18.541,10.407C18.876,10.071 19.279,9.904 19.75,9.904C20.223,9.904 20.625,10.071 20.956,10.407C21.286,10.742 21.452,11.145 21.452,11.615C21.452,12.086 21.286,12.489 20.956,12.824C20.625,13.159 20.223,13.327 19.75,13.327ZM12,12.5C11.279,12.5 10.666,12.248 10.161,11.743C9.656,11.238 9.404,10.625 9.404,9.904C9.404,9.183 9.656,8.57 10.161,8.065C10.666,7.56 11.279,7.308 12,7.308C12.721,7.308 13.334,7.56 13.839,8.065C14.344,8.57 14.596,9.183 14.596,9.904C14.596,10.625 14.344,11.238 13.839,11.743C13.334,12.248 12.721,12.5 12,12.5ZM12.002,8.808C11.691,8.808 11.431,8.913 11.22,9.122C11.009,9.332 10.904,9.592 10.904,9.902C10.904,10.212 11.009,10.473 11.218,10.684C11.428,10.895 11.688,11 11.998,11C12.308,11 12.569,10.895 12.78,10.685C12.991,10.476 13.096,10.216 13.096,9.906C13.096,9.595 12.991,9.335 12.781,9.124C12.572,8.913 12.312,8.808 12.002,8.808Z" />
</vector>