Bubbles: continue R&D on UI

This commit is contained in:
ganfra 2022-01-05 11:00:12 +01:00
parent e540b26112
commit bde1df0322
10 changed files with 109 additions and 19 deletions

View File

@ -13,6 +13,9 @@
<color name="button_bot_background_color">#14368BD6</color> <color name="button_bot_background_color">#14368BD6</color>
<color name="button_bot_enabled_text_color">@color/palette_azure</color> <color name="button_bot_enabled_text_color">@color/palette_azure</color>
<color name="bubble_background_outgoing">#0F0DBD8B</color>
<color name="bubble_background_incoming">@color/element_system_light</color>
<!-- Notification (do not depends on theme) --> <!-- Notification (do not depends on theme) -->
<color name="notification_accent_color">@color/palette_azure</color> <color name="notification_accent_color">@color/palette_azure</color>
<color name="key_share_req_accent_color">@color/palette_melon</color> <color name="key_share_req_accent_color">@color/palette_melon</color>
@ -137,4 +140,5 @@
<attr name="vctr_presence_indicator_offline" format="color" /> <attr name="vctr_presence_indicator_offline" format="color" />
<color name="vctr_presence_indicator_offline_light">@color/palette_gray_100</color> <color name="vctr_presence_indicator_offline_light">@color/palette_gray_100</color>
<color name="vctr_presence_indicator_offline_dark">@color/palette_gray_450</color> <color name="vctr_presence_indicator_offline_dark">@color/palette_gray_450</color>
</resources> </resources>

View File

@ -47,4 +47,8 @@
<dimen name="composer_min_height">56dp</dimen> <dimen name="composer_min_height">56dp</dimen>
<dimen name="composer_attachment_size">52dp</dimen> <dimen name="composer_attachment_size">52dp</dimen>
<dimen name="composer_attachment_margin">1dp</dimen> <dimen name="composer_attachment_margin">1dp</dimen>
<dimen name="chat_bubble_margin_start">28dp</dimen>
<dimen name="chat_bubble_margin_end">62dp</dimen>
</resources> </resources>

View File

@ -3,6 +3,8 @@
<declare-styleable name="MessageBubble"> <declare-styleable name="MessageBubble">
<attr name="incoming_style" format="boolean" /> <attr name="incoming_style" format="boolean" />
<attr name="is_first" format="boolean" />
<attr name="is_last" format="boolean" />
</declare-styleable> </declare-styleable>
</resources> </resources>

View File

@ -145,6 +145,7 @@ class AvatarRenderer @Inject constructor(private val activeSessionHolder: Active
} }
else -> { else -> {
it.apply(RequestOptions.circleCropTransform()) it.apply(RequestOptions.circleCropTransform())
} }
} }
} }

View File

@ -24,7 +24,7 @@ class AvatarSizeProvider @Inject constructor(private val dimensionConverter: Dim
private val avatarStyle = AvatarStyle.X_SMALL private val avatarStyle = AvatarStyle.X_SMALL
val leftGuideline: Int by lazy { val leftGuideline: Int by lazy {
dimensionConverter.dpToPx(avatarStyle.avatarSizeDP) dimensionConverter.dpToPx(avatarStyle.avatarSizeDP + 4)
} }
val avatarSize: Int by lazy { val avatarSize: Int by lazy {
@ -37,7 +37,7 @@ class AvatarSizeProvider @Inject constructor(private val dimensionConverter: Dim
BIG(50), BIG(50),
MEDIUM(40), MEDIUM(40),
SMALL(30), SMALL(30),
X_SMALL(24), X_SMALL(28),
NONE(0) NONE(0)
} }
} }

View File

@ -17,42 +17,94 @@
package im.vector.app.features.home.room.detail.timeline.view package im.vector.app.features.home.room.detail.timeline.view
import android.content.Context import android.content.Context
import android.graphics.drawable.Drawable
import android.util.AttributeSet import android.util.AttributeSet
import android.view.View import android.view.View
import android.view.ViewGroup
import android.view.ViewOutlineProvider
import android.widget.RelativeLayout import android.widget.RelativeLayout
import androidx.core.content.ContextCompat
import androidx.core.content.withStyledAttributes import androidx.core.content.withStyledAttributes
import androidx.core.view.updateLayoutParams
import com.google.android.material.shape.CornerFamily
import com.google.android.material.shape.MaterialShapeDrawable
import com.google.android.material.shape.ShapeAppearanceModel
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.utils.DimensionConverter
class MessageBubbleView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, class MessageBubbleView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null,
defStyleAttr: Int = 0) : RelativeLayout(context, attrs, defStyleAttr) { defStyleAttr: Int = 0) : RelativeLayout(context, attrs, defStyleAttr) {
var incoming: Boolean = false var incoming: Boolean = false
var isFirst: Boolean = false
var isLast: Boolean = false
var cornerRadius = DimensionConverter(resources).dpToPx(12).toFloat()
init { init {
inflate(context, R.layout.view_message_bubble, this) inflate(context, R.layout.view_message_bubble, this)
context.withStyledAttributes(attrs, R.styleable.MessageBubble) { context.withStyledAttributes(attrs, R.styleable.MessageBubble) {
incoming = getBoolean(R.styleable.MessageBubble_incoming_style, false) incoming = getBoolean(R.styleable.MessageBubble_incoming_style, false)
isFirst = getBoolean(R.styleable.MessageBubble_is_first, false)
isLast = getBoolean(R.styleable.MessageBubble_is_last, false)
} }
} }
override fun onFinishInflate() { override fun onFinishInflate() {
super.onFinishInflate() super.onFinishInflate()
val currentLayoutDirection = layoutDirection val currentLayoutDirection = layoutDirection
findViewById<ViewGroup>(R.id.bubbleView).apply {
background = createBackgroundDrawable()
outlineProvider = ViewOutlineProvider.BACKGROUND
clipToOutline = true
}
if (incoming) { if (incoming) {
findViewById<View>(R.id.informationBottom).layoutDirection = currentLayoutDirection findViewById<View>(R.id.informationBottom).layoutDirection = currentLayoutDirection
findViewById<View>(R.id.bubbleWrapper).layoutDirection = currentLayoutDirection findViewById<View>(R.id.bubbleWrapper).layoutDirection = currentLayoutDirection
findViewById<View>(R.id.bubbleView).layoutDirection = currentLayoutDirection findViewById<View>(R.id.bubbleView).layoutDirection = currentLayoutDirection
findViewById<RelativeLayout>(R.id.bubbleView).setBackgroundResource(R.drawable.bg_timeline_incoming_message) findViewById<View>(R.id.messageEndGuideline).updateLayoutParams<LayoutParams> {
marginEnd = resources.getDimensionPixelSize(R.dimen.chat_bubble_margin_end)
}
} else { } else {
val oppositeLayoutDirection = if (currentLayoutDirection == View.LAYOUT_DIRECTION_LTR) { val oppositeLayoutDirection = if (currentLayoutDirection == View.LAYOUT_DIRECTION_LTR) {
View.LAYOUT_DIRECTION_RTL View.LAYOUT_DIRECTION_RTL
} else { } else {
View.LAYOUT_DIRECTION_LTR View.LAYOUT_DIRECTION_LTR
} }
findViewById<View>(R.id.informationBottom).layoutDirection = oppositeLayoutDirection findViewById<View>(R.id.informationBottom).layoutDirection = oppositeLayoutDirection
findViewById<View>(R.id.bubbleWrapper).layoutDirection = oppositeLayoutDirection findViewById<View>(R.id.bubbleWrapper).layoutDirection = oppositeLayoutDirection
findViewById<View>(R.id.bubbleView).layoutDirection = currentLayoutDirection findViewById<View>(R.id.bubbleView).layoutDirection = currentLayoutDirection
findViewById<RelativeLayout>(R.id.bubbleView).setBackgroundResource(R.drawable.bg_timeline_outgoing_message) findViewById<View>(R.id.messageEndGuideline).updateLayoutParams<LayoutParams> {
marginEnd = resources.getDimensionPixelSize(R.dimen.chat_bubble_margin_start)
}
} }
} }
private fun createBackgroundDrawable(): Drawable {
val topCornerFamily = if (isFirst) CornerFamily.ROUNDED else CornerFamily.CUT
val bottomCornerFamily = if (isLast) CornerFamily.ROUNDED else CornerFamily.CUT
val topRadius = if (isFirst) cornerRadius else 0f
val bottomRadius = if (isLast) cornerRadius else 0f
val shapeAppearanceModelBuilder = ShapeAppearanceModel().toBuilder()
val backgroundColor: Int
if (incoming) {
backgroundColor = R.color.bubble_background_incoming
shapeAppearanceModelBuilder
.setTopRightCorner(CornerFamily.ROUNDED, cornerRadius)
.setBottomRightCorner(CornerFamily.ROUNDED, cornerRadius)
.setTopLeftCorner(topCornerFamily, topRadius)
.setBottomLeftCorner(bottomCornerFamily, bottomRadius)
} else {
backgroundColor = R.color.bubble_background_outgoing
shapeAppearanceModelBuilder
.setTopLeftCorner(CornerFamily.ROUNDED, cornerRadius)
.setBottomLeftCorner(CornerFamily.ROUNDED, cornerRadius)
.setTopRightCorner(topCornerFamily, topRadius)
.setBottomRightCorner(bottomCornerFamily, bottomRadius)
}
val shapeAppearanceModel = shapeAppearanceModelBuilder.build()
val shapeDrawable = MaterialShapeDrawable(shapeAppearanceModel)
shapeDrawable.fillColor = ContextCompat.getColorStateList(context, backgroundColor)
return shapeDrawable
}
} }

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="@android:color/transparent"/>
<stroke
android:width="2dp"
android:color="?android:colorBackground"/>
</shape>

View File

@ -1,5 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"> <shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="?attr/vctr_system" /> <solid android:color="?attr/vctr_system" />
<corners android:radius="12dp"/>
</shape> </shape>

View File

@ -1,5 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"> <shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#0F0DBD8B" /> <solid android:color="#0F0DBD8B" />
<corners android:radius="12dp"/>
</shape> </shape>

View File

@ -19,9 +19,12 @@
android:id="@+id/messageAvatarImageView" android:id="@+id/messageAvatarImageView"
android:layout_width="44dp" android:layout_width="44dp"
android:layout_height="44dp" android:layout_height="44dp"
android:layout_marginStart="8dp" android:layout_marginStart="12dp"
android:layout_marginTop="4dp" android:layout_marginTop="4dp"
android:padding="2dp"
android:background="@drawable/bg_avatar_border"
android:contentDescription="@string/avatar" android:contentDescription="@string/avatar"
android:elevation="2dp"
tools:src="@sample/user_round_avatars" /> tools:src="@sample/user_round_avatars" />
<TextView <TextView
@ -31,7 +34,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentTop="true" android:layout_alignParentTop="true"
android:layout_alignParentEnd="true" android:layout_alignParentEnd="true"
android:layout_marginStart="8dp" android:layout_marginStart="12dp"
android:layout_marginEnd="4dp" android:layout_marginEnd="4dp"
android:layout_toEndOf="@id/messageStartGuideline" android:layout_toEndOf="@id/messageStartGuideline"
android:ellipsize="end" android:ellipsize="end"
@ -46,6 +49,13 @@
android:layout_height="0dp" android:layout_height="0dp"
tools:layout_marginStart="52dp" /> tools:layout_marginStart="52dp" />
<View
android:id="@+id/messageEndGuideline"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_alignParentEnd="true"
android:layout_marginEnd="64dp" />
<Space <Space
android:id="@+id/decorationSpace" android:id="@+id/decorationSpace"
android:layout_width="4dp" android:layout_width="4dp"
@ -59,6 +69,7 @@
android:layout_alignTop="@id/bubbleWrapper" android:layout_alignTop="@id/bubbleWrapper"
android:layout_alignEnd="@id/decorationSpace" android:layout_alignEnd="@id/decorationSpace"
android:layout_marginTop="7dp" android:layout_marginTop="7dp"
android:elevation="2dp"
android:visibility="gone" android:visibility="gone"
tools:visibility="visible" /> tools:visibility="visible" />
@ -67,45 +78,49 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_below="@id/messageMemberNameView" android:layout_below="@id/messageMemberNameView"
android:layout_toStartOf="@id/messageSendStateImageView" android:layout_toStartOf="@id/messageEndGuideline"
android:layout_toEndOf="@id/messageStartGuideline" android:layout_toEndOf="@id/messageStartGuideline"
android:addStatesFromChildren="true" android:addStatesFromChildren="true"
android:clipChildren="false" android:clipChildren="false"
android:clipToPadding="false"> android:clipToPadding="false">
<RelativeLayout <androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/bubbleView" android:id="@+id/bubbleView"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginVertical="4dp"
android:layout_marginStart="0dp" android:layout_marginStart="0dp"
android:layout_marginEnd="0dp" android:layout_marginEnd="0dp"
android:addStatesFromChildren="true" android:addStatesFromChildren="true"
android:background="@drawable/bg_timeline_incoming_message" android:background="@drawable/bg_timeline_incoming_message"
android:clipChildren="false" android:paddingStart="4dp"
android:clipToPadding="false" app:layout_constraintStart_toStartOf="parent"
tools:ignore="UselessParent"> app:layout_constraintTop_toTopOf="parent">
<include <include
android:id="@+id/viewStubContainer" android:id="@+id/viewStubContainer"
layout="@layout/item_timeline_event_view_stubs_container" layout="@layout/item_timeline_event_view_stubs_container"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:addStatesFromChildren="true" /> android:addStatesFromChildren="true"
app:layout_constrainedWidth="true"
app:layout_constraintEnd_toStartOf="@id/messageTimeView"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintWidth_max="300dp" />
<TextView <TextView
android:id="@+id/messageTimeView" android:id="@+id/messageTimeView"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignBottom="@id/viewStubContainer"
android:layout_marginEnd="8dp" android:layout_marginEnd="8dp"
android:layout_marginBottom="4dp" android:layout_marginBottom="4dp"
android:layout_toEndOf="@id/viewStubContainer"
android:textColor="?vctr_content_tertiary" android:textColor="?vctr_content_tertiary"
android:textSize="10sp" android:textSize="10sp"
app:layout_constraintBottom_toBottomOf="@id/viewStubContainer"
app:layout_constraintEnd_toEndOf="parent"
tools:text="@tools:sample/date/hhmm" /> tools:text="@tools:sample/date/hhmm" />
</RelativeLayout> </androidx.constraintlayout.widget.ConstraintLayout>
</FrameLayout> </FrameLayout>
<im.vector.app.core.ui.views.SendStateImageView <im.vector.app.core.ui.views.SendStateImageView
@ -143,6 +158,9 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_below="@id/bubbleWrapper" android:layout_below="@id/bubbleWrapper"
android:layout_marginTop="2dp"
android:layout_marginBottom="2dp"
android:layout_toStartOf="@id/messageEndGuideline"
android:layout_toEndOf="@id/messageStartGuideline" android:layout_toEndOf="@id/messageStartGuideline"
android:addStatesFromChildren="true" android:addStatesFromChildren="true"
android:orientation="vertical"> android:orientation="vertical">
@ -151,7 +169,6 @@
android:id="@+id/reactionsContainer" android:id="@+id/reactionsContainer"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginBottom="4dp" android:layout_marginBottom="4dp"
app:dividerDrawable="@drawable/reaction_divider" app:dividerDrawable="@drawable/reaction_divider"
app:flexWrap="wrap" app:flexWrap="wrap"