diff --git a/generate_bubbles.sh b/generate_bubbles.sh
index 3faeaace61..98157f4fae 100755
--- a/generate_bubbles.sh
+++ b/generate_bubbles.sh
@@ -5,7 +5,7 @@ set -e
my_dir="$(dirname "$(realpath "$0")")"
pushd "$my_dir" > /dev/null
-res_dir="vector/src/main/res/"
+res_dir="vector/src/main/res"
god_bubble="vector/src/main/res/drawable/msg_godbubble.xml"
# Multiline sed -i
@@ -27,6 +27,7 @@ function create_msg_bubble() {
local is_rtl="$2"
local is_notice="$3"
local has_tail="$4"
+ local roundness="$5"
# Out file name
local out_bubble="$res_dir/drawable"
@@ -34,6 +35,9 @@ function create_msg_bubble() {
local out_bubble="$out_bubble-ldrtl"
fi
local out_bubble="$out_bubble/msg_bubble"
+ if [ ! -z "$roundness" ]; then
+ local out_bubble="${out_bubble}_$roundness"
+ fi
if ((is_notice)); then
local out_bubble="${out_bubble}_notice"
else
@@ -79,19 +83,25 @@ function create_msg_bubble() {
if ((is_outgoing)); then
sed -i 's|_incoming|_outgoing|g' "$out_bubble"
fi
+ # Modify roundness
+ if [ ! -z "$roundness" ]; then
+ sed -i "s|sc_bubble_radius|sc_bubble_${roundness}_radius|g" "$out_bubble"
+ fi
# Remove unneeded size, which only exists to make it look nicer in drawable preview
sed -i 's|||g' "$out_bubble"
}
-for is_outgoing in 0 1; do
- for is_rtl in 0 1; do
- # Notices are handled via transparency and do not need own drawables right now
- is_notice=0
- #for is_notice in 0 1; do
- for has_tail in 0 1; do
- create_msg_bubble "$is_outgoing" "$is_rtl" "$is_notice" "$has_tail"
- done
- #done
+for roundness in "" "r1" "r2"; do
+ for is_outgoing in 0 1; do
+ for is_rtl in 0 1; do
+ # Notices are handled via transparency and do not need own drawables right now
+ is_notice=0
+ #for is_notice in 0 1; do
+ for has_tail in 0 1; do
+ create_msg_bubble "$is_outgoing" "$is_rtl" "$is_notice" "$has_tail" "$roundness"
+ done
+ #done
+ done
done
done
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageImageVideoItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageImageVideoItem.kt
index 233af786ac..c8252a3c7a 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageImageVideoItem.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageImageVideoItem.kt
@@ -17,6 +17,7 @@
package im.vector.app.features.home.room.detail.timeline.item
import android.content.res.Resources
+import android.graphics.Bitmap
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
@@ -25,6 +26,7 @@ import androidx.core.view.ViewCompat
import androidx.core.view.isVisible
import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass
+import com.bumptech.glide.load.Transformation
import com.bumptech.glide.load.resource.bitmap.RoundedCorners
import im.vector.app.R
import im.vector.app.core.epoxy.ClickListener
@@ -37,7 +39,9 @@ import im.vector.app.features.home.room.detail.timeline.style.TimelineMessageLay
import im.vector.app.features.home.room.detail.timeline.style.granularRoundedCorners
import im.vector.app.features.home.room.detail.timeline.view.ScMessageBubbleWrapView
import im.vector.app.features.media.ImageContentRenderer
+import im.vector.app.features.themes.defaultScBubbleAppearance
import org.matrix.android.sdk.api.util.MimeTypes
+import kotlin.math.round
@EpoxyModelClass(layout = R.layout.item_timeline_event_base)
abstract class MessageImageVideoItem : AbsMessageItem() {
@@ -90,12 +94,23 @@ abstract class MessageImageVideoItem : AbsMessageItem RoundedCorners(dimensionConverter.dpToPx(3))
- is TimelineMessageLayout.Bubble -> messageLayout.cornersRadius.granularRoundedCorners()
- else -> RoundedCorners(dimensionConverter.dpToPx(8))
+ val cornerRoundnessDp: Int
+ val imageCornerTransformation: Transformation
+ when (messageLayout) {
+ is TimelineMessageLayout.ScBubble -> {
+ cornerRoundnessDp = round(messageLayout.bubbleAppearance.getBubbleRadiusDp(holder.view.context)).toInt()
+ imageCornerTransformation = RoundedCorners(dimensionConverter.dpToPx(cornerRoundnessDp))
+ }
+ is TimelineMessageLayout.Bubble -> {
+ cornerRoundnessDp = 8
+ imageCornerTransformation = messageLayout.cornersRadius.granularRoundedCorners()
+ }
+ else -> {
+ cornerRoundnessDp = 8
+ imageCornerTransformation = RoundedCorners(dimensionConverter.dpToPx(cornerRoundnessDp))
+ }
}
- imageContentRenderer.render(mediaData, effectiveMode, holder.imageView, imageCornerTransformation, onImageSizeListener)
+ imageContentRenderer.render(mediaData, effectiveMode, holder.imageView, cornerRoundnessDp, imageCornerTransformation, onImageSizeListener)
if (!attributes.informationData.sendState.hasFailed()) {
contentUploadStateTrackerBinder.bind(
attributes.informationData.eventId,
@@ -180,14 +195,16 @@ abstract class MessageImageVideoItem : AbsMessageItem {
- // Don't show it for non-bubble layouts, don't show for Stickers, ...
+ // Don't show it for non-bubble layouts, don't show for Stickers, ...
+ // Also only supported for default corner radius
+ !(messageLayout.isRealBubble || messageLayout.isPseudoBubble) || mode != ImageContentRenderer.Mode.THUMBNAIL
+ || messageLayout.bubbleAppearance != defaultScBubbleAppearance -> {
holder.mediaContentView.background = null
}
- attributes.informationData.sentByMe -> {
+ attributes.informationData.sentByMe -> {
holder.mediaContentView.setBackgroundResource(R.drawable.background_image_border_outgoing)
}
- else -> {
+ else -> {
holder.mediaContentView.setBackgroundResource(R.drawable.background_image_border_incoming)
}
}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageLocationItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageLocationItem.kt
index b8573de4a0..923a034c80 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageLocationItem.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageLocationItem.kt
@@ -61,7 +61,7 @@ abstract class MessageLocationItem : AbsMessageItem(
val messageLayout = attributes.informationData.messageLayout
val dimensionConverter = DimensionConverter(holder.view.resources)
val imageCornerTransformation = when (messageLayout) {
- is TimelineMessageLayout.ScBubble -> RoundedCorners(dimensionConverter.dpToPx(3))
+ is TimelineMessageLayout.ScBubble -> RoundedCorners(messageLayout.bubbleAppearance.getBubbleRadiusPx(holder.view.context))
is TimelineMessageLayout.Bubble -> messageLayout.cornersRadius.granularRoundedCorners()
else -> RoundedCorners(dimensionConverter.dpToPx(8))
}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayout.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayout.kt
index 847d03e338..c4242bb3b4 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayout.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayout.kt
@@ -17,8 +17,9 @@
package im.vector.app.features.home.room.detail.timeline.style
import android.os.Parcelable
+import androidx.annotation.DrawableRes
import im.vector.app.R
-import im.vector.app.features.home.room.detail.timeline.item.AnonymousReadReceipt
+import im.vector.app.features.themes.ScBubbleAppearance
import kotlinx.parcelize.Parcelize
sealed interface TimelineMessageLayout : Parcelable {
@@ -68,6 +69,7 @@ sealed interface TimelineMessageLayout : Parcelable {
override val showDisplayName: Boolean,
override val showTimestamp: Boolean = true,
override val showE2eDecoration: Boolean = false,
+ val bubbleAppearance: ScBubbleAppearance,
val isIncoming: Boolean,
val reverseBubble: Boolean,
val singleSidedLayout: Boolean,
@@ -79,6 +81,22 @@ sealed interface TimelineMessageLayout : Parcelable {
R.layout.item_timeline_event_sc_bubble_incoming_base
} else {
R.layout.item_timeline_event_sc_bubble_outgoing_base
+ },
+ @DrawableRes
+ val bubbleDrawable: Int = if (isPseudoBubble) {
+ 0
+ } else if (showAvatar) { // tail
+ if (reverseBubble) { // outgoing
+ bubbleAppearance.textBubbleOutgoing
+ } else { // incoming
+ bubbleAppearance.textBubbleIncoming
+ }
+ } else { // notail
+ if (reverseBubble) { // outgoing
+ bubbleAppearance.textBubbleOutgoingNoTail
+ } else { // incoming
+ bubbleAppearance.textBubbleIncomingNoTail
+ }
}
) : TimelineMessageLayout
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt
index ff38d57909..4134387d1d 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt
@@ -112,6 +112,7 @@ class TimelineMessageLayoutFactory @Inject constructor(private val session: Sess
// Display name still required for single sided layout if timestamp is shown (empty space looks bad otherwise)
showDisplayName = showInformation && ((singleSidedLayout && showTimestamp) || !messageContent.redundantDisplayName()),
showTimestamp = showTimestamp,
+ bubbleAppearance = bubbleThemeUtils.getBubbleAppearance(),
isIncoming = !isSentByMe,
isNotice = messageContent is MessageNoticeContent,
reverseBubble = isSentByMe && !singleSidedLayout,
@@ -168,6 +169,7 @@ class TimelineMessageLayoutFactory @Inject constructor(private val session: Sess
showAvatar = false,
showDisplayName = false,
showTimestamp = true,
+ bubbleAppearance = bubbleThemeUtils.getBubbleAppearance(),
isIncoming = false,
isNotice = false,
reverseBubble = false,
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/ScMessageBubbleWrapView.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/ScMessageBubbleWrapView.kt
index 85fc35f614..0574569b8c 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/ScMessageBubbleWrapView.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/ScMessageBubbleWrapView.kt
@@ -264,21 +264,8 @@ class ScMessageBubbleWrapView @JvmOverloads constructor(context: Context, attrs:
// Padding for bubble content: long for side with tail, short for other sides
val longPaddingDp: Int
val shortPaddingDp: Int
+ bubbleView.setBackgroundResource(messageLayout.bubbleDrawable)
if (!messageLayout.isPseudoBubble) {
- val bubbleRes = if (messageLayout.showAvatar) { // tail
- if (messageLayout.reverseBubble) { // outgoing
- R.drawable.msg_bubble_text_outgoing
- } else { // incoming
- R.drawable.msg_bubble_text_incoming
- }
- } else { // notail
- if (messageLayout.reverseBubble) { // outgoing
- R.drawable.msg_bubble_text_outgoing_notail
- } else { // incoming
- R.drawable.msg_bubble_text_incoming_notail
- }
- }
- bubbleView.setBackgroundResource(bubbleRes)
longPaddingDp = bubbleView.resources.getDimensionPixelSize(R.dimen.sc_bubble_inner_padding_long_side)
shortPaddingDp = bubbleView.resources.getDimensionPixelSize(R.dimen.sc_bubble_inner_padding_short_side)
} else {
diff --git a/vector/src/main/java/im/vector/app/features/media/ImageContentRenderer.kt b/vector/src/main/java/im/vector/app/features/media/ImageContentRenderer.kt
index 00cce78010..dc1bdcb171 100644
--- a/vector/src/main/java/im/vector/app/features/media/ImageContentRenderer.kt
+++ b/vector/src/main/java/im/vector/app/features/media/ImageContentRenderer.kt
@@ -146,7 +146,7 @@ class ImageContentRenderer @Inject constructor(private val localFilesHelper: Loc
.into(imageView)
}
- fun render(data: Data, mode: Mode, imageView: ImageView, cornerTransformation: Transformation = RoundedCorners(dimensionConverter.dpToPx(3)), onImageSizeListener: OnImageSizeListener? = null, animate: Boolean = false) {
+ fun render(data: Data, mode: Mode, imageView: ImageView, cornerRoundnessDp: Int = 3, cornerTransformation: Transformation = RoundedCorners(dimensionConverter.dpToPx(cornerRoundnessDp)), onImageSizeListener: OnImageSizeListener? = null, animate: Boolean = false) {
val size = processSize(data, mode)
imageView.updateLayoutParams {
width = size.width
@@ -177,8 +177,7 @@ class ImageContentRenderer @Inject constructor(private val localFilesHelper: Loc
})
request = if (animate) {
// Glide seems to already do some dp to px calculation for animated gifs?
- // SC-TODO extract dp from cornerTransformation
- request.transform(RoundedCorners(3))
+ request.transform(RoundedCorners(cornerRoundnessDp))
//request.apply(RequestOptions.bitmapTransform(RoundedCorners(3)))
} else {
request.dontAnimate()
diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsBubbleAppearanceFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsBubbleAppearanceFragment.kt
new file mode 100644
index 0000000000..29eff43f54
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsBubbleAppearanceFragment.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2019 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 im.vector.app.features.settings
+
+import im.vector.app.R
+import javax.inject.Inject
+
+class VectorSettingsBubbleAppearanceFragment @Inject constructor(
+ //private val vectorPreferences: VectorPreferences
+) : VectorSettingsBaseFragment() {
+
+ override var titleRes = R.string.bubble_appearance
+ override val preferenceXmlRes = R.xml.vector_settings_bubble_appearance
+
+ override fun bindPref() {
+ }
+}
diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsPreferencesFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsPreferencesFragment.kt
index 026f973dd9..a316d4afea 100644
--- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsPreferencesFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsPreferencesFragment.kt
@@ -43,11 +43,16 @@ class VectorSettingsPreferencesFragment @Inject constructor(
private val vectorPreferences: VectorPreferences
) : VectorSettingsBaseFragment() {
+ companion object {
+ const val BUBBLE_APPEARANCE_KEY = "BUBBLE_APPEARANCE_KEY"
+ }
+
override var titleRes = R.string.settings_preferences
override val preferenceXmlRes = R.xml.vector_settings_preferences
//private var bubbleTimeLocationPref: VectorListPreference? = null
private var alwaysShowTimestampsPref: VectorSwitchPreference? = null
+ private var bubbleAppearancePref: VectorPreference? = null
private val selectedLanguagePreference by lazy {
findPreference(VectorPreferences.SETTINGS_INTERFACE_LANGUAGE_PREFERENCE_KEY)!!
@@ -104,6 +109,7 @@ class VectorSettingsPreferencesFragment @Inject constructor(
}
alwaysShowTimestampsPref = findPreference(VectorPreferences.SETTINGS_ALWAYS_SHOW_TIMESTAMPS_KEY)
+ bubbleAppearancePref = findPreference(BUBBLE_APPEARANCE_KEY)
updateBubbleDependencies(bubbleStyle = bubbleStylePreference.value)
findPreference(VectorPreferences.SETTINGS_PREF_SPACE_SHOW_ALL_ROOM_IN_HOME)!!.let { pref ->
@@ -246,6 +252,7 @@ class VectorSettingsPreferencesFragment @Inject constructor(
private fun updateBubbleDependencies(bubbleStyle: String) {
//bubbleTimeLocationPref?.setEnabled(BubbleThemeUtils.isBubbleTimeLocationSettingAllowed(bubbleStyle))
- alwaysShowTimestampsPref?.setEnabled(bubbleStyle in listOf(BubbleThemeUtils.BUBBLE_STYLE_NONE, BubbleThemeUtils.BUBBLE_STYLE_START))
+ alwaysShowTimestampsPref?.isEnabled = bubbleStyle in listOf(BubbleThemeUtils.BUBBLE_STYLE_NONE, BubbleThemeUtils.BUBBLE_STYLE_START)
+ bubbleAppearancePref?.isEnabled = bubbleStyle in listOf(BubbleThemeUtils.BUBBLE_STYLE_START, BubbleThemeUtils.BUBBLE_STYLE_BOTH)
}
}
diff --git a/vector/src/main/java/im/vector/app/features/themes/BubbleThemeUtils.kt b/vector/src/main/java/im/vector/app/features/themes/BubbleThemeUtils.kt
index d0ebe7f34d..780c890032 100644
--- a/vector/src/main/java/im/vector/app/features/themes/BubbleThemeUtils.kt
+++ b/vector/src/main/java/im/vector/app/features/themes/BubbleThemeUtils.kt
@@ -2,9 +2,14 @@ package im.vector.app.features.themes
import android.content.Context
import android.graphics.Paint
+import android.os.Parcelable
import android.widget.TextView
+import androidx.annotation.DimenRes
+import androidx.annotation.DrawableRes
import androidx.preference.PreferenceManager
+import im.vector.app.R
import im.vector.app.features.home.room.detail.timeline.item.AnonymousReadReceipt
+import kotlinx.parcelize.Parcelize
import org.matrix.android.sdk.api.session.room.send.SendState
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
import timber.log.Timber
@@ -16,6 +21,11 @@ import javax.inject.Inject
class BubbleThemeUtils @Inject constructor(private val context: Context) {
companion object {
const val BUBBLE_STYLE_KEY = "BUBBLE_STYLE_KEY"
+ const val BUBBLE_ROUNDNESS_KEY = "SETTINGS_SC_BUBBLE_ROUNDED_CORNERS"
+ const val BUBBLE_ROUNDNESS_DEFAULT = "default"
+ const val BUBBLE_ROUNDNESS_R1 = "r1"
+ const val BUBBLE_ROUNDNESS_R2 = "r2"
+ const val BUBBLE_TAIL_KEY = "SETTINGS_SC_BUBBLE_TAIL"
const val BUBBLE_STYLE_NONE = "none"
const val BUBBLE_STYLE_ELEMENT = "element"
@@ -62,6 +72,22 @@ class BubbleThemeUtils @Inject constructor(private val context: Context) {
PreferenceManager.getDefaultSharedPreferences(context).edit().putString(BUBBLE_STYLE_KEY, value).apply()
}
+ fun getBubbleAppearance(): ScBubbleAppearance {
+ val prefs = PreferenceManager.getDefaultSharedPreferences(context)
+ val baseAppearance = when (prefs.getString(BUBBLE_ROUNDNESS_KEY, BUBBLE_ROUNDNESS_DEFAULT)) {
+ BUBBLE_ROUNDNESS_R1 -> r1ScBubbleAppearance
+ BUBBLE_ROUNDNESS_R2 -> r2ScBubbleAppearance
+ else -> defaultScBubbleAppearance
+ }
+ return if (prefs.getBoolean(BUBBLE_TAIL_KEY, true)) {
+ baseAppearance
+ } else {
+ baseAppearance.copy(
+ textBubbleOutgoing = baseAppearance.textBubbleOutgoingNoTail,
+ textBubbleIncoming = baseAppearance.textBubbleIncomingNoTail
+ )
+ }
+ }
}
fun guessTextWidth(view: TextView): Float {
@@ -77,3 +103,49 @@ fun guessTextWidth(textSize: Float, text: CharSequence): Float {
paint.textSize = textSize
return paint.measureText(text.toString())
}
+
+@Parcelize
+data class ScBubbleAppearance(
+ @DimenRes
+ val roundness: Int,
+ @DrawableRes
+ val textBubbleOutgoing: Int,
+ @DrawableRes
+ val textBubbleIncoming: Int,
+ @DrawableRes
+ val textBubbleOutgoingNoTail: Int,
+ @DrawableRes
+ val textBubbleIncomingNoTail: Int,
+) : Parcelable {
+ fun getBubbleRadiusPx(context: Context): Int {
+ return context.resources.getDimensionPixelSize(roundness)
+ }
+ fun getBubbleRadiusDp(context: Context): Float {
+ return context.resources.getDimension(roundness)
+ }
+}
+
+val defaultScBubbleAppearance = ScBubbleAppearance(
+ R.dimen.sc_bubble_radius,
+ R.drawable.msg_bubble_text_outgoing,
+ R.drawable.msg_bubble_text_incoming,
+ R.drawable.msg_bubble_text_outgoing_notail,
+ R.drawable.msg_bubble_text_incoming_notail,
+)
+
+val r1ScBubbleAppearance = ScBubbleAppearance(
+ R.dimen.sc_bubble_r1_radius,
+ R.drawable.msg_bubble_r1_text_outgoing,
+ R.drawable.msg_bubble_r1_text_incoming,
+ R.drawable.msg_bubble_r1_text_outgoing_notail,
+ R.drawable.msg_bubble_r1_text_incoming_notail,
+)
+
+
+val r2ScBubbleAppearance = ScBubbleAppearance(
+ R.dimen.sc_bubble_r2_radius,
+ R.drawable.msg_bubble_r2_text_outgoing,
+ R.drawable.msg_bubble_r2_text_incoming,
+ R.drawable.msg_bubble_r2_text_outgoing_notail,
+ R.drawable.msg_bubble_r2_text_incoming_notail,
+)
diff --git a/vector/src/main/res/drawable-ldrtl/msg_bubble_r1_text_incoming.xml b/vector/src/main/res/drawable-ldrtl/msg_bubble_r1_text_incoming.xml
new file mode 100644
index 0000000000..4a3e42c64b
--- /dev/null
+++ b/vector/src/main/res/drawable-ldrtl/msg_bubble_r1_text_incoming.xml
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
diff --git a/vector/src/main/res/drawable-ldrtl/msg_bubble_r1_text_incoming_notail.xml b/vector/src/main/res/drawable-ldrtl/msg_bubble_r1_text_incoming_notail.xml
new file mode 100644
index 0000000000..31ca8fc8a0
--- /dev/null
+++ b/vector/src/main/res/drawable-ldrtl/msg_bubble_r1_text_incoming_notail.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/vector/src/main/res/drawable-ldrtl/msg_bubble_r1_text_outgoing.xml b/vector/src/main/res/drawable-ldrtl/msg_bubble_r1_text_outgoing.xml
new file mode 100644
index 0000000000..cf9f23d270
--- /dev/null
+++ b/vector/src/main/res/drawable-ldrtl/msg_bubble_r1_text_outgoing.xml
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
diff --git a/vector/src/main/res/drawable-ldrtl/msg_bubble_r1_text_outgoing_notail.xml b/vector/src/main/res/drawable-ldrtl/msg_bubble_r1_text_outgoing_notail.xml
new file mode 100644
index 0000000000..87c322fd8c
--- /dev/null
+++ b/vector/src/main/res/drawable-ldrtl/msg_bubble_r1_text_outgoing_notail.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/vector/src/main/res/drawable-ldrtl/msg_bubble_r2_text_incoming.xml b/vector/src/main/res/drawable-ldrtl/msg_bubble_r2_text_incoming.xml
new file mode 100644
index 0000000000..ae6cb9e02c
--- /dev/null
+++ b/vector/src/main/res/drawable-ldrtl/msg_bubble_r2_text_incoming.xml
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
diff --git a/vector/src/main/res/drawable-ldrtl/msg_bubble_r2_text_incoming_notail.xml b/vector/src/main/res/drawable-ldrtl/msg_bubble_r2_text_incoming_notail.xml
new file mode 100644
index 0000000000..da92ad8b55
--- /dev/null
+++ b/vector/src/main/res/drawable-ldrtl/msg_bubble_r2_text_incoming_notail.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/vector/src/main/res/drawable-ldrtl/msg_bubble_r2_text_outgoing.xml b/vector/src/main/res/drawable-ldrtl/msg_bubble_r2_text_outgoing.xml
new file mode 100644
index 0000000000..e88276d9fc
--- /dev/null
+++ b/vector/src/main/res/drawable-ldrtl/msg_bubble_r2_text_outgoing.xml
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
diff --git a/vector/src/main/res/drawable-ldrtl/msg_bubble_r2_text_outgoing_notail.xml b/vector/src/main/res/drawable-ldrtl/msg_bubble_r2_text_outgoing_notail.xml
new file mode 100644
index 0000000000..e57bb803b9
--- /dev/null
+++ b/vector/src/main/res/drawable-ldrtl/msg_bubble_r2_text_outgoing_notail.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/vector/src/main/res/drawable/msg_bubble_r1_text_incoming.xml b/vector/src/main/res/drawable/msg_bubble_r1_text_incoming.xml
new file mode 100644
index 0000000000..6ff25128c0
--- /dev/null
+++ b/vector/src/main/res/drawable/msg_bubble_r1_text_incoming.xml
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
diff --git a/vector/src/main/res/drawable/msg_bubble_r1_text_incoming_notail.xml b/vector/src/main/res/drawable/msg_bubble_r1_text_incoming_notail.xml
new file mode 100644
index 0000000000..a063054e73
--- /dev/null
+++ b/vector/src/main/res/drawable/msg_bubble_r1_text_incoming_notail.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/vector/src/main/res/drawable/msg_bubble_r1_text_outgoing.xml b/vector/src/main/res/drawable/msg_bubble_r1_text_outgoing.xml
new file mode 100644
index 0000000000..15ad2fffed
--- /dev/null
+++ b/vector/src/main/res/drawable/msg_bubble_r1_text_outgoing.xml
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
diff --git a/vector/src/main/res/drawable/msg_bubble_r1_text_outgoing_notail.xml b/vector/src/main/res/drawable/msg_bubble_r1_text_outgoing_notail.xml
new file mode 100644
index 0000000000..6f1f14d25a
--- /dev/null
+++ b/vector/src/main/res/drawable/msg_bubble_r1_text_outgoing_notail.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/vector/src/main/res/drawable/msg_bubble_r2_text_incoming.xml b/vector/src/main/res/drawable/msg_bubble_r2_text_incoming.xml
new file mode 100644
index 0000000000..15e6d40367
--- /dev/null
+++ b/vector/src/main/res/drawable/msg_bubble_r2_text_incoming.xml
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
diff --git a/vector/src/main/res/drawable/msg_bubble_r2_text_incoming_notail.xml b/vector/src/main/res/drawable/msg_bubble_r2_text_incoming_notail.xml
new file mode 100644
index 0000000000..0cdae4e0b8
--- /dev/null
+++ b/vector/src/main/res/drawable/msg_bubble_r2_text_incoming_notail.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/vector/src/main/res/drawable/msg_bubble_r2_text_outgoing.xml b/vector/src/main/res/drawable/msg_bubble_r2_text_outgoing.xml
new file mode 100644
index 0000000000..b2ecd4fb56
--- /dev/null
+++ b/vector/src/main/res/drawable/msg_bubble_r2_text_outgoing.xml
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
diff --git a/vector/src/main/res/drawable/msg_bubble_r2_text_outgoing_notail.xml b/vector/src/main/res/drawable/msg_bubble_r2_text_outgoing_notail.xml
new file mode 100644
index 0000000000..4e5900ecac
--- /dev/null
+++ b/vector/src/main/res/drawable/msg_bubble_r2_text_outgoing_notail.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/vector/src/main/res/values/arrays_sc.xml b/vector/src/main/res/values/arrays_sc.xml
index 5b9a554b0a..8205a7f6f1 100644
--- a/vector/src/main/res/values/arrays_sc.xml
+++ b/vector/src/main/res/values/arrays_sc.xml
@@ -14,6 +14,17 @@
- none
+
+ - @string/bubble_rounded_corners_default
+ - @string/bubble_rounded_corners_r1
+ - @string/bubble_rounded_corners_r2
+
+
+ - default
+ - r1
+ - r2
+
+
- @string/bubble_time_location_top
- @string/bubble_time_location_bottom
diff --git a/vector/src/main/res/values/dimens_sc.xml b/vector/src/main/res/values/dimens_sc.xml
index 143575f9ae..1413f7fdf3 100644
--- a/vector/src/main/res/values/dimens_sc.xml
+++ b/vector/src/main/res/values/dimens_sc.xml
@@ -20,6 +20,12 @@
32dp
+
+ 8dp
+ 12dp
+ @dimen/sc_bubble_radius_in_tail
+ @dimen/sc_bubble_radius_in_tail
+
2dp
4dp
diff --git a/vector/src/main/res/values/strings_sc.xml b/vector/src/main/res/values/strings_sc.xml
index 6f0f4307b0..53609e7fbe 100644
--- a/vector/src/main/res/values/strings_sc.xml
+++ b/vector/src/main/res/values/strings_sc.xml
@@ -45,6 +45,14 @@
Top
Bottom
+ Bubble appearance
+ Corners
+ Default
+ Round
+ Extra round
+ Bubble tail
+ Include bubble tail for a sender\'s first message
+
Unread counter
Count muted messages
Show counts for muted messages in the chat overview
diff --git a/vector/src/main/res/xml/vector_settings_bubble_appearance.xml b/vector/src/main/res/xml/vector_settings_bubble_appearance.xml
new file mode 100644
index 0000000000..80f8d33bcc
--- /dev/null
+++ b/vector/src/main/res/xml/vector_settings_bubble_appearance.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
diff --git a/vector/src/main/res/xml/vector_settings_preferences.xml b/vector/src/main/res/xml/vector_settings_preferences.xml
index b0c932bd78..ae17e7d192 100644
--- a/vector/src/main/res/xml/vector_settings_preferences.xml
+++ b/vector/src/main/res/xml/vector_settings_preferences.xml
@@ -38,6 +38,10 @@
android:title="@string/settings_dark_theme"
app:iconSpaceReserved="false" />
+
+
+
+