Skip to content
Snippets Groups Projects
Commit 9739c4bd authored by Janne Mareike Koschinski's avatar Janne Mareike Koschinski
Browse files

Implement significantly faster DayChange headers

parent 6d4706de
Branches
Tags
No related merge requests found
Showing
with 169 additions and 121 deletions
...@@ -112,11 +112,15 @@ class AutoCompleteAdapter( ...@@ -112,11 +112,15 @@ class AutoCompleteAdapter(
nick.text = SpanFormatter.format("%s%s", data.modes, data.displayNick ?: data.nick) nick.text = SpanFormatter.format("%s%s", data.modes, data.displayNick ?: data.nick)
realname.text = data.realname realname.text = data.realname
if (data.avatarUrl != null) {
GlideApp.with(itemView) GlideApp.with(itemView)
.load(data.avatarUrl) .load(data.avatarUrl)
.apply(RequestOptions.circleCropTransform()) .apply(RequestOptions.circleCropTransform())
.placeholder(data.fallbackDrawable) .placeholder(data.fallbackDrawable)
.into(avatar) .into(avatar)
} else {
avatar.setImageDrawable(data.fallbackDrawable)
}
} }
} }
......
package de.kuschku.quasseldroid.ui.chat.messages
import android.graphics.Canvas
import android.graphics.Rect
import android.support.v7.widget.RecyclerView
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import de.kuschku.quasseldroid.R
import org.threeten.bp.ZoneId
import org.threeten.bp.format.DateTimeFormatter
import org.threeten.bp.format.FormatStyle
import org.threeten.bp.temporal.ChronoUnit
class DayChangeItemDecoration(private val adapter: MessageAdapter) :
RecyclerView.ItemDecoration() {
private val dayChangeFormatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM)
private val mBounds = Rect()
override fun onDraw(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
c.save()
val left: Int
val right: Int
if (parent.clipToPadding) {
left = parent.paddingLeft
right = parent.width - parent.paddingRight
c.clipRect(left, parent.paddingTop, right, parent.height - parent.paddingBottom)
} else {
left = 0
right = parent.width
}
val childCount = parent.childCount
for (i in 0 until childCount) {
val child = parent.getChildAt(i)
if (child.getTag(R.id.tag_daychange) == true) {
parent.getDecoratedBoundsWithMargins(child, mBounds)
val bottom = mBounds.bottom + Math.round(child.translationY)
val top = mBounds.top + Math.round(child.translationY)
val layout = child.getTag(R.id.tag_daychange_layout) as View
c.save()
c.clipRect(left.toFloat(), top.toFloat(), right.toFloat(), bottom.toFloat())
c.translate(left.toFloat(), top.toFloat())
layout.draw(c)
c.restore()
}
}
c.restore()
}
private fun fixLayoutSize(view: View, parent: ViewGroup) {
val widthSpec = View.MeasureSpec.makeMeasureSpec(
parent.width,
View.MeasureSpec.EXACTLY
)
val heightSpec = View.MeasureSpec.makeMeasureSpec(
parent.height,
View.MeasureSpec.UNSPECIFIED
)
val childWidthSpec = ViewGroup.getChildMeasureSpec(
widthSpec,
parent.paddingLeft + parent.paddingRight,
view.layoutParams.width
)
val childHeightSpec = ViewGroup.getChildMeasureSpec(
heightSpec,
parent.paddingTop + parent.paddingBottom,
view.layoutParams.height
)
view.measure(childWidthSpec, childHeightSpec)
view.layout(0, 0, view.measuredWidth, view.measuredHeight)
}
override fun getItemOffsets(outRect: Rect, v: View, parent: RecyclerView,
state: RecyclerView.State) {
adapter[parent.getChildAdapterPosition(v)]?.let {
if (it.hasDayChange) {
if (v.getTag(R.id.tag_daychange_layout) == null) {
val layout = LayoutInflater.from(parent.context).inflate(
R.layout.widget_chatmessage_daychange, parent, false
)
val content = layout.findViewById<TextView>(R.id.combined)
content?.text = dayChangeFormatter.format(
it.content.time.atZone(ZoneId.systemDefault()).truncatedTo(ChronoUnit.DAYS)
)
fixLayoutSize(layout, parent)
v.setTag(R.id.tag_daychange_layout, layout)
v.setTag(R.id.tag_daychange_content, content)
}
v.setTag(R.id.tag_daychange, true)
val layout = v.getTag(R.id.tag_daychange_layout) as View
outRect.set(0, layout.measuredHeight, 0, 10)
} else {
v.setTag(R.id.tag_daychange, false)
}
}
}
}
...@@ -6,19 +6,22 @@ import de.kuschku.quasseldroid.persistence.QuasselDatabase ...@@ -6,19 +6,22 @@ import de.kuschku.quasseldroid.persistence.QuasselDatabase
data class DisplayMessage( data class DisplayMessage(
val content: QuasselDatabase.DatabaseMessage, val content: QuasselDatabase.DatabaseMessage,
val hasDayChange: Boolean,
val isFollowUp: Boolean,
val isSelected: Boolean, val isSelected: Boolean,
val isExpanded: Boolean, val isExpanded: Boolean,
val isMarkerLine: Boolean val isMarkerLine: Boolean
) { ) {
data class Tag( data class Tag(
val id: MsgId, val id: MsgId,
val hasDayChange: Boolean,
val isFollowUp: Boolean, val isFollowUp: Boolean,
val isSelected: Boolean, val isSelected: Boolean,
val isExpanded: Boolean, val isExpanded: Boolean,
val isMarkerLine: Boolean val isMarkerLine: Boolean
) )
val tag = Tag(content.messageId, content.followUp, isSelected, isExpanded, isMarkerLine) val tag = Tag(content.messageId, hasDayChange, isFollowUp, isSelected, isExpanded, isMarkerLine)
val avatarUrl = content.sender.let { val avatarUrl = content.sender.let {
Regex("[us]id(\\d+)").matchEntire(HostmaskHelper.user(it))?.groupValues?.lastOrNull()?.let { Regex("[us]id(\\d+)").matchEntire(HostmaskHelper.user(it))?.groupValues?.lastOrNull()?.let {
"https://www.irccloud.com/avatar-redirect/$it" "https://www.irccloud.com/avatar-redirect/$it"
......
...@@ -61,7 +61,7 @@ class MessageAdapter( ...@@ -61,7 +61,7 @@ class MessageAdapter(
override fun getItemViewType(position: Int) = getItem(position)?.let { override fun getItemViewType(position: Int) = getItem(position)?.let {
viewType(Message_Flags.of(it.content.type), viewType(Message_Flags.of(it.content.type),
Message_Flags.of(it.content.flag), Message_Flags.of(it.content.flag),
it.content.followUp) it.isFollowUp)
} ?: 0 } ?: 0
private fun viewType(type: Message_Types, flags: Message_Flags, followUp: Boolean) = private fun viewType(type: Message_Types, flags: Message_Flags, followUp: Boolean) =
...@@ -200,11 +200,15 @@ class MessageAdapter( ...@@ -200,11 +200,15 @@ class MessageAdapter(
this.itemView.isSelected = message.isSelected this.itemView.isSelected = message.isSelected
avatar?.let { avatarView -> avatar?.let { avatarView ->
if (message.avatarUrl != null) {
GlideApp.with(itemView) GlideApp.with(itemView)
.load(message.avatarUrl) .load(message.avatarUrl)
.apply(RequestOptions.circleCropTransform()) .apply(RequestOptions.circleCropTransform())
.placeholder(message.fallbackDrawable) .placeholder(message.fallbackDrawable)
.into(avatarView) .into(avatarView)
} else {
avatarView.setImageDrawable(message.fallbackDrawable)
}
} }
} }
} }
......
...@@ -26,7 +26,6 @@ import de.kuschku.libquassel.util.helpers.value ...@@ -26,7 +26,6 @@ import de.kuschku.libquassel.util.helpers.value
import de.kuschku.quasseldroid.GlideApp import de.kuschku.quasseldroid.GlideApp
import de.kuschku.quasseldroid.R import de.kuschku.quasseldroid.R
import de.kuschku.quasseldroid.persistence.QuasselDatabase import de.kuschku.quasseldroid.persistence.QuasselDatabase
import de.kuschku.quasseldroid.persistence.findByBufferIdPagedWithDayChange
import de.kuschku.quasseldroid.settings.AppearanceSettings import de.kuschku.quasseldroid.settings.AppearanceSettings
import de.kuschku.quasseldroid.settings.BacklogSettings import de.kuschku.quasseldroid.settings.BacklogSettings
import de.kuschku.quasseldroid.settings.MessageSettings import de.kuschku.quasseldroid.settings.MessageSettings
...@@ -34,6 +33,9 @@ import de.kuschku.quasseldroid.util.helper.* ...@@ -34,6 +33,9 @@ import de.kuschku.quasseldroid.util.helper.*
import de.kuschku.quasseldroid.util.service.ServiceBoundFragment import de.kuschku.quasseldroid.util.service.ServiceBoundFragment
import de.kuschku.quasseldroid.util.ui.SpanFormatter import de.kuschku.quasseldroid.util.ui.SpanFormatter
import io.reactivex.BackpressureStrategy import io.reactivex.BackpressureStrategy
import org.threeten.bp.ZoneId
import org.threeten.bp.ZonedDateTime
import org.threeten.bp.temporal.ChronoUnit
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import javax.inject.Inject import javax.inject.Inject
...@@ -234,11 +236,17 @@ class MessageListFragment : ServiceBoundFragment() { ...@@ -234,11 +236,17 @@ class MessageListFragment : ServiceBoundFragment() {
fun processMessages(list: List<QuasselDatabase.DatabaseMessage>, selected: Set<MsgId>, fun processMessages(list: List<QuasselDatabase.DatabaseMessage>, selected: Set<MsgId>,
expanded: Set<MsgId>, markerLine: MsgId?): List<DisplayMessage> { expanded: Set<MsgId>, markerLine: MsgId?): List<DisplayMessage> {
var previous: QuasselDatabase.DatabaseMessage? = null var previous: QuasselDatabase.DatabaseMessage? = null
var previousDate: ZonedDateTime? = null
return list.asReversed().map { return list.asReversed().map {
it.followUp = previous?.sender == it.sender val date = it.time.atZone(ZoneId.systemDefault()).truncatedTo(ChronoUnit.DAYS)
val isSameDay = previousDate?.isEqual(date) ?: false
val isFollowUp = previous?.sender == it.sender && isSameDay
previous = it previous = it
previousDate = date
DisplayMessage( DisplayMessage(
content = it, content = it,
hasDayChange = !isSameDay,
isFollowUp = isFollowUp,
isSelected = selected.contains(it.messageId), isSelected = selected.contains(it.messageId),
isExpanded = expanded.contains(it.messageId), isExpanded = expanded.contains(it.messageId),
isMarkerLine = markerLine == it.messageId isMarkerLine = markerLine == it.messageId
...@@ -253,7 +261,7 @@ class MessageListFragment : ServiceBoundFragment() { ...@@ -253,7 +261,7 @@ class MessageListFragment : ServiceBoundFragment() {
.toLiveData().switchMapNotNull { (buffer, selected, expanded, markerLine) -> .toLiveData().switchMapNotNull { (buffer, selected, expanded, markerLine) ->
database.filtered().listen(accountId, buffer).switchMapNotNull { filtered -> database.filtered().listen(accountId, buffer).switchMapNotNull { filtered ->
LivePagedListBuilder( LivePagedListBuilder(
database.message().findByBufferIdPagedWithDayChange(buffer, filtered).mapByPage { database.message().findByBufferIdPaged(buffer, filtered).mapByPage {
processMessages(it, selected.keys, expanded, markerLine.orNull()) processMessages(it, selected.keys, expanded, markerLine.orNull())
}, },
PagedList.Config.Builder() PagedList.Config.Builder()
...@@ -368,6 +376,7 @@ class MessageListFragment : ServiceBoundFragment() { ...@@ -368,6 +376,7 @@ class MessageListFragment : ServiceBoundFragment() {
val preloader = RecyclerViewPreloader(Glide.with(this), preloadModelProvider, sizeProvider, 10) val preloader = RecyclerViewPreloader(Glide.with(this), preloadModelProvider, sizeProvider, 10)
messageList.addOnScrollListener(preloader) messageList.addOnScrollListener(preloader)
messageList.addItemDecoration(DayChangeItemDecoration(adapter))
return view return view
} }
......
...@@ -86,12 +86,15 @@ class NickListAdapter( ...@@ -86,12 +86,15 @@ class NickListAdapter(
nick.text = SpanFormatter.format("%s%s", data.modes, data.displayNick ?: data.nick) nick.text = SpanFormatter.format("%s%s", data.modes, data.displayNick ?: data.nick)
realname.text = data.realname realname.text = data.realname
if (data.avatarUrl != null) {
GlideApp.with(itemView) GlideApp.with(itemView)
.load(data.avatarUrl) .load(data.avatarUrl)
.apply(RequestOptions.circleCropTransform()) .apply(RequestOptions.circleCropTransform())
.placeholder(data.fallbackDrawable) .placeholder(data.fallbackDrawable)
.into(avatar) .into(avatar)
} else {
avatar.setImageDrawable(data.fallbackDrawable)
}
} }
} }
......
...@@ -7,6 +7,10 @@ ...@@ -7,6 +7,10 @@
android:orientation="vertical" android:orientation="vertical"
android:textAppearance="?android:attr/textAppearanceListItemSmall"> android:textAppearance="?android:attr/textAppearanceListItemSmall">
<Space
android:layout_width="match_parent"
android:layout_height="@dimen/message_vertical" />
<View <View
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="1dp" android:layout_height="1dp"
......
<?xml version="1.0" encoding="utf-8"?>
<resources>
<item name="tag_daychange" type="id" />
<item name="tag_daychange_layout" type="id" />
<item name="tag_daychange_content" type="id" />
</resources>
...@@ -29,8 +29,7 @@ class QuasselBacklogStorage(private val db: QuasselDatabase) : BacklogStorage { ...@@ -29,8 +29,7 @@ class QuasselBacklogStorage(private val db: QuasselDatabase) : BacklogStorage {
bufferId = message.bufferInfo.bufferId, bufferId = message.bufferInfo.bufferId,
sender = message.sender, sender = message.sender,
senderPrefixes = message.senderPrefixes, senderPrefixes = message.senderPrefixes,
content = message.content, content = message.content
followUp = false
) )
) )
} }
......
...@@ -2,7 +2,6 @@ package de.kuschku.quasseldroid.persistence ...@@ -2,7 +2,6 @@ package de.kuschku.quasseldroid.persistence
import android.arch.lifecycle.LiveData import android.arch.lifecycle.LiveData
import android.arch.paging.DataSource import android.arch.paging.DataSource
import android.arch.persistence.db.SimpleSQLiteQuery
import android.arch.persistence.db.SupportSQLiteDatabase import android.arch.persistence.db.SupportSQLiteDatabase
import android.arch.persistence.db.SupportSQLiteQuery import android.arch.persistence.db.SupportSQLiteQuery
import android.arch.persistence.room.* import android.arch.persistence.room.*
...@@ -17,7 +16,7 @@ import de.kuschku.quasseldroid.persistence.QuasselDatabase.Filtered ...@@ -17,7 +16,7 @@ import de.kuschku.quasseldroid.persistence.QuasselDatabase.Filtered
import io.reactivex.Flowable import io.reactivex.Flowable
import org.threeten.bp.Instant import org.threeten.bp.Instant
@Database(entities = [DatabaseMessage::class, Filtered::class], version = 4) @Database(entities = [DatabaseMessage::class, Filtered::class], version = 5)
@TypeConverters(DatabaseMessage.MessageTypeConverters::class) @TypeConverters(DatabaseMessage.MessageTypeConverters::class)
abstract class QuasselDatabase : RoomDatabase() { abstract class QuasselDatabase : RoomDatabase() {
abstract fun message(): MessageDao abstract fun message(): MessageDao
...@@ -32,8 +31,7 @@ abstract class QuasselDatabase : RoomDatabase() { ...@@ -32,8 +31,7 @@ abstract class QuasselDatabase : RoomDatabase() {
var bufferId: Int, var bufferId: Int,
var sender: String, var sender: String,
var senderPrefixes: String, var senderPrefixes: String,
var content: String, var content: String
var followUp: Boolean
) { ) {
class MessageTypeConverters { class MessageTypeConverters {
@TypeConverter @TypeConverter
...@@ -177,6 +175,12 @@ abstract class QuasselDatabase : RoomDatabase() { ...@@ -177,6 +175,12 @@ abstract class QuasselDatabase : RoomDatabase() {
"ALTER TABLE message ADD followUp INT DEFAULT 0 NOT NULL;" "ALTER TABLE message ADD followUp INT DEFAULT 0 NOT NULL;"
) )
} }
},
object : Migration(4, 5) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("drop table message;")
database.execSQL("create table message (messageId INTEGER not null primary key, time INTEGER not null, type INTEGER not null, flag INTEGER not null, bufferId INTEGER not null, sender TEXT not null, senderPrefixes TEXT not null, content TEXT not null);")
}
} }
).build() ).build()
} }
...@@ -197,93 +201,3 @@ fun QuasselDatabase.MessageDao.clearMessages( ...@@ -197,93 +201,3 @@ fun QuasselDatabase.MessageDao.clearMessages(
) { ) {
this.clearMessages(bufferId, idRange.first, idRange.last) this.clearMessages(bufferId, idRange.first, idRange.last)
} }
fun QuasselDatabase.MessageDao.findByBufferIdPagedWithDayChange(bufferId: Int, type: Int) =
this.findMessagesRawPaged(SimpleSQLiteQuery("""
SELECT t.*
FROM
(
SELECT
messageId,
time,
type,
flag,
bufferId,
sender,
senderPrefixes,
content,
followUp
FROM message
WHERE bufferId = ?
AND type & ~? > 0
UNION ALL
SELECT DISTINCT
strftime('%s', date(datetime(time / 1000, 'unixepoch', 'localtime')), 'utc') * -1000 AS messageId,
strftime('%s', date(datetime(time / 1000, 'unixepoch', 'localtime')), 'utc') * 1000 AS time,
8192 AS type,
0 AS flag,
? AS bufferId,
'' AS sender,
'' AS senderPrefixes,
'' AS content,
0 AS followUp
FROM message
WHERE bufferId = ?
AND type & ~? > 0
) t
ORDER BY TIME
DESC, messageId
DESC
""", arrayOf(bufferId, type, bufferId, bufferId, type)))
fun QuasselDatabase.MessageDao.findByBufferIdPagedWithDayChangeSlow(bufferId: Int, type: Int) =
this.findMessagesRawPaged(SimpleSQLiteQuery("""
SELECT t.*
FROM
(
SELECT
messageId,
time,
type,
flag,
bufferId,
sender,
senderPrefixes,
content,
(SELECT 1
FROM
(SELECT *
FROM message m
WHERE m.messageId < message.messageId
AND bufferId = ?
AND type & ~? > 0
ORDER BY m.messageId
DESC
LIMIT 1) t
WHERE t.sender = message.sender
AND strftime('%s', date(datetime(t.time / 1000, 'unixepoch', 'localtime')), 'utc') * 1000 =
strftime('%s', date(datetime(message.time / 1000, 'unixepoch', 'localtime')), 'utc') * 1000
AND t.type = message.type
) = 1 AS followUp
FROM message
WHERE bufferId = ?
AND type & ~? > 0
UNION ALL
SELECT DISTINCT
strftime('%s', date(datetime(time / 1000, 'unixepoch', 'localtime')), 'utc') * -1000 AS messageId,
strftime('%s', date(datetime(time / 1000, 'unixepoch', 'localtime')), 'utc') * 1000 AS time,
8192 AS type,
0 AS flag,
? AS bufferId,
'' AS sender,
'' AS senderPrefixes,
'' AS content,
0 AS followUp
FROM message
WHERE bufferId = ?
AND type & ~? > 0
) t
ORDER BY TIME
DESC, messageId
DESC
""", arrayOf(bufferId, type, bufferId, type, bufferId, bufferId, type)))
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment