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
No related branches found
No related tags found
No related merge requests found
Showing
with 169 additions and 121 deletions
......@@ -112,11 +112,15 @@ class AutoCompleteAdapter(
nick.text = SpanFormatter.format("%s%s", data.modes, data.displayNick ?: data.nick)
realname.text = data.realname
if (data.avatarUrl != null) {
GlideApp.with(itemView)
.load(data.avatarUrl)
.apply(RequestOptions.circleCropTransform())
.placeholder(data.fallbackDrawable)
.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
data class DisplayMessage(
val content: QuasselDatabase.DatabaseMessage,
val hasDayChange: Boolean,
val isFollowUp: Boolean,
val isSelected: Boolean,
val isExpanded: Boolean,
val isMarkerLine: Boolean
) {
data class Tag(
val id: MsgId,
val hasDayChange: Boolean,
val isFollowUp: Boolean,
val isSelected: Boolean,
val isExpanded: 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 {
Regex("[us]id(\\d+)").matchEntire(HostmaskHelper.user(it))?.groupValues?.lastOrNull()?.let {
"https://www.irccloud.com/avatar-redirect/$it"
......
......@@ -61,7 +61,7 @@ class MessageAdapter(
override fun getItemViewType(position: Int) = getItem(position)?.let {
viewType(Message_Flags.of(it.content.type),
Message_Flags.of(it.content.flag),
it.content.followUp)
it.isFollowUp)
} ?: 0
private fun viewType(type: Message_Types, flags: Message_Flags, followUp: Boolean) =
......@@ -200,11 +200,15 @@ class MessageAdapter(
this.itemView.isSelected = message.isSelected
avatar?.let { avatarView ->
if (message.avatarUrl != null) {
GlideApp.with(itemView)
.load(message.avatarUrl)
.apply(RequestOptions.circleCropTransform())
.placeholder(message.fallbackDrawable)
.into(avatarView)
} else {
avatarView.setImageDrawable(message.fallbackDrawable)
}
}
}
}
......
......@@ -26,7 +26,6 @@ import de.kuschku.libquassel.util.helpers.value
import de.kuschku.quasseldroid.GlideApp
import de.kuschku.quasseldroid.R
import de.kuschku.quasseldroid.persistence.QuasselDatabase
import de.kuschku.quasseldroid.persistence.findByBufferIdPagedWithDayChange
import de.kuschku.quasseldroid.settings.AppearanceSettings
import de.kuschku.quasseldroid.settings.BacklogSettings
import de.kuschku.quasseldroid.settings.MessageSettings
......@@ -34,6 +33,9 @@ import de.kuschku.quasseldroid.util.helper.*
import de.kuschku.quasseldroid.util.service.ServiceBoundFragment
import de.kuschku.quasseldroid.util.ui.SpanFormatter
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 javax.inject.Inject
......@@ -234,11 +236,17 @@ class MessageListFragment : ServiceBoundFragment() {
fun processMessages(list: List<QuasselDatabase.DatabaseMessage>, selected: Set<MsgId>,
expanded: Set<MsgId>, markerLine: MsgId?): List<DisplayMessage> {
var previous: QuasselDatabase.DatabaseMessage? = null
var previousDate: ZonedDateTime? = null
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
previousDate = date
DisplayMessage(
content = it,
hasDayChange = !isSameDay,
isFollowUp = isFollowUp,
isSelected = selected.contains(it.messageId),
isExpanded = expanded.contains(it.messageId),
isMarkerLine = markerLine == it.messageId
......@@ -253,7 +261,7 @@ class MessageListFragment : ServiceBoundFragment() {
.toLiveData().switchMapNotNull { (buffer, selected, expanded, markerLine) ->
database.filtered().listen(accountId, buffer).switchMapNotNull { filtered ->
LivePagedListBuilder(
database.message().findByBufferIdPagedWithDayChange(buffer, filtered).mapByPage {
database.message().findByBufferIdPaged(buffer, filtered).mapByPage {
processMessages(it, selected.keys, expanded, markerLine.orNull())
},
PagedList.Config.Builder()
......@@ -368,6 +376,7 @@ class MessageListFragment : ServiceBoundFragment() {
val preloader = RecyclerViewPreloader(Glide.with(this), preloadModelProvider, sizeProvider, 10)
messageList.addOnScrollListener(preloader)
messageList.addItemDecoration(DayChangeItemDecoration(adapter))
return view
}
......
......@@ -86,12 +86,15 @@ class NickListAdapter(
nick.text = SpanFormatter.format("%s%s", data.modes, data.displayNick ?: data.nick)
realname.text = data.realname
if (data.avatarUrl != null) {
GlideApp.with(itemView)
.load(data.avatarUrl)
.apply(RequestOptions.circleCropTransform())
.placeholder(data.fallbackDrawable)
.into(avatar)
} else {
avatar.setImageDrawable(data.fallbackDrawable)
}
}
}
......
......@@ -7,6 +7,10 @@
android:orientation="vertical"
android:textAppearance="?android:attr/textAppearanceListItemSmall">
<Space
android:layout_width="match_parent"
android:layout_height="@dimen/message_vertical" />
<View
android:layout_width="match_parent"
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 {
bufferId = message.bufferInfo.bufferId,
sender = message.sender,
senderPrefixes = message.senderPrefixes,
content = message.content,
followUp = false
content = message.content
)
)
}
......
......@@ -2,7 +2,6 @@ package de.kuschku.quasseldroid.persistence
import android.arch.lifecycle.LiveData
import android.arch.paging.DataSource
import android.arch.persistence.db.SimpleSQLiteQuery
import android.arch.persistence.db.SupportSQLiteDatabase
import android.arch.persistence.db.SupportSQLiteQuery
import android.arch.persistence.room.*
......@@ -17,7 +16,7 @@ import de.kuschku.quasseldroid.persistence.QuasselDatabase.Filtered
import io.reactivex.Flowable
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)
abstract class QuasselDatabase : RoomDatabase() {
abstract fun message(): MessageDao
......@@ -32,8 +31,7 @@ abstract class QuasselDatabase : RoomDatabase() {
var bufferId: Int,
var sender: String,
var senderPrefixes: String,
var content: String,
var followUp: Boolean
var content: String
) {
class MessageTypeConverters {
@TypeConverter
......@@ -177,6 +175,12 @@ abstract class QuasselDatabase : RoomDatabase() {
"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()
}
......@@ -197,93 +201,3 @@ fun QuasselDatabase.MessageDao.clearMessages(
) {
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