[Rich text editor] Add formatting menu (#1261)
--------- Co-authored-by: ElementBot <benoitm+elementbot@element.io>
This commit is contained in:
1
changelog.d/1261.feature
Normal file
1
changelog.d/1261.feature
Normal file
@@ -0,0 +1 @@
|
||||
[Rich text editor] Add formatting menu (accessible via the '+' button)
|
||||
@@ -26,6 +26,7 @@ import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.AttachFile
|
||||
import androidx.compose.material.icons.filled.BarChart
|
||||
import androidx.compose.material.icons.filled.Collections
|
||||
import androidx.compose.material.icons.filled.FormatColorText
|
||||
import androidx.compose.material.icons.filled.LocationOn
|
||||
import androidx.compose.material.icons.filled.PhotoCamera
|
||||
import androidx.compose.material.icons.filled.Videocam
|
||||
@@ -145,6 +146,11 @@ internal fun AttachmentSourcePickerMenu(
|
||||
text = { Text(stringResource(R.string.screen_room_attachment_source_poll)) },
|
||||
)
|
||||
}
|
||||
ListItem(
|
||||
modifier = Modifier.clickable { state.eventSink(MessageComposerEvents.ToggleTextFormatting(enabled = true)) },
|
||||
icon = { Icon(Icons.Default.FormatColorText, null) },
|
||||
text = { Text(stringResource(R.string.screen_room_attachment_text_formatting)) },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ sealed interface MessageComposerEvents {
|
||||
data object Location : PickAttachmentSource
|
||||
data object Poll : PickAttachmentSource
|
||||
}
|
||||
data class ToggleTextFormatting(val enabled: Boolean) : MessageComposerEvents
|
||||
data object CancelSendAttachment : MessageComposerEvents
|
||||
data class Error(val error: Throwable) : MessageComposerEvents
|
||||
}
|
||||
|
||||
@@ -110,6 +110,7 @@ class MessageComposerPresenter @Inject constructor(
|
||||
val ongoingSendAttachmentJob = remember { mutableStateOf<Job?>(null) }
|
||||
|
||||
var showAttachmentSourcePicker: Boolean by remember { mutableStateOf(false) }
|
||||
var showTextFormatting: Boolean by remember { mutableStateOf(false) }
|
||||
|
||||
LaunchedEffect(messageComposerContext.composerMode) {
|
||||
when (val modeValue = messageComposerContext.composerMode) {
|
||||
@@ -190,6 +191,10 @@ class MessageComposerPresenter @Inject constructor(
|
||||
ongoingSendAttachmentJob.value == null
|
||||
}
|
||||
}
|
||||
is MessageComposerEvents.ToggleTextFormatting -> {
|
||||
showAttachmentSourcePicker = false
|
||||
showTextFormatting = event.enabled
|
||||
}
|
||||
is MessageComposerEvents.Error -> {
|
||||
analyticsService.trackError(event.error)
|
||||
}
|
||||
@@ -201,6 +206,7 @@ class MessageComposerPresenter @Inject constructor(
|
||||
isFullScreen = isFullScreen.value,
|
||||
mode = messageComposerContext.composerMode,
|
||||
showAttachmentSourcePicker = showAttachmentSourcePicker,
|
||||
showTextFormatting = showTextFormatting,
|
||||
canShareLocation = canShareLocation.value,
|
||||
canCreatePoll = canCreatePoll.value,
|
||||
attachmentsState = attachmentsState.value,
|
||||
|
||||
@@ -28,6 +28,7 @@ data class MessageComposerState(
|
||||
val isFullScreen: Boolean,
|
||||
val mode: MessageComposerMode,
|
||||
val showAttachmentSourcePicker: Boolean,
|
||||
val showTextFormatting: Boolean,
|
||||
val canShareLocation: Boolean,
|
||||
val canCreatePoll: Boolean,
|
||||
val attachmentsState: AttachmentsState,
|
||||
|
||||
@@ -32,6 +32,7 @@ fun aMessageComposerState(
|
||||
composerState: RichTextEditorState = RichTextEditorState("", fake = true),
|
||||
isFullScreen: Boolean = false,
|
||||
mode: MessageComposerMode = MessageComposerMode.Normal(content = ""),
|
||||
showTextFormatting: Boolean = false,
|
||||
showAttachmentSourcePicker: Boolean = false,
|
||||
canShareLocation: Boolean = true,
|
||||
canCreatePoll: Boolean = true,
|
||||
@@ -40,6 +41,7 @@ fun aMessageComposerState(
|
||||
richTextEditorState = composerState.apply { if(requestFocus) requestFocus() },
|
||||
isFullScreen = isFullScreen,
|
||||
mode = mode,
|
||||
showTextFormatting = showTextFormatting,
|
||||
showAttachmentSourcePicker = showAttachmentSourcePicker,
|
||||
canShareLocation = canShareLocation,
|
||||
canCreatePoll = canCreatePoll,
|
||||
|
||||
@@ -49,6 +49,10 @@ fun MessageComposerView(
|
||||
state.eventSink(MessageComposerEvents.CloseSpecialMode)
|
||||
}
|
||||
|
||||
fun onDismissTextFormatting() {
|
||||
state.eventSink(MessageComposerEvents.ToggleTextFormatting(enabled = false))
|
||||
}
|
||||
|
||||
fun onError(error: Throwable) {
|
||||
state.eventSink(MessageComposerEvents.Error(error))
|
||||
}
|
||||
@@ -66,8 +70,10 @@ fun MessageComposerView(
|
||||
onRequestFocus = { state.richTextEditorState.requestFocus() },
|
||||
onSendMessage = ::sendMessage,
|
||||
composerMode = state.mode,
|
||||
showTextFormatting = state.showTextFormatting,
|
||||
onResetComposerMode = ::onCloseSpecialMode,
|
||||
onAddAttachment = ::onAddAttachment,
|
||||
onDismissTextFormatting = ::onDismissTextFormatting,
|
||||
onError = ::onError,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -539,6 +539,29 @@ class MessageComposerPresenterTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - ToggleTextFormatting toggles text formatting`() = runTest {
|
||||
val presenter = createPresenter(this)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
skipItems(1)
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.showTextFormatting).isFalse()
|
||||
initialState.eventSink(MessageComposerEvents.AddAttachment)
|
||||
val composerOptions = awaitItem()
|
||||
assertThat(composerOptions.showAttachmentSourcePicker).isTrue()
|
||||
composerOptions.eventSink(MessageComposerEvents.ToggleTextFormatting(true))
|
||||
awaitItem() // composer options closed
|
||||
val showTextFormatting = awaitItem()
|
||||
assertThat(showTextFormatting.showAttachmentSourcePicker).isFalse()
|
||||
assertThat(showTextFormatting.showTextFormatting).isTrue()
|
||||
showTextFormatting.eventSink(MessageComposerEvents.ToggleTextFormatting(false))
|
||||
val finished = awaitItem()
|
||||
assertThat(finished.showTextFormatting).isFalse()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun ReceiveTurbine<MessageComposerState>.backToNormalMode(state: MessageComposerState, skipCount: Int = 0) {
|
||||
state.eventSink.invoke(MessageComposerEvents.CloseSpecialMode)
|
||||
skipItems(skipCount)
|
||||
|
||||
@@ -28,4 +28,16 @@ object VectorIcons {
|
||||
val Groups = R.drawable.ic_groups
|
||||
val Share = R.drawable.ic_share
|
||||
val EndPoll = R.drawable.ic_poll_end
|
||||
val Bold = R.drawable.ic_bold
|
||||
val BulletList = R.drawable.ic_bullet_list
|
||||
val CodeBlock = R.drawable.ic_code_block
|
||||
val IndentIncrease = R.drawable.ic_indent_increase
|
||||
val IndentDecrease = R.drawable.ic_indent_decrease
|
||||
val InlineCode = R.drawable.ic_inline_code
|
||||
val Italic = R.drawable.ic_italic
|
||||
val Link = R.drawable.ic_link
|
||||
val NumberedList = R.drawable.ic_numbered_list
|
||||
val Quote = R.drawable.ic_quote
|
||||
val Strikethrough = R.drawable.ic_strikethrough
|
||||
val Underline = R.drawable.ic_underline
|
||||
}
|
||||
|
||||
9
libraries/designsystem/src/main/res/drawable/ic_bold.xml
Normal file
9
libraries/designsystem/src/main/res/drawable/ic_bold.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<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.8,19C8.25,19 7.779,18.804 7.388,18.413C6.996,18.021 6.8,17.55 6.8,17V7C6.8,6.45 6.996,5.979 7.388,5.588C7.779,5.196 8.25,5 8.8,5H12.325C13.408,5 14.408,5.333 15.325,6C16.242,6.667 16.7,7.592 16.7,8.775C16.7,9.625 16.508,10.279 16.125,10.738C15.742,11.196 15.383,11.525 15.05,11.725C15.467,11.908 15.929,12.25 16.438,12.75C16.946,13.25 17.2,14 17.2,15C17.2,16.483 16.658,17.521 15.575,18.112C14.492,18.704 13.475,19 12.525,19H8.8ZM9.825,16.2H12.425C13.225,16.2 13.712,15.996 13.887,15.587C14.063,15.179 14.15,14.883 14.15,14.7C14.15,14.517 14.063,14.221 13.887,13.813C13.712,13.404 13.2,13.2 12.35,13.2H9.825V16.2ZM9.825,10.5H12.15C12.7,10.5 13.1,10.358 13.35,10.075C13.6,9.792 13.725,9.475 13.725,9.125C13.725,8.725 13.583,8.4 13.3,8.15C13.017,7.9 12.65,7.775 12.2,7.775H9.825V10.5Z"
|
||||
android:fillColor="#656D77"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,9 @@
|
||||
<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="M10,19C9.717,19 9.479,18.904 9.288,18.712C9.096,18.521 9,18.283 9,18C9,17.717 9.096,17.479 9.288,17.288C9.479,17.096 9.717,17 10,17H20C20.283,17 20.521,17.096 20.712,17.288C20.904,17.479 21,17.717 21,18C21,18.283 20.904,18.521 20.712,18.712C20.521,18.904 20.283,19 20,19H10ZM10,13C9.717,13 9.479,12.904 9.288,12.712C9.096,12.521 9,12.283 9,12C9,11.717 9.096,11.479 9.288,11.288C9.479,11.096 9.717,11 10,11H20C20.283,11 20.521,11.096 20.712,11.288C20.904,11.479 21,11.717 21,12C21,12.283 20.904,12.521 20.712,12.712C20.521,12.904 20.283,13 20,13H10ZM10,7C9.717,7 9.479,6.904 9.288,6.713C9.096,6.521 9,6.283 9,6C9,5.717 9.096,5.479 9.288,5.287C9.479,5.096 9.717,5 10,5H20C20.283,5 20.521,5.096 20.712,5.287C20.904,5.479 21,5.717 21,6C21,6.283 20.904,6.521 20.712,6.713C20.521,6.904 20.283,7 20,7H10ZM5,20C4.45,20 3.979,19.804 3.588,19.413C3.196,19.021 3,18.55 3,18C3,17.45 3.196,16.979 3.588,16.587C3.979,16.196 4.45,16 5,16C5.55,16 6.021,16.196 6.412,16.587C6.804,16.979 7,17.45 7,18C7,18.55 6.804,19.021 6.412,19.413C6.021,19.804 5.55,20 5,20ZM5,14C4.45,14 3.979,13.804 3.588,13.413C3.196,13.021 3,12.55 3,12C3,11.45 3.196,10.979 3.588,10.587C3.979,10.196 4.45,10 5,10C5.55,10 6.021,10.196 6.412,10.587C6.804,10.979 7,11.45 7,12C7,12.55 6.804,13.021 6.412,13.413C6.021,13.804 5.55,14 5,14ZM5,8C4.45,8 3.979,7.804 3.588,7.412C3.196,7.021 3,6.55 3,6C3,5.45 3.196,4.979 3.588,4.588C3.979,4.196 4.45,4 5,4C5.55,4 6.021,4.196 6.412,4.588C6.804,4.979 7,5.45 7,6C7,6.55 6.804,7.021 6.412,7.412C6.021,7.804 5.55,8 5,8Z"
|
||||
android:fillColor="#656D77"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,9 @@
|
||||
<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="#656D77"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,9 @@
|
||||
<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="M4,21C3.717,21 3.479,20.904 3.287,20.712C3.096,20.521 3,20.283 3,20C3,19.717 3.096,19.479 3.287,19.288C3.479,19.096 3.717,19 4,19H20C20.283,19 20.521,19.096 20.712,19.288C20.904,19.479 21,19.717 21,20C21,20.283 20.904,20.521 20.712,20.712C20.521,20.904 20.283,21 20,21H4ZM12,17C11.717,17 11.479,16.904 11.288,16.712C11.096,16.521 11,16.283 11,16C11,15.717 11.096,15.479 11.288,15.288C11.479,15.096 11.717,15 12,15H20C20.283,15 20.521,15.096 20.712,15.288C20.904,15.479 21,15.717 21,16C21,16.283 20.904,16.521 20.712,16.712C20.521,16.904 20.283,17 20,17H12ZM12,13C11.717,13 11.479,12.904 11.288,12.712C11.096,12.521 11,12.283 11,12C11,11.717 11.096,11.479 11.288,11.288C11.479,11.096 11.717,11 12,11H20C20.283,11 20.521,11.096 20.712,11.288C20.904,11.479 21,11.717 21,12C21,12.283 20.904,12.521 20.712,12.712C20.521,12.904 20.283,13 20,13H12ZM12,9C11.717,9 11.479,8.904 11.288,8.712C11.096,8.521 11,8.283 11,8C11,7.717 11.096,7.479 11.288,7.287C11.479,7.096 11.717,7 12,7H20C20.283,7 20.521,7.096 20.712,7.287C20.904,7.479 21,7.717 21,8C21,8.283 20.904,8.521 20.712,8.712C20.521,8.904 20.283,9 20,9H12ZM4,5C3.717,5 3.479,4.904 3.287,4.713C3.096,4.521 3,4.283 3,4C3,3.717 3.096,3.479 3.287,3.287C3.479,3.096 3.717,3 4,3H20C20.283,3 20.521,3.096 20.712,3.287C20.904,3.479 21,3.717 21,4C21,4.283 20.904,4.521 20.712,4.713C20.521,4.904 20.283,5 20,5H4ZM6.15,15.15L3.35,12.35C3.25,12.25 3.2,12.133 3.2,12C3.2,11.867 3.25,11.75 3.35,11.65L6.15,8.85C6.317,8.683 6.5,8.642 6.7,8.725C6.9,8.808 7,8.967 7,9.2V14.8C7,15.033 6.9,15.192 6.7,15.275C6.5,15.358 6.317,15.317 6.15,15.15Z"
|
||||
android:fillColor="#656D77"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,9 @@
|
||||
<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="M4,21C3.717,21 3.479,20.904 3.287,20.712C3.096,20.521 3,20.283 3,20C3,19.717 3.096,19.479 3.287,19.288C3.479,19.096 3.717,19 4,19H20C20.283,19 20.521,19.096 20.712,19.288C20.904,19.479 21,19.717 21,20C21,20.283 20.904,20.521 20.712,20.712C20.521,20.904 20.283,21 20,21H4ZM12,17C11.717,17 11.479,16.904 11.288,16.712C11.096,16.521 11,16.283 11,16C11,15.717 11.096,15.479 11.288,15.288C11.479,15.096 11.717,15 12,15H20C20.283,15 20.521,15.096 20.712,15.288C20.904,15.479 21,15.717 21,16C21,16.283 20.904,16.521 20.712,16.712C20.521,16.904 20.283,17 20,17H12ZM12,13C11.717,13 11.479,12.904 11.288,12.712C11.096,12.521 11,12.283 11,12C11,11.717 11.096,11.479 11.288,11.288C11.479,11.096 11.717,11 12,11H20C20.283,11 20.521,11.096 20.712,11.288C20.904,11.479 21,11.717 21,12C21,12.283 20.904,12.521 20.712,12.712C20.521,12.904 20.283,13 20,13H12ZM12,9C11.717,9 11.479,8.904 11.288,8.712C11.096,8.521 11,8.283 11,8C11,7.717 11.096,7.479 11.288,7.287C11.479,7.096 11.717,7 12,7H20C20.283,7 20.521,7.096 20.712,7.287C20.904,7.479 21,7.717 21,8C21,8.283 20.904,8.521 20.712,8.712C20.521,8.904 20.283,9 20,9H12ZM4,5C3.717,5 3.479,4.904 3.287,4.713C3.096,4.521 3,4.283 3,4C3,3.717 3.096,3.479 3.287,3.287C3.479,3.096 3.717,3 4,3H20C20.283,3 20.521,3.096 20.712,3.287C20.904,3.479 21,3.717 21,4C21,4.283 20.904,4.521 20.712,4.713C20.521,4.904 20.283,5 20,5H4ZM3.85,15.15C3.683,15.317 3.5,15.358 3.3,15.275C3.1,15.192 3,15.033 3,14.8V9.2C3,8.967 3.1,8.808 3.3,8.725C3.5,8.642 3.683,8.683 3.85,8.85L6.65,11.65C6.75,11.75 6.8,11.867 6.8,12C6.8,12.133 6.75,12.25 6.65,12.35L3.85,15.15Z"
|
||||
android:fillColor="#656D77"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,15 @@
|
||||
<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="M14.958,5.621C15.116,5.092 14.816,4.534 14.287,4.375C13.758,4.217 13.201,4.517 13.042,5.046L9.042,18.379C8.883,18.908 9.184,19.466 9.713,19.625C10.242,19.783 10.799,19.483 10.958,18.954L14.958,5.621Z"
|
||||
android:fillColor="#656D77"/>
|
||||
<path
|
||||
android:pathData="M5.974,7.232C5.549,6.878 4.919,6.936 4.565,7.36L1.232,11.36C0.923,11.731 0.923,12.269 1.232,12.64L4.565,16.64C4.919,17.065 5.549,17.122 5.974,16.768C6.398,16.415 6.455,15.784 6.102,15.36L3.302,12L6.102,8.64C6.455,8.216 6.398,7.585 5.974,7.232Z"
|
||||
android:fillColor="#656D77"/>
|
||||
<path
|
||||
android:pathData="M18.027,7.232C18.451,6.878 19.081,6.936 19.435,7.36L22.768,11.36C23.077,11.731 23.077,12.269 22.768,12.64L19.435,16.64C19.081,17.065 18.451,17.122 18.027,16.768C17.602,16.415 17.545,15.784 17.898,15.36L20.698,12L17.898,8.64C17.545,8.216 17.602,7.585 18.027,7.232Z"
|
||||
android:fillColor="#656D77"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,9 @@
|
||||
<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="M6.25,19C5.9,19 5.604,18.879 5.363,18.638C5.121,18.396 5,18.1 5,17.75C5,17.4 5.121,17.104 5.363,16.862C5.604,16.621 5.9,16.5 6.25,16.5H9L12,7.5H9.25C8.9,7.5 8.604,7.379 8.363,7.137C8.121,6.896 8,6.6 8,6.25C8,5.9 8.121,5.604 8.363,5.363C8.604,5.121 8.9,5 9.25,5H16.75C17.1,5 17.396,5.121 17.638,5.363C17.879,5.604 18,5.9 18,6.25C18,6.6 17.879,6.896 17.638,7.137C17.396,7.379 17.1,7.5 16.75,7.5H14.5L11.5,16.5H13.75C14.1,16.5 14.396,16.621 14.637,16.862C14.879,17.104 15,17.4 15,17.75C15,18.1 14.879,18.396 14.637,18.638C14.396,18.879 14.1,19 13.75,19H6.25Z"
|
||||
android:fillColor="#656D77"/>
|
||||
</vector>
|
||||
9
libraries/designsystem/src/main/res/drawable/ic_link.xml
Normal file
9
libraries/designsystem/src/main/res/drawable/ic_link.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<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="M12,19.071C11.022,20.049 9.843,20.538 8.464,20.538C7.086,20.538 5.907,20.049 4.929,19.071C3.951,18.093 3.462,16.914 3.462,15.536C3.462,14.157 3.951,12.978 4.929,12L7.05,9.879C7.251,9.678 7.486,9.578 7.757,9.578C8.028,9.578 8.264,9.678 8.464,9.879C8.665,10.079 8.765,10.315 8.765,10.586C8.765,10.857 8.665,11.093 8.464,11.293L6.343,13.414C5.754,14.003 5.459,14.711 5.459,15.536C5.459,16.361 5.754,17.068 6.343,17.657C6.932,18.246 7.639,18.541 8.464,18.541C9.289,18.541 9.997,18.246 10.586,17.657L12.707,15.536C12.907,15.335 13.143,15.235 13.414,15.235C13.685,15.235 13.921,15.335 14.121,15.536C14.322,15.736 14.422,15.972 14.422,16.243C14.422,16.514 14.322,16.749 14.121,16.95L12,19.071ZM10.586,14.828C10.385,15.029 10.15,15.129 9.879,15.129C9.608,15.129 9.372,15.029 9.172,14.828C8.971,14.628 8.871,14.392 8.871,14.121C8.871,13.85 8.971,13.615 9.172,13.414L13.414,9.172C13.615,8.971 13.85,8.871 14.121,8.871C14.392,8.871 14.628,8.971 14.828,9.172C15.029,9.372 15.129,9.608 15.129,9.879C15.129,10.15 15.029,10.385 14.828,10.586L10.586,14.828ZM16.95,14.121C16.749,14.322 16.514,14.422 16.243,14.422C15.972,14.422 15.736,14.322 15.535,14.121C15.335,13.921 15.235,13.685 15.235,13.414C15.235,13.143 15.335,12.908 15.535,12.707L17.657,10.586C18.246,9.997 18.541,9.289 18.541,8.465C18.541,7.64 18.246,6.932 17.657,6.343C17.068,5.754 16.361,5.459 15.535,5.459C14.711,5.459 14.003,5.754 13.414,6.343L11.293,8.465C11.092,8.665 10.857,8.765 10.586,8.765C10.315,8.765 10.079,8.665 9.879,8.465C9.678,8.264 9.578,8.028 9.578,7.757C9.578,7.486 9.678,7.251 9.879,7.05L12,4.929C12.978,3.951 14.157,3.462 15.535,3.462C16.914,3.462 18.093,3.951 19.071,4.929C20.049,5.907 20.538,7.086 20.538,8.465C20.538,9.843 20.049,11.022 19.071,12L16.95,14.121Z"
|
||||
android:fillColor="#656D77"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,9 @@
|
||||
<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="M3.75,22C3.533,22 3.354,21.929 3.213,21.788C3.071,21.646 3,21.467 3,21.25C3,21.033 3.071,20.854 3.213,20.712C3.354,20.571 3.533,20.5 3.75,20.5H5.5V19.75H4.75C4.533,19.75 4.354,19.679 4.213,19.538C4.071,19.396 4,19.217 4,19C4,18.783 4.071,18.604 4.213,18.462C4.354,18.321 4.533,18.25 4.75,18.25H5.5V17.5H3.75C3.533,17.5 3.354,17.429 3.213,17.288C3.071,17.146 3,16.967 3,16.75C3,16.533 3.071,16.354 3.213,16.212C3.354,16.071 3.533,16 3.75,16H6C6.283,16 6.521,16.096 6.713,16.288C6.904,16.479 7,16.717 7,17V18C7,18.283 6.904,18.521 6.713,18.712C6.521,18.904 6.283,19 6,19C6.283,19 6.521,19.096 6.713,19.288C6.904,19.479 7,19.717 7,20V21C7,21.283 6.904,21.521 6.713,21.712C6.521,21.904 6.283,22 6,22H3.75ZM3.75,15C3.533,15 3.354,14.929 3.213,14.788C3.071,14.646 3,14.467 3,14.25V12.25C3,11.967 3.096,11.729 3.287,11.538C3.479,11.346 3.717,11.25 4,11.25H5.5V10.5H3.75C3.533,10.5 3.354,10.429 3.213,10.288C3.071,10.146 3,9.967 3,9.75C3,9.533 3.071,9.354 3.213,9.212C3.354,9.071 3.533,9 3.75,9H6C6.283,9 6.521,9.096 6.713,9.288C6.904,9.479 7,9.717 7,10V11.75C7,12.033 6.904,12.271 6.713,12.462C6.521,12.654 6.283,12.75 6,12.75H4.5V13.5H6.25C6.467,13.5 6.646,13.571 6.787,13.712C6.929,13.854 7,14.033 7,14.25C7,14.467 6.929,14.646 6.787,14.788C6.646,14.929 6.467,15 6.25,15H3.75ZM5.25,8C5.033,8 4.854,7.929 4.713,7.787C4.571,7.646 4.5,7.467 4.5,7.25V3.5H3.75C3.533,3.5 3.354,3.429 3.213,3.287C3.071,3.146 3,2.967 3,2.75C3,2.533 3.071,2.354 3.213,2.213C3.354,2.071 3.533,2 3.75,2H5.25C5.467,2 5.646,2.071 5.787,2.213C5.929,2.354 6,2.533 6,2.75V7.25C6,7.467 5.929,7.646 5.787,7.787C5.646,7.929 5.467,8 5.25,8ZM10,19C9.717,19 9.479,18.904 9.288,18.712C9.096,18.521 9,18.283 9,18C9,17.717 9.096,17.479 9.288,17.288C9.479,17.096 9.717,17 10,17H20C20.283,17 20.521,17.096 20.712,17.288C20.904,17.479 21,17.717 21,18C21,18.283 20.904,18.521 20.712,18.712C20.521,18.904 20.283,19 20,19H10ZM10,13C9.717,13 9.479,12.904 9.288,12.712C9.096,12.521 9,12.283 9,12C9,11.717 9.096,11.479 9.288,11.288C9.479,11.096 9.717,11 10,11H20C20.283,11 20.521,11.096 20.712,11.288C20.904,11.479 21,11.717 21,12C21,12.283 20.904,12.521 20.712,12.712C20.521,12.904 20.283,13 20,13H10ZM10,7C9.717,7 9.479,6.904 9.288,6.713C9.096,6.521 9,6.283 9,6C9,5.717 9.096,5.479 9.288,5.287C9.479,5.096 9.717,5 10,5H20C20.283,5 20.521,5.096 20.712,5.287C20.904,5.479 21,5.717 21,6C21,6.283 20.904,6.521 20.712,6.713C20.521,6.904 20.283,7 20,7H10Z"
|
||||
android:fillColor="#656D77"/>
|
||||
</vector>
|
||||
18
libraries/designsystem/src/main/res/drawable/ic_quote.xml
Normal file
18
libraries/designsystem/src/main/res/drawable/ic_quote.xml
Normal file
@@ -0,0 +1,18 @@
|
||||
<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="M4.719,4.34C4.813,3.698 4.353,3.104 3.691,3.012C3.028,2.92 2.415,3.366 2.32,4.008L1.512,9.486C1.418,10.128 1.878,10.723 2.54,10.814C3.203,10.906 3.816,10.46 3.911,9.818L4.719,4.34Z"
|
||||
android:fillColor="#656D77"/>
|
||||
<path
|
||||
android:pathData="M16.834,14.514C16.928,13.872 16.468,13.277 15.806,13.186C15.144,13.094 14.53,13.54 14.435,14.182L13.627,19.66C13.533,20.302 13.993,20.896 14.656,20.988C15.318,21.08 15.932,20.634 16.026,19.992L16.834,14.514Z"
|
||||
android:fillColor="#656D77"/>
|
||||
<path
|
||||
android:pathData="M9.318,3.009C9.983,3.086 10.456,3.671 10.376,4.315L10.354,4.49C10.34,4.602 10.319,4.763 10.293,4.961C10.242,5.358 10.17,5.902 10.088,6.496C9.927,7.667 9.72,9.075 9.553,9.882C9.422,10.518 8.784,10.931 8.128,10.803C7.472,10.676 7.046,10.058 7.177,9.422C7.326,8.701 7.523,7.37 7.687,6.185C7.767,5.599 7.838,5.061 7.889,4.669C7.915,4.473 7.935,4.314 7.949,4.204L7.97,4.034C8.05,3.39 8.654,2.931 9.318,3.009Z"
|
||||
android:fillColor="#656D77"/>
|
||||
<path
|
||||
android:pathData="M22.488,14.514C22.582,13.872 22.122,13.277 21.46,13.186C20.797,13.094 20.184,13.54 20.089,14.182L19.281,19.66C19.187,20.302 19.647,20.896 20.309,20.988C20.972,21.08 21.585,20.634 21.68,19.992L22.488,14.514Z"
|
||||
android:fillColor="#656D77"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,9 @@
|
||||
<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="M12.15,20C10.883,20 9.758,19.625 8.775,18.875C7.792,18.125 7.083,17.1 6.65,15.8L8.85,14.85C9.083,15.65 9.488,16.308 10.063,16.825C10.637,17.342 11.35,17.6 12.2,17.6C12.9,17.6 13.533,17.433 14.1,17.1C14.667,16.767 14.95,16.233 14.95,15.5C14.95,15.2 14.892,14.925 14.775,14.675C14.658,14.425 14.5,14.2 14.3,14H17.1C17.183,14.233 17.246,14.471 17.288,14.712C17.329,14.954 17.35,15.217 17.35,15.5C17.35,16.933 16.837,18.042 15.813,18.825C14.788,19.608 13.567,20 12.15,20ZM3,12C2.717,12 2.479,11.904 2.287,11.712C2.096,11.521 2,11.283 2,11C2,10.717 2.096,10.479 2.287,10.288C2.479,10.096 2.717,10 3,10H21C21.283,10 21.521,10.096 21.712,10.288C21.904,10.479 22,10.717 22,11C22,11.283 21.904,11.521 21.712,11.712C21.521,11.904 21.283,12 21,12H3ZM12.05,3.85C13.15,3.85 14.113,4.121 14.938,4.662C15.762,5.204 16.4,6.033 16.85,7.15L14.65,8.125C14.5,7.642 14.221,7.208 13.813,6.825C13.404,6.442 12.833,6.25 12.1,6.25C11.417,6.25 10.85,6.404 10.4,6.713C9.95,7.021 9.7,7.45 9.65,8H7.25C7.283,6.85 7.738,5.871 8.613,5.063C9.488,4.254 10.633,3.85 12.05,3.85Z"
|
||||
android:fillColor="#656D77"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,9 @@
|
||||
<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="M6,21C5.717,21 5.479,20.904 5.287,20.712C5.096,20.521 5,20.283 5,20C5,19.717 5.096,19.479 5.287,19.288C5.479,19.096 5.717,19 6,19H18C18.283,19 18.521,19.096 18.712,19.288C18.904,19.479 19,19.717 19,20C19,20.283 18.904,20.521 18.712,20.712C18.521,20.904 18.283,21 18,21H6ZM12,17C10.317,17 9.008,16.475 8.075,15.425C7.142,14.375 6.675,12.983 6.675,11.25V4.275C6.675,3.925 6.804,3.625 7.063,3.375C7.321,3.125 7.625,3 7.975,3C8.325,3 8.625,3.125 8.875,3.375C9.125,3.625 9.25,3.925 9.25,4.275V11.4C9.25,12.333 9.483,13.092 9.95,13.675C10.417,14.258 11.1,14.55 12,14.55C12.9,14.55 13.583,14.258 14.05,13.675C14.517,13.092 14.75,12.333 14.75,11.4V4.275C14.75,3.925 14.879,3.625 15.137,3.375C15.396,3.125 15.7,3 16.05,3C16.4,3 16.7,3.125 16.95,3.375C17.2,3.625 17.325,3.925 17.325,4.275V11.25C17.325,12.983 16.858,14.375 15.925,15.425C14.992,16.475 13.683,17 12,17Z"
|
||||
android:fillColor="#656D77"/>
|
||||
</vector>
|
||||
@@ -31,6 +31,8 @@ dependencies {
|
||||
implementation(projects.libraries.matrix.api)
|
||||
implementation(projects.libraries.matrixui)
|
||||
implementation(projects.libraries.designsystem)
|
||||
implementation(libs.androidx.constraintlayout)
|
||||
implementation(libs.androidx.constraintlayout.compose)
|
||||
|
||||
implementation(libs.matrix.richtexteditor)
|
||||
api(libs.matrix.richtexteditor.compose)
|
||||
|
||||
@@ -18,29 +18,29 @@ package io.element.android.libraries.textcomposer
|
||||
|
||||
import androidx.compose.animation.core.animateDpAsState
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.horizontalScroll
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.BoxScope
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.heightIn
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Close
|
||||
import androidx.compose.material.ripple.rememberRipple
|
||||
import androidx.compose.material3.LocalContentColor
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
@@ -51,21 +51,22 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.ColorFilter
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.constraintlayout.compose.ConstraintLayout
|
||||
import androidx.constraintlayout.compose.Dimension.Companion.fillToConstraints
|
||||
import androidx.constraintlayout.compose.Visibility
|
||||
import io.element.android.libraries.designsystem.VectorIcons
|
||||
import io.element.android.libraries.designsystem.modifiers.applyIf
|
||||
import io.element.android.libraries.designsystem.preview.DayNightPreviews
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.text.applyScaleUp
|
||||
import io.element.android.libraries.designsystem.theme.components.Icon
|
||||
import io.element.android.libraries.designsystem.theme.components.Surface
|
||||
import io.element.android.libraries.designsystem.theme.components.IconButton
|
||||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.core.TransactionId
|
||||
@@ -73,12 +74,17 @@ import io.element.android.libraries.matrix.api.media.MediaSource
|
||||
import io.element.android.libraries.matrix.ui.components.AttachmentThumbnail
|
||||
import io.element.android.libraries.matrix.ui.components.AttachmentThumbnailInfo
|
||||
import io.element.android.libraries.matrix.ui.components.AttachmentThumbnailType
|
||||
import io.element.android.libraries.textcomposer.components.FormattingOption
|
||||
import io.element.android.libraries.textcomposer.components.FormattingOptionState
|
||||
import io.element.android.libraries.theme.ElementTheme
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
import io.element.android.wysiwyg.compose.RichTextEditor
|
||||
import io.element.android.wysiwyg.compose.RichTextEditorDefaults
|
||||
import io.element.android.wysiwyg.compose.RichTextEditorState
|
||||
import io.element.android.wysiwyg.view.models.InlineFormat
|
||||
import kotlinx.coroutines.android.awaitFrame
|
||||
import uniffi.wysiwyg_composer.ActionState
|
||||
import uniffi.wysiwyg_composer.ComposerAction
|
||||
|
||||
@Composable
|
||||
fun TextComposer(
|
||||
@@ -86,112 +92,130 @@ fun TextComposer(
|
||||
composerMode: MessageComposerMode,
|
||||
canSendMessage: Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
showTextFormatting: Boolean = false,
|
||||
onRequestFocus: () -> Unit = {},
|
||||
onSendMessage: (Message) -> Unit = {},
|
||||
onResetComposerMode: () -> Unit = {},
|
||||
onAddAttachment: () -> Unit = {},
|
||||
onDismissTextFormatting: () -> Unit = {},
|
||||
onError: (Throwable) -> Unit = {},
|
||||
) {
|
||||
Row(
|
||||
modifier.padding(
|
||||
horizontal = 12.dp,
|
||||
vertical = 8.dp
|
||||
), verticalAlignment = Alignment.Bottom
|
||||
) {
|
||||
AttachmentButton(onClick = onAddAttachment, modifier = Modifier.padding(vertical = 6.dp))
|
||||
Spacer(modifier = Modifier.width(12.dp))
|
||||
val roundCornerSmall = 20.dp.applyScaleUp()
|
||||
val roundCornerLarge = 28.dp.applyScaleUp()
|
||||
val onSendClicked = {
|
||||
onSendMessage(Message(html = state.messageHtml, markdown = state.messageMarkdown))
|
||||
}
|
||||
|
||||
val roundedCornerSize = remember(state.lineCount, composerMode) {
|
||||
if (state.lineCount > 1 || composerMode is MessageComposerMode.Special) {
|
||||
roundCornerSmall
|
||||
} else {
|
||||
roundCornerLarge
|
||||
}
|
||||
}
|
||||
val roundedCornerSizeState = animateDpAsState(
|
||||
targetValue = roundedCornerSize,
|
||||
animationSpec = tween(
|
||||
durationMillis = 100,
|
||||
Column(
|
||||
modifier = modifier
|
||||
.padding(
|
||||
start = 3.dp,
|
||||
end = 6.dp,
|
||||
top = 8.dp,
|
||||
bottom = 5.dp,
|
||||
)
|
||||
)
|
||||
val roundedCorners = RoundedCornerShape(roundedCornerSizeState.value)
|
||||
val minHeight = 42.dp.applyScaleUp()
|
||||
val colors = ElementTheme.colors
|
||||
val bgColor = colors.bgSubtleSecondary
|
||||
|
||||
val borderColor by remember(state.hasFocus, colors) {
|
||||
derivedStateOf {
|
||||
if (state.hasFocus) colors.borderDisabled else bgColor
|
||||
}
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clip(roundedCorners)
|
||||
.background(color = bgColor)
|
||||
.border(1.dp, borderColor, roundedCorners)
|
||||
.fillMaxWidth(),
|
||||
) {
|
||||
ConstraintLayout(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) {
|
||||
if (composerMode is MessageComposerMode.Special) {
|
||||
ComposerModeView(composerMode = composerMode, onResetComposerMode = onResetComposerMode)
|
||||
val (composeOptions, textInput, sendButton) = createRefs()
|
||||
val showComposerOptionsButton by remember(showTextFormatting) {
|
||||
derivedStateOf { !showTextFormatting }
|
||||
}
|
||||
val defaultTypography = ElementTheme.typography.fontBodyLgRegular
|
||||
|
||||
Box {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.heightIn(min = minHeight)
|
||||
.background(color = bgColor, shape = roundedCorners)
|
||||
.padding(
|
||||
PaddingValues(
|
||||
top = 4.dp.applyScaleUp(),
|
||||
bottom = 4.dp.applyScaleUp(),
|
||||
start = 12.dp.applyScaleUp(),
|
||||
end = 42.dp.applyScaleUp()
|
||||
)
|
||||
),
|
||||
contentAlignment = Alignment.CenterStart,
|
||||
) {
|
||||
|
||||
// Placeholder
|
||||
if (state.messageHtml.isEmpty()) {
|
||||
Text(
|
||||
stringResource(CommonStrings.common_message),
|
||||
style = defaultTypography.copy(
|
||||
color = ElementTheme.colors.textDisabled,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
RichTextEditor(
|
||||
state = state,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth(),
|
||||
style = RichTextEditorDefaults.style(
|
||||
text = RichTextEditorDefaults.textStyle(
|
||||
color = if (state.hasFocus) {
|
||||
MaterialTheme.colorScheme.primary
|
||||
} else {
|
||||
MaterialTheme.colorScheme.secondary
|
||||
}
|
||||
),
|
||||
cursor = RichTextEditorDefaults.cursorStyle(
|
||||
color = ElementTheme.colors.iconAccentTertiary,
|
||||
)
|
||||
),
|
||||
onError = onError
|
||||
)
|
||||
}
|
||||
|
||||
SendButton(
|
||||
canSendMessage = canSendMessage,
|
||||
onClick = { onSendMessage(Message(html = state.messageHtml, markdown = state.messageMarkdown)) },
|
||||
composerMode = composerMode,
|
||||
modifier = Modifier.padding(end = 6.dp.applyScaleUp(), bottom = 6.dp.applyScaleUp())
|
||||
IconButton(
|
||||
modifier = Modifier
|
||||
.size(48.dp)
|
||||
.constrainAs(composeOptions) {
|
||||
start.linkTo(parent.start)
|
||||
bottom.linkTo(parent.bottom)
|
||||
visibility = if (showComposerOptionsButton) Visibility.Visible else Visibility.Gone
|
||||
},
|
||||
onClick = onAddAttachment
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier.size(30.dp.applyScaleUp()),
|
||||
resourceId = R.drawable.ic_plus, // TODO Replace with design system icon when available
|
||||
contentDescription = stringResource(R.string.rich_text_editor_a11y_add_attachment),
|
||||
tint = ElementTheme.colors.iconPrimary,
|
||||
)
|
||||
}
|
||||
val roundCornerSmall = 20.dp.applyScaleUp()
|
||||
val roundCornerLarge = 28.dp.applyScaleUp()
|
||||
|
||||
val roundedCornerSize = remember(state.lineCount, composerMode) {
|
||||
if (state.lineCount > 1 || composerMode is MessageComposerMode.Special) {
|
||||
roundCornerSmall
|
||||
} else {
|
||||
roundCornerLarge
|
||||
}
|
||||
}
|
||||
val roundedCornerSizeState = animateDpAsState(
|
||||
targetValue = roundedCornerSize,
|
||||
animationSpec = tween(
|
||||
durationMillis = 100,
|
||||
)
|
||||
)
|
||||
val roundedCorners = RoundedCornerShape(roundedCornerSizeState.value)
|
||||
val colors = ElementTheme.colors
|
||||
val bgColor = colors.bgSubtleSecondary
|
||||
|
||||
val borderColor by remember(state.hasFocus, colors) {
|
||||
derivedStateOf {
|
||||
if (state.hasFocus) colors.borderDisabled else bgColor
|
||||
}
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.constrainAs(textInput) {
|
||||
start.linkTo(composeOptions.end, margin = 3.dp, goneMargin = 9.dp)
|
||||
end.linkTo(sendButton.start, margin = 6.dp, goneMargin = 6.dp)
|
||||
bottom.linkTo(parent.bottom)
|
||||
width = fillToConstraints
|
||||
}
|
||||
.padding(vertical = 3.dp)
|
||||
.fillMaxWidth()
|
||||
.clip(roundedCorners)
|
||||
.background(color = bgColor)
|
||||
.border(1.dp, borderColor, roundedCorners)
|
||||
) {
|
||||
if (composerMode is MessageComposerMode.Special) {
|
||||
ComposerModeView(composerMode = composerMode, onResetComposerMode = onResetComposerMode)
|
||||
}
|
||||
|
||||
TextInput(
|
||||
state = state,
|
||||
roundedCorners = roundedCorners,
|
||||
bgColor = bgColor,
|
||||
onError = onError,
|
||||
)
|
||||
}
|
||||
|
||||
SendButton(
|
||||
canSendMessage = canSendMessage,
|
||||
onClick = onSendClicked,
|
||||
composerMode = composerMode,
|
||||
modifier = Modifier
|
||||
.constrainAs(sendButton) {
|
||||
bottom.linkTo(parent.bottom)
|
||||
end.linkTo(parent.end)
|
||||
visibility = if (!showTextFormatting) Visibility.Visible else Visibility.Gone
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
if (showTextFormatting) {
|
||||
TextFormatting(
|
||||
state = state,
|
||||
onDismiss = onDismissTextFormatting,
|
||||
sendButton = {
|
||||
SendButton(
|
||||
canSendMessage = canSendMessage,
|
||||
onClick = onSendClicked,
|
||||
composerMode = composerMode,
|
||||
modifier = it
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -208,6 +232,192 @@ fun TextComposer(
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TextInput(
|
||||
state: RichTextEditorState,
|
||||
roundedCorners: RoundedCornerShape,
|
||||
bgColor: Color,
|
||||
modifier: Modifier = Modifier,
|
||||
onError: (Throwable) -> Unit = {},
|
||||
) {
|
||||
val minHeight = 42.dp.applyScaleUp()
|
||||
val defaultTypography = ElementTheme.typography.fontBodyLgRegular
|
||||
Box(
|
||||
modifier = modifier
|
||||
.heightIn(min = minHeight)
|
||||
.background(color = bgColor, shape = roundedCorners)
|
||||
.padding(
|
||||
PaddingValues(
|
||||
top = 4.dp.applyScaleUp(),
|
||||
bottom = 4.dp.applyScaleUp(),
|
||||
start = 12.dp.applyScaleUp(),
|
||||
end = 42.dp.applyScaleUp()
|
||||
)
|
||||
),
|
||||
contentAlignment = Alignment.CenterStart,
|
||||
) {
|
||||
|
||||
// Placeholder
|
||||
if (state.messageHtml.isEmpty()) {
|
||||
Text(
|
||||
stringResource(CommonStrings.common_message),
|
||||
style = defaultTypography.copy(
|
||||
color = ElementTheme.colors.textDisabled,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
RichTextEditor(
|
||||
state = state,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth(),
|
||||
style = RichTextEditorDefaults.style(
|
||||
text = RichTextEditorDefaults.textStyle(
|
||||
color = if (state.hasFocus) {
|
||||
MaterialTheme.colorScheme.primary
|
||||
} else {
|
||||
MaterialTheme.colorScheme.secondary
|
||||
}
|
||||
),
|
||||
cursor = RichTextEditorDefaults.cursorStyle(
|
||||
color = ElementTheme.colors.iconAccentTertiary,
|
||||
)
|
||||
),
|
||||
onError = onError
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TextFormatting(
|
||||
state: RichTextEditorState,
|
||||
onDismiss: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
sendButton: @Composable (modifier: Modifier) -> Unit,
|
||||
) {
|
||||
ConstraintLayout(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
) {
|
||||
val (close, formatting, send) = createRefs()
|
||||
|
||||
IconButton(
|
||||
modifier = Modifier
|
||||
.size(48.dp)
|
||||
.constrainAs(close) {
|
||||
start.linkTo(parent.start)
|
||||
top.linkTo(parent.top)
|
||||
bottom.linkTo(parent.bottom)
|
||||
},
|
||||
onClick = onDismiss
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier.size(30.dp.applyScaleUp()),
|
||||
resourceId = R.drawable.ic_cancel, // TODO Replace with design system icon when available
|
||||
contentDescription = stringResource(CommonStrings.action_close),
|
||||
tint = ElementTheme.colors.iconPrimary,
|
||||
)
|
||||
}
|
||||
|
||||
val scrollState = rememberScrollState()
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.constrainAs(formatting) {
|
||||
top.linkTo(parent.top)
|
||||
bottom.linkTo(parent.bottom)
|
||||
start.linkTo(close.end, margin = 3.dp)
|
||||
end.linkTo(send.start, margin = 20.dp)
|
||||
width = fillToConstraints
|
||||
}
|
||||
.horizontalScroll(scrollState),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||
) {
|
||||
FormattingOption(
|
||||
state = state.actions[ComposerAction.BOLD].toButtonState(),
|
||||
onClick = { state.toggleInlineFormat(InlineFormat.Bold) },
|
||||
imageVector = ImageVector.vectorResource(VectorIcons.Bold),
|
||||
contentDescription = stringResource(CommonStrings.rich_text_editor_format_bold)
|
||||
)
|
||||
FormattingOption(
|
||||
state = state.actions[ComposerAction.ITALIC].toButtonState(),
|
||||
onClick = { state.toggleInlineFormat(InlineFormat.Italic) },
|
||||
imageVector = ImageVector.vectorResource(VectorIcons.Italic),
|
||||
contentDescription = stringResource(CommonStrings.rich_text_editor_format_italic)
|
||||
)
|
||||
FormattingOption(
|
||||
state = state.actions[ComposerAction.UNDERLINE].toButtonState(),
|
||||
onClick = { state.toggleInlineFormat(InlineFormat.Underline) },
|
||||
imageVector = ImageVector.vectorResource(VectorIcons.Underline),
|
||||
contentDescription = stringResource(CommonStrings.rich_text_editor_format_underline)
|
||||
)
|
||||
FormattingOption(
|
||||
state = state.actions[ComposerAction.STRIKE_THROUGH].toButtonState(),
|
||||
onClick = { state.toggleInlineFormat(InlineFormat.StrikeThrough) },
|
||||
imageVector = ImageVector.vectorResource(VectorIcons.Strikethrough),
|
||||
contentDescription = stringResource(CommonStrings.rich_text_editor_format_strikethrough)
|
||||
)
|
||||
FormattingOption(
|
||||
state = state.actions[ComposerAction.UNORDERED_LIST].toButtonState(),
|
||||
onClick = { state.toggleList(ordered = false) },
|
||||
imageVector = ImageVector.vectorResource(VectorIcons.BulletList),
|
||||
contentDescription = stringResource(CommonStrings.rich_text_editor_bullet_list)
|
||||
)
|
||||
FormattingOption(
|
||||
state = state.actions[ComposerAction.ORDERED_LIST].toButtonState(),
|
||||
onClick = { state.toggleList(ordered = true) },
|
||||
imageVector = ImageVector.vectorResource(VectorIcons.NumberedList),
|
||||
contentDescription = stringResource(CommonStrings.rich_text_editor_numbered_list)
|
||||
)
|
||||
FormattingOption(
|
||||
state = state.actions[ComposerAction.INDENT].toButtonState(),
|
||||
onClick = { state.indent() },
|
||||
imageVector = ImageVector.vectorResource(VectorIcons.IndentIncrease),
|
||||
contentDescription = stringResource(CommonStrings.rich_text_editor_indent)
|
||||
)
|
||||
FormattingOption(
|
||||
state = state.actions[ComposerAction.UNINDENT].toButtonState(),
|
||||
onClick = { state.unindent() },
|
||||
imageVector = ImageVector.vectorResource(VectorIcons.IndentDecrease),
|
||||
contentDescription = stringResource(CommonStrings.rich_text_editor_unindent)
|
||||
)
|
||||
FormattingOption(
|
||||
state = state.actions[ComposerAction.INLINE_CODE].toButtonState(),
|
||||
onClick = { state.toggleInlineFormat(InlineFormat.InlineCode) },
|
||||
imageVector = ImageVector.vectorResource(VectorIcons.InlineCode),
|
||||
contentDescription = stringResource(CommonStrings.rich_text_editor_inline_code)
|
||||
)
|
||||
FormattingOption(
|
||||
state = state.actions[ComposerAction.CODE_BLOCK].toButtonState(),
|
||||
onClick = { state.toggleCodeBlock() },
|
||||
imageVector = ImageVector.vectorResource(VectorIcons.CodeBlock),
|
||||
contentDescription = stringResource(CommonStrings.rich_text_editor_code_block)
|
||||
)
|
||||
FormattingOption(
|
||||
state = state.actions[ComposerAction.QUOTE].toButtonState(),
|
||||
onClick = { state.toggleQuote() },
|
||||
imageVector = ImageVector.vectorResource(VectorIcons.Quote),
|
||||
contentDescription = stringResource(CommonStrings.rich_text_editor_quote)
|
||||
)
|
||||
}
|
||||
|
||||
sendButton(
|
||||
Modifier.constrainAs(send) {
|
||||
top.linkTo(parent.top)
|
||||
bottom.linkTo(parent.bottom)
|
||||
end.linkTo(parent.end)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun ActionState?.toButtonState(): FormattingOptionState =
|
||||
when (this) {
|
||||
ActionState.ENABLED -> FormattingOptionState.Default
|
||||
ActionState.REVERSED -> FormattingOptionState.Selected
|
||||
ActionState.DISABLED, null -> FormattingOptionState.Disabled
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ComposerModeView(
|
||||
composerMode: MessageComposerMode,
|
||||
@@ -341,53 +551,17 @@ private fun ReplyToModeView(
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AttachmentButton(
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
Surface(
|
||||
modifier
|
||||
.size(30.dp.applyScaleUp())
|
||||
.clickable(onClick = onClick),
|
||||
shape = CircleShape,
|
||||
color = ElementTheme.colors.iconPrimary
|
||||
) {
|
||||
Image(
|
||||
modifier = Modifier.size(12.5f.dp.applyScaleUp()),
|
||||
painter = painterResource(R.drawable.ic_add_attachment),
|
||||
contentDescription = stringResource(R.string.rich_text_editor_a11y_add_attachment),
|
||||
contentScale = ContentScale.Inside,
|
||||
colorFilter = ColorFilter.tint(
|
||||
LocalContentColor.current
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun BoxScope.SendButton(
|
||||
private fun SendButton(
|
||||
canSendMessage: Boolean,
|
||||
onClick: () -> Unit,
|
||||
composerMode: MessageComposerMode,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val interactionSource = remember { MutableInteractionSource() }
|
||||
Box(
|
||||
IconButton(
|
||||
modifier = modifier
|
||||
.clip(CircleShape)
|
||||
.background(if (canSendMessage) ElementTheme.colors.iconAccentTertiary else Color.Transparent)
|
||||
.size(30.dp.applyScaleUp())
|
||||
.align(Alignment.BottomEnd)
|
||||
.applyIf(composerMode !is MessageComposerMode.Edit, ifTrue = {
|
||||
padding(start = 1.dp.applyScaleUp()) // Center the arrow in the circle
|
||||
})
|
||||
.clickable(
|
||||
enabled = canSendMessage,
|
||||
interactionSource = interactionSource,
|
||||
indication = rememberRipple(bounded = false),
|
||||
onClick = onClick,
|
||||
),
|
||||
contentAlignment = Alignment.Center,
|
||||
.size(48.dp.applyScaleUp()),
|
||||
onClick = onClick,
|
||||
enabled = canSendMessage,
|
||||
) {
|
||||
val iconId = when (composerMode) {
|
||||
is MessageComposerMode.Edit -> R.drawable.ic_tick
|
||||
@@ -397,13 +571,22 @@ private fun BoxScope.SendButton(
|
||||
is MessageComposerMode.Edit -> stringResource(CommonStrings.action_edit)
|
||||
else -> stringResource(CommonStrings.action_send)
|
||||
}
|
||||
Icon(
|
||||
modifier = Modifier.size(16.dp.applyScaleUp()),
|
||||
resourceId = iconId,
|
||||
contentDescription = contentDescription,
|
||||
// Exception here, we use Color.White instead of ElementTheme.colors.iconOnSolidPrimary
|
||||
tint = if (canSendMessage) Color.White else ElementTheme.colors.iconDisabled
|
||||
)
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.clip(CircleShape)
|
||||
.size(36.dp.applyScaleUp())
|
||||
.background(if (canSendMessage) ElementTheme.colors.iconAccentTertiary else Color.Transparent)
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier
|
||||
.height(18.dp.applyScaleUp())
|
||||
.align(Alignment.Center),
|
||||
resourceId = iconId,
|
||||
contentDescription = contentDescription,
|
||||
// Exception here, we use Color.White instead of ElementTheme.colors.iconOnSolidPrimary
|
||||
tint = if (canSendMessage) Color.White else ElementTheme.colors.iconDisabled
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -447,6 +630,31 @@ internal fun TextComposerSimplePreview() = ElementPreview {
|
||||
}
|
||||
}
|
||||
|
||||
@DayNightPreviews
|
||||
@Composable
|
||||
internal fun TextComposerFormattingPreview() = ElementPreview {
|
||||
Column {
|
||||
TextComposer(
|
||||
RichTextEditorState("", fake = true),
|
||||
canSendMessage = false,
|
||||
showTextFormatting = true,
|
||||
composerMode = MessageComposerMode.Normal(""),
|
||||
)
|
||||
TextComposer(
|
||||
RichTextEditorState("A message", fake = true),
|
||||
canSendMessage = true,
|
||||
showTextFormatting = true,
|
||||
composerMode = MessageComposerMode.Normal(""),
|
||||
)
|
||||
TextComposer(
|
||||
RichTextEditorState("A message\nWith several lines\nTo preview larger textfields and long lines with overflow", fake = true),
|
||||
canSendMessage = true,
|
||||
showTextFormatting = true,
|
||||
composerMode = MessageComposerMode.Normal(""),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@DayNightPreviews
|
||||
@Composable
|
||||
internal fun TextComposerEditPreview() = ElementPreview {
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright (c) 2023 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.textcomposer.components
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.libraries.designsystem.text.applyScaleUp
|
||||
import io.element.android.libraries.designsystem.theme.components.Icon
|
||||
import io.element.android.libraries.theme.ElementTheme
|
||||
import io.element.android.libraries.theme.compound.generated.SemanticColors
|
||||
|
||||
@Composable
|
||||
internal fun FormattingOption(
|
||||
state: FormattingOptionState,
|
||||
onClick: () -> Unit,
|
||||
imageVector: ImageVector,
|
||||
contentDescription: String,
|
||||
modifier: Modifier = Modifier,
|
||||
colors: SemanticColors = ElementTheme.colors,
|
||||
) {
|
||||
val backgroundColor = when (state) {
|
||||
FormattingOptionState.Selected -> colors.bgActionPrimaryRest
|
||||
FormattingOptionState.Default,
|
||||
FormattingOptionState.Disabled -> Color.Transparent
|
||||
}
|
||||
|
||||
val foregroundColor = when (state) {
|
||||
FormattingOptionState.Selected -> colors.iconOnSolidPrimary
|
||||
FormattingOptionState.Default -> colors.iconPrimary
|
||||
FormattingOptionState.Disabled -> colors.iconDisabled
|
||||
}
|
||||
Box(
|
||||
modifier = modifier
|
||||
.clickable { onClick() }
|
||||
.size(44.dp.applyScaleUp())
|
||||
.background(backgroundColor, shape = RoundedCornerShape(4.dp.applyScaleUp()))
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier.align(Alignment.Center),
|
||||
imageVector = imageVector,
|
||||
contentDescription = contentDescription,
|
||||
tint = foregroundColor,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright (c) 2023 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.textcomposer.components
|
||||
|
||||
internal enum class FormattingOptionState {
|
||||
Default, Selected, Disabled
|
||||
}
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="20dp"
|
||||
android:height="20dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M480,773Q457.91,773 441.46,756.54Q425,740.09 425,718.31L425,535L241.69,535Q219.91,535 203.46,518.54Q187,502.09 187,480Q187,457.91 203.46,441.46Q219.91,425 241.69,425L425,425L425,241.69Q425,219.91 441.46,203.46Q457.91,187 480,187Q502.09,187 518.54,203.46Q535,219.91 535,241.69L535,425L718.31,425Q740.09,425 756.54,441.46Q773,457.91 773,480Q773,502.09 756.54,518.54Q740.09,535 718.31,535L535,535L535,718.31Q535,740.09 518.54,756.54Q502.09,773 480,773Z"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="32dp"
|
||||
android:height="32dp"
|
||||
android:viewportWidth="32"
|
||||
android:viewportHeight="32">
|
||||
<path
|
||||
android:pathData="M16,18.121L19.182,21.303C19.483,21.604 19.836,21.754 20.243,21.754C20.649,21.754 21.003,21.604 21.303,21.303C21.604,21.003 21.754,20.649 21.754,20.243C21.754,19.836 21.604,19.483 21.303,19.182L18.121,16L21.303,12.818C21.604,12.517 21.754,12.164 21.754,11.757C21.754,11.351 21.604,10.997 21.303,10.697C21.003,10.396 20.649,10.246 20.243,10.246C19.836,10.246 19.483,10.396 19.182,10.697L16,13.879L12.818,10.697C12.517,10.396 12.164,10.246 11.757,10.246C11.351,10.246 10.997,10.396 10.697,10.697C10.396,10.997 10.246,11.351 10.246,11.757C10.246,12.164 10.396,12.517 10.697,12.818L13.879,16L10.697,19.182C10.396,19.483 10.246,19.836 10.246,20.243C10.246,20.649 10.396,21.003 10.697,21.303C10.997,21.604 11.351,21.754 11.757,21.754C12.164,21.754 12.517,21.604 12.818,21.303L16,18.121ZM26.607,26.607C25.139,28.074 23.482,29.174 21.635,29.908C19.787,30.642 17.909,31.008 16,31.008C14.091,31.008 12.213,30.642 10.365,29.908C8.518,29.174 6.861,28.074 5.393,26.607C3.926,25.139 2.826,23.482 2.092,21.635C1.358,19.787 0.992,17.909 0.992,16C0.992,14.091 1.358,12.213 2.092,10.365C2.826,8.518 3.926,6.861 5.393,5.393C6.861,3.926 8.518,2.826 10.365,2.092C12.213,1.358 14.091,0.992 16,0.992C17.909,0.992 19.787,1.358 21.635,2.092C23.482,2.826 25.139,3.926 26.607,5.393C28.074,6.861 29.174,8.518 29.908,10.365C30.642,12.213 31.008,14.091 31.008,16C31.008,17.909 30.642,19.787 29.908,21.635C29.174,23.482 28.074,25.139 26.607,26.607Z"
|
||||
android:fillColor="#1B1D22"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="30dp"
|
||||
android:height="30dp"
|
||||
android:viewportWidth="30"
|
||||
android:viewportHeight="30">
|
||||
<path
|
||||
android:pathData="M13.5,16.5V21C13.5,21.425 13.644,21.781 13.931,22.069C14.219,22.356 14.575,22.5 15,22.5C15.425,22.5 15.781,22.356 16.069,22.069C16.356,21.781 16.5,21.425 16.5,21V16.5H21C21.425,16.5 21.781,16.356 22.069,16.069C22.356,15.781 22.5,15.425 22.5,15C22.5,14.575 22.356,14.219 22.069,13.931C21.781,13.644 21.425,13.5 21,13.5H16.5V9C16.5,8.575 16.356,8.219 16.069,7.931C15.781,7.644 15.425,7.5 15,7.5C14.575,7.5 14.219,7.644 13.931,7.931C13.644,8.219 13.5,8.575 13.5,9V13.5H9C8.575,13.5 8.219,13.644 7.931,13.931C7.644,14.219 7.5,14.575 7.5,15C7.5,15.425 7.644,15.781 7.931,16.069C8.219,16.356 8.575,16.5 9,16.5H13.5ZM15,30C12.925,30 10.975,29.606 9.15,28.819C7.325,28.031 5.738,26.962 4.387,25.612C3.037,24.263 1.969,22.675 1.181,20.85C0.394,19.025 0,17.075 0,15C0,12.925 0.394,10.975 1.181,9.15C1.969,7.325 3.037,5.738 4.387,4.387C5.738,3.037 7.325,1.969 9.15,1.181C10.975,0.394 12.925,0 15,0C17.075,0 19.025,0.394 20.85,1.181C22.675,1.969 24.263,3.037 25.612,4.387C26.962,5.738 28.031,7.325 28.819,9.15C29.606,10.975 30,12.925 30,15C30,17.075 29.606,19.025 28.819,20.85C28.031,22.675 26.962,24.263 25.612,25.612C24.263,26.962 22.675,28.031 20.85,28.819C19.025,29.606 17.075,30 15,30Z"
|
||||
android:fillColor="#1B1D22"/>
|
||||
</vector>
|
||||
@@ -1,9 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="16dp"
|
||||
android:height="16dp"
|
||||
android:viewportWidth="16"
|
||||
android:viewportHeight="16">
|
||||
android:width="21dp"
|
||||
android:height="18dp"
|
||||
android:viewportWidth="21"
|
||||
android:viewportHeight="18">
|
||||
<path
|
||||
android:pathData="M15.404,8.965L1.563,15.882C0.631,16.348 -0.34,15.348 0.116,14.435C0.116,14.435 1.832,10.971 2.303,10.064C2.775,9.156 3.315,8.999 8.331,8.351C8.517,8.327 8.669,8.187 8.669,8C8.669,7.813 8.517,7.673 8.331,7.649C3.315,7.001 2.775,6.844 2.303,5.936C1.832,5.029 0.116,1.565 0.116,1.565C-0.34,0.653 0.631,-0.348 1.563,0.118L15.404,7.036C16.199,7.433 16.199,8.567 15.404,8.965Z"
|
||||
android:fillColor="#A6ADB7"/>
|
||||
android:pathData="M20.252,10.085 L4.681,17.867c-1.049,0.525 -2.141,-0.601 -1.628,-1.627 0,0 1.93,-3.897 2.461,-4.918 0.531,-1.021 1.138,-1.197 6.781,-1.927 0.209,-0.027 0.38,-0.185 0.38,-0.395 0,-0.21 -0.171,-0.368 -0.38,-0.395C6.652,7.876 6.045,7.699 5.514,6.678 4.983,5.658 3.053,1.76 3.053,1.76 2.54,0.734 3.632,-0.391 4.681,0.133L20.252,7.915c0.894,0.446 0.894,1.723 0,2.17z"
|
||||
android:fillColor="@android:color/white"/>
|
||||
</vector>
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user