Merge pull request #1512 from simophin/ses-2021-improve-xml-loading

[SES-2021] Optimise XML loading
This commit is contained in:
ThomasSession 2024-06-21 12:53:22 +10:00 committed by GitHub
commit ba9f729d63
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 105 additions and 84 deletions

View File

@ -30,13 +30,15 @@ public abstract class BaseActionBarActivity extends AppCompatActivity {
private static final String TAG = BaseActionBarActivity.class.getSimpleName(); private static final String TAG = BaseActionBarActivity.class.getSimpleName();
public ThemeState currentThemeState; public ThemeState currentThemeState;
private Resources.Theme modifiedTheme;
private TextSecurePreferences getPreferences() { private TextSecurePreferences getPreferences() {
ApplicationContext appContext = (ApplicationContext) getApplicationContext(); ApplicationContext appContext = (ApplicationContext) getApplicationContext();
return appContext.textSecurePreferences; return appContext.textSecurePreferences;
} }
@StyleRes @StyleRes
public int getDesiredTheme() { private int getDesiredTheme() {
ThemeState themeState = ActivityUtilitiesKt.themeState(getPreferences()); ThemeState themeState = ActivityUtilitiesKt.themeState(getPreferences());
int userSelectedTheme = themeState.getTheme(); int userSelectedTheme = themeState.getTheme();
@ -58,7 +60,7 @@ public abstract class BaseActionBarActivity extends AppCompatActivity {
} }
@StyleRes @Nullable @StyleRes @Nullable
public Integer getAccentTheme() { private Integer getAccentTheme() {
if (!getPreferences().hasPreference(SELECTED_ACCENT_COLOR)) return null; if (!getPreferences().hasPreference(SELECTED_ACCENT_COLOR)) return null;
ThemeState themeState = ActivityUtilitiesKt.themeState(getPreferences()); ThemeState themeState = ActivityUtilitiesKt.themeState(getPreferences());
return themeState.getAccentStyle(); return themeState.getAccentStyle();
@ -66,8 +68,12 @@ public abstract class BaseActionBarActivity extends AppCompatActivity {
@Override @Override
public Resources.Theme getTheme() { public Resources.Theme getTheme() {
if (modifiedTheme != null) {
return modifiedTheme;
}
// New themes // New themes
Resources.Theme modifiedTheme = super.getTheme(); modifiedTheme = super.getTheme();
modifiedTheme.applyStyle(getDesiredTheme(), true); modifiedTheme.applyStyle(getDesiredTheme(), true);
Integer accentTheme = getAccentTheme(); Integer accentTheme = getAccentTheme();
if (accentTheme != null) { if (accentTheme != null) {

View File

@ -61,7 +61,6 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import network.loki.messenger.R import network.loki.messenger.R
import network.loki.messenger.databinding.ActivityConversationV2Binding import network.loki.messenger.databinding.ActivityConversationV2Binding
import network.loki.messenger.databinding.ViewVisibleMessageBinding
import network.loki.messenger.libsession_util.util.ExpiryMode import network.loki.messenger.libsession_util.util.ExpiryMode
import nl.komponents.kovenant.ui.successUi import nl.komponents.kovenant.ui.successUi
import org.session.libsession.messaging.MessagingModuleConfiguration import org.session.libsession.messaging.MessagingModuleConfiguration
@ -1558,8 +1557,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
if (indexInAdapter < 0 || indexInAdapter >= adapter.itemCount) { return } if (indexInAdapter < 0 || indexInAdapter >= adapter.itemCount) { return }
val viewHolder = binding?.conversationRecyclerView?.findViewHolderForAdapterPosition(indexInAdapter) as? ConversationAdapter.VisibleMessageViewHolder ?: return val viewHolder = binding?.conversationRecyclerView?.findViewHolderForAdapterPosition(indexInAdapter) as? ConversationAdapter.VisibleMessageViewHolder ?: return
val visibleMessageView = ViewVisibleMessageBinding.bind(viewHolder.view).visibleMessageView viewHolder.view.playVoiceMessage()
visibleMessageView.playVoiceMessage()
} }
override fun sendMessage() { override fun sendMessage() {

View File

@ -5,9 +5,7 @@ import android.content.Intent
import android.database.Cursor import android.database.Cursor
import android.util.SparseArray import android.util.SparseArray
import android.util.SparseBooleanArray import android.util.SparseBooleanArray
import android.view.LayoutInflater
import android.view.MotionEvent import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.annotation.WorkerThread import androidx.annotation.WorkerThread
import androidx.core.util.getOrDefault import androidx.core.util.getOrDefault
@ -20,14 +18,11 @@ import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.isActive import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import network.loki.messenger.R import network.loki.messenger.R
import network.loki.messenger.databinding.ViewVisibleMessageBinding
import org.session.libsession.messaging.contacts.Contact import org.session.libsession.messaging.contacts.Contact
import org.session.libsession.utilities.TextSecurePreferences
import org.thoughtcrime.securesms.conversation.v2.messages.ControlMessageView import org.thoughtcrime.securesms.conversation.v2.messages.ControlMessageView
import org.thoughtcrime.securesms.conversation.v2.messages.VisibleMessageView import org.thoughtcrime.securesms.conversation.v2.messages.VisibleMessageView
import org.thoughtcrime.securesms.conversation.v2.messages.VisibleMessageViewDelegate import org.thoughtcrime.securesms.conversation.v2.messages.VisibleMessageViewDelegate
import org.thoughtcrime.securesms.database.CursorRecyclerViewAdapter import org.thoughtcrime.securesms.database.CursorRecyclerViewAdapter
import org.thoughtcrime.securesms.database.MmsSmsColumns
import org.thoughtcrime.securesms.database.model.MessageRecord import org.thoughtcrime.securesms.database.model.MessageRecord
import org.thoughtcrime.securesms.dependencies.DatabaseComponent import org.thoughtcrime.securesms.dependencies.DatabaseComponent
import org.thoughtcrime.securesms.mms.GlideRequests import org.thoughtcrime.securesms.mms.GlideRequests
@ -90,7 +85,7 @@ class ConversationAdapter(
} }
} }
class VisibleMessageViewHolder(val view: View) : ViewHolder(view) class VisibleMessageViewHolder(val view: VisibleMessageView) : ViewHolder(view)
class ControlMessageViewHolder(val view: ControlMessageView) : ViewHolder(view) class ControlMessageViewHolder(val view: ControlMessageView) : ViewHolder(view)
override fun getItemViewType(cursor: Cursor): Int { override fun getItemViewType(cursor: Cursor): Int {
@ -103,7 +98,7 @@ class ConversationAdapter(
@Suppress("NAME_SHADOWING") @Suppress("NAME_SHADOWING")
val viewType = ViewType.allValues[viewType] val viewType = ViewType.allValues[viewType]
return when (viewType) { return when (viewType) {
ViewType.Visible -> VisibleMessageViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.view_visible_message, parent, false)) ViewType.Visible -> VisibleMessageViewHolder(VisibleMessageView(context))
ViewType.Control -> ControlMessageViewHolder(ControlMessageView(context)) ViewType.Control -> ControlMessageViewHolder(ControlMessageView(context))
else -> throw IllegalStateException("Unexpected view type: $viewType.") else -> throw IllegalStateException("Unexpected view type: $viewType.")
} }
@ -115,7 +110,7 @@ class ConversationAdapter(
val messageBefore = getMessageBefore(position, cursor) val messageBefore = getMessageBefore(position, cursor)
when (viewHolder) { when (viewHolder) {
is VisibleMessageViewHolder -> { is VisibleMessageViewHolder -> {
val visibleMessageView = ViewVisibleMessageBinding.bind(viewHolder.view).visibleMessageView val visibleMessageView = viewHolder.view
val isSelected = selectedItems.contains(message) val isSelected = selectedItems.contains(message)
visibleMessageView.snIsSelected = isSelected visibleMessageView.snIsSelected = isSelected
visibleMessageView.indexInAdapter = position visibleMessageView.indexInAdapter = position
@ -181,7 +176,7 @@ class ConversationAdapter(
override fun onItemViewRecycled(viewHolder: ViewHolder?) { override fun onItemViewRecycled(viewHolder: ViewHolder?) {
when (viewHolder) { when (viewHolder) {
is VisibleMessageViewHolder -> viewHolder.view.findViewById<VisibleMessageView>(R.id.visibleMessageView).recycle() is VisibleMessageViewHolder -> viewHolder.view.recycle()
is ControlMessageViewHolder -> viewHolder.view.recycle() is ControlMessageViewHolder -> viewHolder.view.recycle()
} }
super.onItemViewRecycled(viewHolder) super.onItemViewRecycled(viewHolder)

View File

@ -25,16 +25,15 @@ class ControlMessageView : LinearLayout {
private val TAG = "ControlMessageView" private val TAG = "ControlMessageView"
private lateinit var binding: ViewControlMessageBinding private val binding = ViewControlMessageBinding.inflate(LayoutInflater.from(context), this, true)
constructor(context: Context) : super(context) { initialize() } constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { initialize() } constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { initialize() } constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
@Inject lateinit var disappearingMessages: DisappearingMessages @Inject lateinit var disappearingMessages: DisappearingMessages
private fun initialize() { init {
binding = ViewControlMessageBinding.inflate(LayoutInflater.from(context), this, true)
layoutParams = RecyclerView.LayoutParams(RecyclerView.LayoutParams.MATCH_PARENT, RecyclerView.LayoutParams.WRAP_CONTENT) layoutParams = RecyclerView.LayoutParams(RecyclerView.LayoutParams.MATCH_PARENT, RecyclerView.LayoutParams.WRAP_CONTENT)
} }

View File

@ -11,8 +11,10 @@ import android.os.Looper
import android.util.AttributeSet import android.util.AttributeSet
import android.view.Gravity import android.view.Gravity
import android.view.HapticFeedbackConstants import android.view.HapticFeedbackConstants
import android.view.LayoutInflater
import android.view.MotionEvent import android.view.MotionEvent
import android.view.View import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout import android.widget.FrameLayout
import android.widget.LinearLayout import android.widget.LinearLayout
import androidx.annotation.ColorInt import androidx.annotation.ColorInt
@ -26,17 +28,17 @@ import androidx.core.view.isVisible
import androidx.core.view.marginBottom import androidx.core.view.marginBottom
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import network.loki.messenger.R import network.loki.messenger.R
import network.loki.messenger.databinding.ViewEmojiReactionsBinding
import network.loki.messenger.databinding.ViewVisibleMessageBinding import network.loki.messenger.databinding.ViewVisibleMessageBinding
import network.loki.messenger.databinding.ViewstubVisibleMessageMarkerContainerBinding
import org.session.libsession.messaging.contacts.Contact import org.session.libsession.messaging.contacts.Contact
import org.session.libsession.messaging.contacts.Contact.ContactContext import org.session.libsession.messaging.contacts.Contact.ContactContext
import org.session.libsession.messaging.open_groups.OpenGroupApi import org.session.libsession.messaging.open_groups.OpenGroupApi
import org.session.libsession.utilities.Address import org.session.libsession.utilities.Address
import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsession.utilities.ViewUtil import org.session.libsession.utilities.ViewUtil
import org.session.libsession.utilities.getColorFromAttr import org.session.libsession.utilities.getColorFromAttr
import org.session.libsession.utilities.modifyLayoutParams import org.session.libsession.utilities.modifyLayoutParams
import org.session.libsignal.utilities.IdPrefix import org.session.libsignal.utilities.IdPrefix
import org.session.libsignal.utilities.Log
import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2 import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2
import org.thoughtcrime.securesms.database.LastSentTimestampCache import org.thoughtcrime.securesms.database.LastSentTimestampCache
import org.thoughtcrime.securesms.database.LokiAPIDatabase import org.thoughtcrime.securesms.database.LokiAPIDatabase
@ -65,7 +67,7 @@ import kotlin.math.sqrt
private const val TAG = "VisibleMessageView" private const val TAG = "VisibleMessageView"
@AndroidEntryPoint @AndroidEntryPoint
class VisibleMessageView : LinearLayout { class VisibleMessageView : FrameLayout {
private var replyDisabled: Boolean = false private var replyDisabled: Boolean = false
@Inject lateinit var threadDb: ThreadDatabase @Inject lateinit var threadDb: ThreadDatabase
@Inject lateinit var lokiThreadDb: LokiThreadDatabase @Inject lateinit var lokiThreadDb: LokiThreadDatabase
@ -75,7 +77,16 @@ class VisibleMessageView : LinearLayout {
@Inject lateinit var mmsDb: MmsDatabase @Inject lateinit var mmsDb: MmsDatabase
@Inject lateinit var lastSentTimestampCache: LastSentTimestampCache @Inject lateinit var lastSentTimestampCache: LastSentTimestampCache
private val binding by lazy { ViewVisibleMessageBinding.bind(this) } private val binding = ViewVisibleMessageBinding.inflate(LayoutInflater.from(context), this, true)
private val markerContainerBinding = lazy(LazyThreadSafetyMode.NONE) {
ViewstubVisibleMessageMarkerContainerBinding.bind(binding.unreadMarkerContainerStub.inflate())
}
private val emojiReactionsBinding = lazy(LazyThreadSafetyMode.NONE) {
ViewEmojiReactionsBinding.bind(binding.emojiReactionsView.inflate())
}
private val swipeToReplyIcon = ContextCompat.getDrawable(context, R.drawable.ic_baseline_reply_24)!!.mutate() private val swipeToReplyIcon = ContextCompat.getDrawable(context, R.drawable.ic_baseline_reply_24)!!.mutate()
private val swipeToReplyIconRect = Rect() private val swipeToReplyIconRect = Rect()
private var dx = 0.0f private var dx = 0.0f
@ -94,7 +105,7 @@ class VisibleMessageView : LinearLayout {
var onPress: ((event: MotionEvent) -> Unit)? = null var onPress: ((event: MotionEvent) -> Unit)? = null
var onSwipeToReply: (() -> Unit)? = null var onSwipeToReply: (() -> Unit)? = null
var onLongPress: (() -> Unit)? = null var onLongPress: (() -> Unit)? = null
val messageContentView: VisibleMessageContentView by lazy { binding.messageContentView.root } val messageContentView: VisibleMessageContentView get() = binding.messageContentView.root
companion object { companion object {
const val swipeToReplyThreshold = 64.0f // dp const val swipeToReplyThreshold = 64.0f // dp
@ -108,12 +119,7 @@ class VisibleMessageView : LinearLayout {
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
override fun onFinishInflate() { init {
super.onFinishInflate()
initialize()
}
private fun initialize() {
isHapticFeedbackEnabled = true isHapticFeedbackEnabled = true
setWillNotDraw(false) setWillNotDraw(false)
binding.root.disableClipping() binding.root.disableClipping()
@ -121,7 +127,11 @@ class VisibleMessageView : LinearLayout {
binding.messageInnerContainer.disableClipping() binding.messageInnerContainer.disableClipping()
binding.messageInnerLayout.disableClipping() binding.messageInnerLayout.disableClipping()
binding.messageContentView.root.disableClipping() binding.messageContentView.root.disableClipping()
// Default layout params
layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
} }
// endregion // endregion
// region Updating // region Updating
@ -203,7 +213,13 @@ class VisibleMessageView : LinearLayout {
binding.senderNameTextView.text = contact?.displayName(contactContext) ?: senderSessionID binding.senderNameTextView.text = contact?.displayName(contactContext) ?: senderSessionID
// Unread marker // Unread marker
binding.unreadMarkerContainer.isVisible = lastSeen != -1L && message.timestamp > lastSeen && (previous == null || previous.timestamp <= lastSeen) && !message.isOutgoing val shouldShowUnreadMarker = lastSeen != -1L && message.timestamp > lastSeen && (previous == null || previous.timestamp <= lastSeen) && !message.isOutgoing
if (shouldShowUnreadMarker) {
markerContainerBinding.value.root.isVisible = true
} else if (markerContainerBinding.isInitialized()) {
// Only need to hide the binding when the binding is inflated. (default is gone)
markerContainerBinding.value.root.isVisible = false
}
// Date break // Date break
val showDateBreak = isStartOfMessageCluster || snIsSelected val showDateBreak = isStartOfMessageCluster || snIsSelected
@ -214,21 +230,22 @@ class VisibleMessageView : LinearLayout {
showStatusMessage(message) showStatusMessage(message)
// Emoji Reactions // Emoji Reactions
val emojiLayoutParams = binding.emojiReactionsView.root.layoutParams as ConstraintLayout.LayoutParams
emojiLayoutParams.horizontalBias = if (message.isOutgoing) 1f else 0f
binding.emojiReactionsView.root.layoutParams = emojiLayoutParams
if (message.reactions.isNotEmpty()) { if (message.reactions.isNotEmpty()) {
val capabilities = lokiThreadDb.getOpenGroupChat(threadID)?.server?.let { lokiApiDb.getServerCapabilities(it) } val capabilities = lokiThreadDb.getOpenGroupChat(threadID)?.server?.let { lokiApiDb.getServerCapabilities(it) }
if (capabilities.isNullOrEmpty() || capabilities.contains(OpenGroupApi.Capability.REACTIONS.name.lowercase())) { if (capabilities.isNullOrEmpty() || capabilities.contains(OpenGroupApi.Capability.REACTIONS.name.lowercase())) {
binding.emojiReactionsView.root.setReactions(message.id, message.reactions, message.isOutgoing, delegate) emojiReactionsBinding.value.root.let { root ->
binding.emojiReactionsView.root.isVisible = true root.setReactions(message.id, message.reactions, message.isOutgoing, delegate)
} else { root.isVisible = true
binding.emojiReactionsView.root.isVisible = false (root.layoutParams as ConstraintLayout.LayoutParams).apply {
horizontalBias = if (message.isOutgoing) 1f else 0f
}
}
} else if (emojiReactionsBinding.isInitialized()) {
emojiReactionsBinding.value.root.isVisible = false
} }
} }
else { else if (emojiReactionsBinding.isInitialized()) {
binding.emojiReactionsView.root.isVisible = false emojiReactionsBinding.value.root.isVisible = false
} }
// Populate content view // Populate content view

View File

@ -19,11 +19,11 @@ abstract class AppModule {
@Binds @Binds
abstract fun bindConversationRepository(repository: DefaultConversationRepository): ConversationRepository abstract fun bindConversationRepository(repository: DefaultConversationRepository): ConversationRepository
} }
@EntryPoint @EntryPoint
@InstallIn(SingletonComponent::class) @InstallIn(SingletonComponent::class)
interface AppComponent { interface AppComponent {
fun getPrefs(): TextSecurePreferences fun getPrefs(): TextSecurePreferences
} }

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<org.thoughtcrime.securesms.conversation.v2.messages.VisibleMessageView <LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
@ -8,45 +8,11 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical"> android:orientation="vertical">
<androidx.constraintlayout.widget.ConstraintLayout <ViewStub
android:id="@+id/unreadMarkerContainer"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/small_spacing" android:id="@+id/unreadMarkerContainerStub"
android:visibility="gone" android:layout="@layout/viewstub_visible_message_marker_container" />
tools:visibility="visible">
<View
android:layout_width="0dp"
android:layout_height="1dp"
android:layout_marginStart="@dimen/medium_spacing"
android:layout_marginEnd="@dimen/small_spacing"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@id/unreadMarker"
android:background="?android:colorAccent" />
<TextView
android:id="@+id/unreadMarker"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:text="@string/unread_marker"
android:gravity="center"
android:textColor="?android:colorAccent"
android:textSize="@dimen/small_font_size"
android:textStyle="bold" />
<View
android:layout_width="0dp"
android:layout_height="1dp"
android:layout_marginStart="@dimen/small_spacing"
android:layout_marginEnd="@dimen/medium_spacing"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@id/unreadMarker"
app:layout_constraintEnd_toEndOf="parent"
android:background="?android:colorAccent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<TextView <TextView
android:id="@+id/dateBreakTextView" android:id="@+id/dateBreakTextView"
@ -129,8 +95,10 @@
</LinearLayout> </LinearLayout>
</FrameLayout> </FrameLayout>
<include layout="@layout/view_emoji_reactions" <ViewStub
android:layout="@layout/view_emoji_reactions"
android:id="@+id/emojiReactionsView" android:id="@+id/emojiReactionsView"
android:inflatedId="@+id/emojiReactionsView"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:visibility="gone" android:visibility="gone"
@ -176,4 +144,4 @@
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
</org.thoughtcrime.securesms.conversation.v2.messages.VisibleMessageView> </LinearLayout>

View File

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/unreadMarkerContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/small_spacing"
android:gravity="center"
android:orientation="horizontal"
android:visibility="gone"
tools:visibility="visible">
<View
android:layout_width="0dp"
android:layout_height="1dp"
android:layout_marginStart="@dimen/medium_spacing"
android:layout_marginEnd="@dimen/small_spacing"
android:layout_weight="1"
android:background="?android:colorAccent" />
<TextView
android:id="@+id/unreadMarker"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:text="@string/unread_marker"
android:textColor="?android:colorAccent"
android:textSize="@dimen/small_font_size"
android:textStyle="bold" />
<View
android:layout_width="0dp"
android:layout_height="1dp"
android:layout_marginStart="@dimen/small_spacing"
android:layout_marginEnd="@dimen/medium_spacing"
android:layout_weight="1"
android:background="?android:colorAccent" />
</LinearLayout>