From 5cc6ea138f2f4d1ef0ddfab7d1deae0e30f9b16a Mon Sep 17 00:00:00 2001 From: Janne Koschinski <janne@kuschku.de> Date: Sun, 10 Dec 2017 17:56:10 +0100 Subject: [PATCH] Implement improved message loading and display --- app/build.gradle.kts | 2 + .../persistence/QuasselBacklogStorage.kt | 16 +- .../persistence/QuasselDatabase.kt | 3 + .../ui/chat/BufferViewConfigFragment.kt | 2 +- .../quasseldroid_ng/ui/chat/ChatActivity.kt | 66 +++-- .../quasseldroid_ng/ui/chat/MessageAdapter.kt | 44 ++- .../ui/chat/MessageListFragment.kt | 93 ++++++- .../ui/chat/MessageRenderer.kt | 15 +- .../ui/chat/QuasselMessageRenderer.kt | 38 ++- .../compatibility/AndroidLoggingHandler.kt | 4 +- .../util/helper/FloatingActionButtonHelper.kt | 8 + .../util/helper/LiveDataHelper.kt | 5 + .../util/helper/LruCacheHelper.kt | 8 + ...MaterialContentLoadingProgressBarHelper.kt | 8 + .../ui/MaterialContentLoadingProgressBar.kt | 109 ++++++++ app/src/main/res/layout/activity_main.xml | 260 ++++++++++-------- app/src/main/res/layout/content_messages.xml | 8 - ...t_chat_list.xml => fragment_chat_list.xml} | 0 app/src/main/res/layout/fragment_messages.xml | 31 +++ ...t_nick_list.xml => fragment_nick_list.xml} | 0 .../res/layout/widget_chatmessage_plain.xml | 7 +- .../res/menu/{main.xml => activity_main.xml} | 10 +- .../quassel/syncables/BacklogManager.kt | 28 +- .../libquassel/session/BacklogStorage.kt | 3 +- .../de/kuschku/libquassel/session/ISession.kt | 2 + .../libquassel/session/ProtocolHandler.kt | 6 + .../de/kuschku/libquassel/session/Session.kt | 7 + .../libquassel/session/SessionManager.kt | 1 + 28 files changed, 558 insertions(+), 226 deletions(-) create mode 100644 app/src/main/java/de/kuschku/quasseldroid_ng/util/helper/FloatingActionButtonHelper.kt create mode 100644 app/src/main/java/de/kuschku/quasseldroid_ng/util/helper/LruCacheHelper.kt create mode 100644 app/src/main/java/de/kuschku/quasseldroid_ng/util/helper/MaterialContentLoadingProgressBarHelper.kt create mode 100644 app/src/main/java/de/kuschku/quasseldroid_ng/util/ui/MaterialContentLoadingProgressBar.kt delete mode 100644 app/src/main/res/layout/content_messages.xml rename app/src/main/res/layout/{content_chat_list.xml => fragment_chat_list.xml} (100%) create mode 100644 app/src/main/res/layout/fragment_messages.xml rename app/src/main/res/layout/{content_nick_list.xml => fragment_nick_list.xml} (100%) rename app/src/main/res/menu/{main.xml => activity_main.xml} (51%) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index c3df58fe4..27c388d7b 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -115,6 +115,8 @@ dependencies { exclude(group = "junit", module = "junit") } + implementation("me.zhanghai.android.materialprogressbar", "library", "1.4.2") + implementation("org.threeten", "threetenbp", "1.3.6", classifier = "no-tzdb") implementation("com.jakewharton", "butterknife", "8.8.1") diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/persistence/QuasselBacklogStorage.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/persistence/QuasselBacklogStorage.kt index f78640484..b05e47731 100644 --- a/app/src/main/java/de/kuschku/quasseldroid_ng/persistence/QuasselBacklogStorage.kt +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/persistence/QuasselBacklogStorage.kt @@ -5,7 +5,21 @@ import de.kuschku.libquassel.protocol.Message import de.kuschku.libquassel.session.BacklogStorage class QuasselBacklogStorage(private val db: QuasselDatabase) : BacklogStorage { - override fun storeMessages(vararg messages: Message) { + override fun storeMessages(vararg messages: Message, initialLoad: Boolean) + = storeMessages(messages.asIterable(), initialLoad) + + override fun storeMessages(messages: Iterable<Message>, initialLoad: Boolean) { + if (initialLoad) + for ((bufferId, bufferMessages) in messages.sortedBy { it.messageId }.groupBy { it.bufferInfo.bufferId }) { + val lastMessageId = db.message().findLastByBufferId(bufferId)?.messageId + val firstMessage = bufferMessages.firstOrNull() + if (lastMessageId != null && firstMessage != null) { + if (lastMessageId < firstMessage.messageId) { + db.message().clearMessages(bufferId) + } + } + } + for (message in messages) { db.message().save(QuasselDatabase.DatabaseMessage( messageId = message.messageId, diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/persistence/QuasselDatabase.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/persistence/QuasselDatabase.kt index 205530597..723a937a9 100644 --- a/app/src/main/java/de/kuschku/quasseldroid_ng/persistence/QuasselDatabase.kt +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/persistence/QuasselDatabase.kt @@ -71,6 +71,9 @@ abstract class QuasselDatabase : RoomDatabase() { @Query("UPDATE message SET bufferId = :bufferId1 WHERE bufferId = :bufferId2") fun merge(@IntRange(from = 0) bufferId1: Int, @IntRange(from = 0) bufferId2: Int) + @Query("SELECT count(*) FROM message WHERE bufferId = :bufferId") + fun bufferSize(@IntRange(from = 0) bufferId: Int): Int + @Query("DELETE FROM message") fun clearMessages() diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/BufferViewConfigFragment.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/BufferViewConfigFragment.kt index 5867f9f74..2babd2c93 100644 --- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/BufferViewConfigFragment.kt +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/BufferViewConfigFragment.kt @@ -87,7 +87,7 @@ class BufferViewConfigFragment : ServiceBoundFragment() { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - val view = inflater.inflate(R.layout.content_chat_list, container, false) + val view = inflater.inflate(R.layout.fragment_chat_list, container, false) ButterKnife.bind(this, view) chatListSpinner.adapter = adapter chatListSpinner.onItemSelectedListener = itemSelectedListener diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/ChatActivity.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/ChatActivity.kt index 5493cc2be..589ec88eb 100644 --- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/ChatActivity.kt +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/ChatActivity.kt @@ -10,6 +10,7 @@ import android.support.design.widget.Snackbar import android.support.v4.widget.DrawerLayout import android.support.v7.app.ActionBarDrawerToggle import android.support.v7.widget.Toolbar +import android.view.Gravity import android.view.Menu import android.view.MenuItem import android.widget.Button @@ -26,11 +27,9 @@ import de.kuschku.quasseldroid_ng.R import de.kuschku.quasseldroid_ng.persistence.AccountDatabase import de.kuschku.quasseldroid_ng.persistence.QuasselDatabase import de.kuschku.quasseldroid_ng.util.AndroidHandlerThread -import de.kuschku.quasseldroid_ng.util.helper.editApply -import de.kuschku.quasseldroid_ng.util.helper.map -import de.kuschku.quasseldroid_ng.util.helper.observeSticky -import de.kuschku.quasseldroid_ng.util.helper.switchMapRx +import de.kuschku.quasseldroid_ng.util.helper.* import de.kuschku.quasseldroid_ng.util.service.ServiceBoundActivity +import de.kuschku.quasseldroid_ng.util.ui.MaterialContentLoadingProgressBar class ChatActivity : ServiceBoundActivity() { var contentMessages: MessageListFragment? = null @@ -42,6 +41,9 @@ class ChatActivity : ServiceBoundActivity() { @BindView(R.id.toolbar) lateinit var toolbar: Toolbar + @BindView(R.id.progressBar) + lateinit var progressBar: MaterialContentLoadingProgressBar + @BindView(R.id.buttonSend) lateinit var buttonSend: Button @@ -54,6 +56,7 @@ class ChatActivity : ServiceBoundActivity() { private val sessionManager: LiveData<SessionManager?> = backend.map(Backend::sessionManager) private val state = sessionManager.switchMapRx(SessionManager::state) + private val initStatus = sessionManager.switchMapRx(SessionManager::initStatus) private var snackbar: Snackbar? = null @@ -82,9 +85,14 @@ class ChatActivity : ServiceBoundActivity() { println("Changed buffer to $it") } - //drawerToggle = ActionBarDrawerToggle(this, drawerLayout, R.string.drawer_open, R.string.drawer_close) - //actionBar.setDisplayHomeAsUpEnabled(true) - //actionBar.setHomeButtonEnabled(true) + currentBuffer.observe(this, Observer { + if (it != null) { + drawerLayout.closeDrawer(Gravity.START, true) + } + }) + + drawerToggle = ActionBarDrawerToggle(this, drawerLayout, R.string.drawer_open, R.string.drawer_close) + drawerToggle.syncState() backend.observeSticky(this, Observer { backendValue -> if (backendValue != null) { @@ -113,8 +121,8 @@ class ChatActivity : ServiceBoundActivity() { }) buttonSend.setOnClickListener { - sessionManager.value?.also { sessionManager -> - currentBuffer.value?.also { bufferId -> + sessionManager { sessionManager -> + currentBuffer { bufferId -> sessionManager.bufferSyncer?.bufferInfo(bufferId)?.also { bufferInfo -> sessionManager.rpcHandler?.sendInput(bufferInfo, input.text.toString()) } @@ -126,14 +134,30 @@ class ChatActivity : ServiceBoundActivity() { state.observe(this, Observer { val status = it ?: ConnectionState.DISCONNECTED + if (status == ConnectionState.CONNECTED) { + progressBar.progress = 1 + progressBar.max = 1 + } else { + progressBar.isIndeterminate = status != ConnectionState.INIT + } + + progressBar.toggle(status != ConnectionState.CONNECTED && status != ConnectionState.DISCONNECTED) + snackbar?.dismiss() snackbar = Snackbar.make(findViewById(R.id.contentMessages), status.name, Snackbar.LENGTH_SHORT) snackbar?.show() }) + + initStatus.observe(this, Observer { + val (progress, max) = it ?: 0 to 0 + + progressBar.max = max + progressBar.progress = progress + }) } override fun onCreateOptionsMenu(menu: Menu?): Boolean { - menuInflater.inflate(R.menu.main, menu) + menuInflater.inflate(R.menu.activity_main, menu) return super.onCreateOptionsMenu(menu) } @@ -143,31 +167,13 @@ class ChatActivity : ServiceBoundActivity() { getSharedPreferences(Keys.Status.NAME, Context.MODE_PRIVATE).editApply { putBoolean(Keys.Status.reconnect, false) } - backend.value?.disconnect(true) + backend()?.disconnect(true) setResult(Activity.RESULT_OK) finish() } true } - R.id.loadMore -> handler.post { - currentBuffer.value?.also { bufferId -> - sessionManager.value?.apply { - backlogManager?.requestBacklog( - bufferId = bufferId, - last = database.message().findFirstByBufferId(bufferId)?.messageId ?: -1, - limit = 20 - ) - } - } - } - R.id.clear -> handler.post { - currentBuffer.value?.also { bufferId -> - sessionManager.value?.apply { - backlogStorage.clearMessages(bufferId) - } - } - } - else -> super.onOptionsItemSelected(item) + else -> super.onOptionsItemSelected(item) } override fun onDestroy() { diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/MessageAdapter.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/MessageAdapter.kt index 7422ad507..3cdfd1a86 100644 --- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/MessageAdapter.kt +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/MessageAdapter.kt @@ -2,35 +2,63 @@ package de.kuschku.quasseldroid_ng.ui.chat import android.arch.paging.PagedListAdapter import android.content.Context +import android.util.LruCache import android.view.LayoutInflater import android.view.ViewGroup +import de.kuschku.libquassel.protocol.Message_Flag import de.kuschku.libquassel.protocol.Message_Flags import de.kuschku.libquassel.protocol.Message_Type +import de.kuschku.libquassel.protocol.Message_Types +import de.kuschku.libquassel.util.hasFlag import de.kuschku.quasseldroid_ng.persistence.QuasselDatabase +import de.kuschku.quasseldroid_ng.util.helper.getOrPut class MessageAdapter(context: Context) : PagedListAdapter<QuasselDatabase.DatabaseMessage, QuasselMessageViewHolder>(QuasselDatabase.DatabaseMessage.MessageDiffCallback) { - val messageRenderer: MessageRenderer = QuasselMessageRenderer(context) + private val messageRenderer: MessageRenderer = QuasselMessageRenderer(context) + + private val messageCache = LruCache<Int, FormattedMessage>(512) + + init { + setHasStableIds(true) + } override fun onBindViewHolder(holder: QuasselMessageViewHolder, position: Int) { - getItem(position)?.let { messageRenderer.bind(holder, it) } + getItem(position)?.let { messageRenderer.bind(holder, messageCache.getOrPut(it.messageId) { messageRenderer.render(it) }) } } override fun getItemViewType(position: Int): Int { - return getItem(position)?.type ?: 0 + val item = getItem(position) + if (item != null) { + return viewType(Message_Flags.of(item.type), Message_Flags.of(item.flag)) + } else { + return 0 + } + } + + private fun viewType(type: Message_Types, flags: Message_Flags): Int { + return (if (flags.hasFlag(Message_Flag.Highlight)) 0x8000 else 0x0000) or (type.value and 0x7FF) + } + + override fun getItemId(position: Int): Long { + return getItem(position)?.messageId?.toLong() ?: 0L } private fun messageType(viewType: Int): Message_Type? - = Message_Type.of(viewType).enabledValues().firstOrNull() + = Message_Type.of(viewType and 0x7FF).enabledValues().firstOrNull() - private fun messageFlags(viewType: Int): Message_Flags - = Message_Flags.of() + private fun hasHiglight(viewType: Int) + = viewType and 0x8000 != 0 override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QuasselMessageViewHolder { - return QuasselMessageViewHolder(LayoutInflater.from(parent.context).inflate( - messageRenderer.layout(messageType(viewType), messageFlags(viewType)), + val messageType = messageType(viewType) + val hasHighlight = hasHiglight(viewType) + val viewHolder = QuasselMessageViewHolder(LayoutInflater.from(parent.context).inflate( + messageRenderer.layout(messageType, hasHighlight), parent, false )) + messageRenderer.init(viewHolder, messageType, hasHighlight) + return viewHolder } } diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/MessageListFragment.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/MessageListFragment.kt index 7007b536f..cccda2cf3 100644 --- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/MessageListFragment.kt +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/MessageListFragment.kt @@ -5,7 +5,7 @@ import android.arch.lifecycle.MutableLiveData import android.arch.lifecycle.Observer import android.arch.paging.PagedList import android.os.Bundle -import android.support.v7.widget.DefaultItemAnimator +import android.support.design.widget.FloatingActionButton import android.support.v7.widget.LinearLayoutManager import android.support.v7.widget.RecyclerView import android.view.LayoutInflater @@ -14,34 +14,52 @@ import android.view.ViewGroup import butterknife.BindView import butterknife.ButterKnife import de.kuschku.libquassel.protocol.BufferId +import de.kuschku.libquassel.session.Backend +import de.kuschku.libquassel.session.SessionManager import de.kuschku.quasseldroid_ng.R import de.kuschku.quasseldroid_ng.persistence.QuasselDatabase +import de.kuschku.quasseldroid_ng.util.AndroidHandlerThread +import de.kuschku.quasseldroid_ng.util.helper.invoke +import de.kuschku.quasseldroid_ng.util.helper.map import de.kuschku.quasseldroid_ng.util.helper.switchMap +import de.kuschku.quasseldroid_ng.util.helper.toggle import de.kuschku.quasseldroid_ng.util.service.ServiceBoundFragment class MessageListFragment : ServiceBoundFragment() { val currentBuffer: MutableLiveData<LiveData<BufferId?>?> = MutableLiveData() + private val buffer = currentBuffer.switchMap { it } + + private val handler = AndroidHandlerThread("Chat") private lateinit var database: QuasselDatabase - @BindView(R.id.messageList) + @BindView(R.id.messages) lateinit var messageList: RecyclerView + @BindView(R.id.scrollDown) + lateinit var scrollDown: FloatingActionButton + + private val sessionManager: LiveData<SessionManager?> = backend.map(Backend::sessionManager) + + override fun onCreate(savedInstanceState: Bundle?) { + handler.onCreate() + super.onCreate(savedInstanceState) + setHasOptionsMenu(true) + } + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - val view = inflater.inflate(R.layout.content_messages, container, false) + val view = inflater.inflate(R.layout.fragment_messages, container, false) ButterKnife.bind(this, view) database = QuasselDatabase.Creator.init(context!!.applicationContext) - val data = currentBuffer.switchMap { - it.switchMap { - database.message().findByBufferIdPaged(it).create(Int.MAX_VALUE, - PagedList.Config.Builder() - .setPageSize(20) - .setEnablePlaceholders(true) - .setPrefetchDistance(20) - .build() - ) - } + val data = buffer.switchMap { + database.message().findByBufferIdPaged(it).create(Int.MAX_VALUE, + PagedList.Config.Builder() + .setPageSize(50) + .setEnablePlaceholders(false) + .setPrefetchDistance(50) + .build() + ) } val adapter = MessageAdapter(context!!) @@ -50,10 +68,55 @@ class MessageListFragment : ServiceBoundFragment() { adapter.setList(list) }) + buffer.observe(this, Observer { + handler.post { + // Try loading messages when switching to empty buffer + if (it != null && database.message().bufferSize(it) == 0) { + loadMore() + } + } + }) + + var recyclerViewMeasuredHeight = 0 + val scrollDownListener = object : RecyclerView.OnScrollListener() { + override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { + if (!recyclerView.canScrollVertically(-1)) { + loadMore() + } + if (recyclerViewMeasuredHeight == 0) + recyclerViewMeasuredHeight = recyclerView.measuredHeight + val canScrollDown = recyclerView.canScrollVertically(1) + val isScrollingDown = dy > 0 + val scrollOffsetFromBottom = recyclerView.computeVerticalScrollRange() - recyclerView.computeVerticalScrollOffset() - recyclerViewMeasuredHeight + val isMoreThanOneScreenFromBottom = scrollOffsetFromBottom > recyclerViewMeasuredHeight + val smartVisibility = scrollDown.visibility == View.VISIBLE || isMoreThanOneScreenFromBottom + scrollDown.toggle(canScrollDown && isScrollingDown && smartVisibility) + } + } + messageList.adapter = adapter messageList.layoutManager = LinearLayoutManager(context) - messageList.itemAnimator = DefaultItemAnimator() + messageList.addOnScrollListener(scrollDownListener) + + scrollDown.setOnClickListener { messageList.scrollToPosition(adapter.itemCount) } return view } -} \ No newline at end of file + + private fun loadMore() { + handler.post { + buffer { bufferId -> + backend()?.sessionManager()?.backlogManager?.requestBacklog( + bufferId = bufferId, + last = database.message().findFirstByBufferId(bufferId)?.messageId ?: -1, + limit = 20 + ) + } + } + } + + override fun onDestroy() { + handler.onDestroy() + super.onDestroy() + } +} diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/MessageRenderer.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/MessageRenderer.kt index 4ca12c9fa..c351e2273 100644 --- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/MessageRenderer.kt +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/MessageRenderer.kt @@ -1,13 +1,20 @@ package de.kuschku.quasseldroid_ng.ui.chat import android.support.annotation.LayoutRes -import de.kuschku.libquassel.protocol.Message_Flags import de.kuschku.libquassel.protocol.Message_Type import de.kuschku.quasseldroid_ng.persistence.QuasselDatabase interface MessageRenderer { @LayoutRes - fun layout(type: Message_Type?, flags: Message_Flags): Int + fun layout(type: Message_Type?, hasHighlight: Boolean): Int - fun bind(holder: QuasselMessageViewHolder, message: QuasselDatabase.DatabaseMessage) -} \ No newline at end of file + fun bind(holder: QuasselMessageViewHolder, message: FormattedMessage) + fun render(message: QuasselDatabase.DatabaseMessage): FormattedMessage + fun init(viewHolder: QuasselMessageViewHolder, messageType: Message_Type?, hasHighlight: Boolean) {} +} + +class FormattedMessage( + val id: Int, + val time: CharSequence, + val content: CharSequence +) \ No newline at end of file diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/QuasselMessageRenderer.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/QuasselMessageRenderer.kt index 751e67e35..fe5ed6e93 100644 --- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/QuasselMessageRenderer.kt +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/QuasselMessageRenderer.kt @@ -7,7 +7,6 @@ import android.text.format.DateFormat import android.text.style.ForegroundColorSpan import android.text.style.StyleSpan import de.kuschku.libquassel.protocol.Message.MessageType.* -import de.kuschku.libquassel.protocol.Message_Flags import de.kuschku.libquassel.protocol.Message_Type import de.kuschku.quasseldroid_ng.R import de.kuschku.quasseldroid_ng.persistence.QuasselDatabase @@ -18,8 +17,10 @@ import org.threeten.bp.format.DateTimeFormatter import java.text.SimpleDateFormat class QuasselMessageRenderer(context: Context) : MessageRenderer { - val timeFormatter = DateTimeFormatter.ofPattern((DateFormat.getTimeFormat(context) as SimpleDateFormat).toLocalizedPattern()) - val senderColors: IntArray + private val timeFormatter = DateTimeFormatter.ofPattern((DateFormat.getTimeFormat(context) as SimpleDateFormat).toLocalizedPattern()) + private val senderColors: IntArray + + private val zoneId = ZoneId.systemDefault() init { val typedArray = context.obtainStyledAttributes(intArrayOf( @@ -34,7 +35,7 @@ class QuasselMessageRenderer(context: Context) : MessageRenderer { typedArray.recycle() } - override fun layout(type: Message_Type?, flags: Message_Flags) + override fun layout(type: Message_Type?, hasHighlight: Boolean) = when (type) { Nick, Notice, Mode, Join, Part, Quit, Kick, Kill, Server, Info, DayChange, Topic, NetsplitJoin, NetsplitQuit, Invite -> R.layout.widget_chatmessage_server @@ -44,12 +45,29 @@ class QuasselMessageRenderer(context: Context) : MessageRenderer { else -> R.layout.widget_chatmessage_plain } - override fun bind(holder: QuasselMessageViewHolder, message: QuasselDatabase.DatabaseMessage) { - holder.time.text = timeFormatter.format(message.time.atZone(ZoneId.systemDefault())) - holder.content.text = SpanFormatter.format( - "%s: %s", - formatNick(message.sender), - message.content + override fun init(viewHolder: QuasselMessageViewHolder, messageType: Message_Type?, hasHighlight: Boolean) { + if (hasHighlight) { + val attrs = intArrayOf(R.attr.colorBackgroundHighlight) + val colors = viewHolder.itemView.context.obtainStyledAttributes(attrs) + viewHolder.itemView.setBackgroundColor(colors.getColor(0, 0)) + colors.recycle() + } + } + + override fun bind(holder: QuasselMessageViewHolder, message: FormattedMessage) { + holder.time.text = message.time + holder.content.text = message.content + } + + override fun render(message: QuasselDatabase.DatabaseMessage): FormattedMessage { + return FormattedMessage( + message.messageId, + timeFormatter.format(message.time.atZone(zoneId)), + SpanFormatter.format( + "%s: %s", + formatNick(message.sender), + message.content + ) ) } diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/util/compatibility/AndroidLoggingHandler.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/util/compatibility/AndroidLoggingHandler.kt index 564df9023..9208d91dd 100644 --- a/app/src/main/java/de/kuschku/quasseldroid_ng/util/compatibility/AndroidLoggingHandler.kt +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/util/compatibility/AndroidLoggingHandler.kt @@ -5,9 +5,7 @@ import de.kuschku.libquassel.util.compatibility.LoggingHandler object AndroidLoggingHandler : LoggingHandler() { override fun isLoggable(logLevel: LogLevel, tag: String): Boolean { - return Log.isLoggable(tag, - priority( - logLevel)) + return true || Log.isLoggable(tag, priority(logLevel)) } override fun log(logLevel: LogLevel, tag: String, message: String?, throwable: Throwable?) { diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/util/helper/FloatingActionButtonHelper.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/util/helper/FloatingActionButtonHelper.kt new file mode 100644 index 000000000..67d4bf5b0 --- /dev/null +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/util/helper/FloatingActionButtonHelper.kt @@ -0,0 +1,8 @@ +package de.kuschku.quasseldroid_ng.util.helper + +import android.support.design.widget.FloatingActionButton + +fun FloatingActionButton.toggle(visible: Boolean) { + if (visible) show() + else hide() +} \ No newline at end of file diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/util/helper/LiveDataHelper.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/util/helper/LiveDataHelper.kt index 78f083ab2..ea273739e 100644 --- a/app/src/main/java/de/kuschku/quasseldroid_ng/util/helper/LiveDataHelper.kt +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/util/helper/LiveDataHelper.kt @@ -110,3 +110,8 @@ inline fun <T> LiveData<T>.observeForeverSticky(observer: Observer<T>) { inline fun <T> LiveData<T>.toObservable(lifecycleOwner: LifecycleOwner): Observable<T> = Observable.fromPublisher(LiveDataReactiveStreams.toPublisher(lifecycleOwner, this)) + + +inline operator fun <T> LiveData<T>.invoke() = value + +inline operator fun <T, U> LiveData<T?>.invoke(f: (T) -> U?) = value?.let(f) \ No newline at end of file diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/util/helper/LruCacheHelper.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/util/helper/LruCacheHelper.kt new file mode 100644 index 000000000..772a9f53e --- /dev/null +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/util/helper/LruCacheHelper.kt @@ -0,0 +1,8 @@ +package de.kuschku.quasseldroid_ng.util.helper + +import android.util.LruCache + +inline fun <K, V> LruCache<K, V>.getOrPut(key: K, value: () -> V) = get(key) ?: value().let { + put(key, it) + it +} \ No newline at end of file diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/util/helper/MaterialContentLoadingProgressBarHelper.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/util/helper/MaterialContentLoadingProgressBarHelper.kt new file mode 100644 index 000000000..87ecb1c85 --- /dev/null +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/util/helper/MaterialContentLoadingProgressBarHelper.kt @@ -0,0 +1,8 @@ +package de.kuschku.quasseldroid_ng.util.helper + +import de.kuschku.quasseldroid_ng.util.ui.MaterialContentLoadingProgressBar + +fun MaterialContentLoadingProgressBar.toggle(visible: Boolean) { + if (visible) show() + else hide() +} \ No newline at end of file diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/util/ui/MaterialContentLoadingProgressBar.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/util/ui/MaterialContentLoadingProgressBar.kt new file mode 100644 index 000000000..bceebb5e0 --- /dev/null +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/util/ui/MaterialContentLoadingProgressBar.kt @@ -0,0 +1,109 @@ +package de.kuschku.quasseldroid_ng.util.ui + +/* + * Copyright (C) 2013 The Android Open Source Project + * + * 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. + */ + +import android.content.Context +import android.util.AttributeSet +import android.view.View +import me.zhanghai.android.materialprogressbar.MaterialProgressBar + +/** + * ContentLoadingProgressBar implements a ProgressBar that waits a minimum time to be + * dismissed before showing. Once visible, the progress bar will be visible for + * a minimum amount of time to avoid "flashes" in the UI when an event could take + * a largely variable time to complete (from none, to a user perceivable amount) + */ +class MaterialContentLoadingProgressBar @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : MaterialProgressBar(context, attrs, 0) { + private var mStartTime: Long = -1 + private var mPostedHide = false + private var mPostedShow = false + private var mDismissed = false + + private val mDelayedHide = Runnable { + mPostedHide = false + mStartTime = -1 + visibility = View.GONE + } + + private val mDelayedShow = Runnable { + mPostedShow = false + if (!mDismissed) { + mStartTime = System.currentTimeMillis() + visibility = View.VISIBLE + } + } + + public override fun onAttachedToWindow() { + super.onAttachedToWindow() + removeCallbacks() + } + + public override fun onDetachedFromWindow() { + super.onDetachedFromWindow() + removeCallbacks() + } + + private fun removeCallbacks() { + removeCallbacks(mDelayedHide) + removeCallbacks(mDelayedShow) + } + + /** + * Hide the progress view if it is visible. The progress view will not be + * hidden until it has been shown for at least a minimum show time. If the + * progress view was not yet visible, cancels showing the progress view. + */ + fun hide() { + mDismissed = true + removeCallbacks(mDelayedShow) + val diff = System.currentTimeMillis() - mStartTime + if (diff >= MIN_SHOW_TIME || mStartTime == -1L) { + // The progress spinner has been shown long enough + // OR was not shown yet. If it wasn't shown yet, + // it will just never be shown. + visibility = View.GONE + } else { + // The progress spinner is shown, but not long enough, + // so put a delayed message in to hide it when its been + // shown long enough. + if (!mPostedHide) { + postDelayed(mDelayedHide, MIN_SHOW_TIME - diff) + mPostedHide = true + } + } + } + + /** + * Show the progress view after waiting for a minimum delay. If + * during that time, hide() is called, the view is never made visible. + */ + fun show() { + // Reset the start time. + mStartTime = -1 + mDismissed = false + removeCallbacks(mDelayedHide) + if (!mPostedShow) { + postDelayed(mDelayedShow, MIN_DELAY.toLong()) + mPostedShow = true + } + } + + companion object { + private val MIN_SHOW_TIME = 500 // ms + private val MIN_DELAY = 500 // ms + } +} diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 2f9777bab..aece9930f 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -1,140 +1,156 @@ <android.support.v4.widget.DrawerLayout 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" android:id="@+id/drawerLayout" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:fitsSystemWindows="true"> - - <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" - android:fitsSystemWindows="true" - android:orientation="vertical"> + android:fitsSystemWindows="true"> - <android.support.design.widget.AppBarLayout - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:theme="?attr/actionBarTheme"> - - <android.support.v7.widget.Toolbar - android:id="@+id/toolbar" + <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" - android:layout_height="?attr/actionBarSize" - app:contentInsetStartWithNavigation="0dp" - app:popupTheme="@style/Widget.PopupOverlay"> - - <LinearLayout - android:id="@+id/toolbar_action_area" - android:layout_width="fill_parent" - android:layout_height="fill_parent" - android:background="?attr/selectableItemBackgroundBorderless" - android:clickable="true" - android:focusable="true" - android:focusableInTouchMode="false" - android:gravity="center_vertical|start" - android:minHeight="?attr/actionBarSize" - android:orientation="vertical"> - - <LinearLayout - android:layout_width="wrap_content" + android:layout_height="match_parent" + android:fitsSystemWindows="true" + android:orientation="vertical"> + + <android.support.design.widget.AppBarLayout + android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_gravity="center_vertical" - android:layout_marginTop="-2dp" - android:baselineAligned="false" - android:gravity="center_vertical"> - - <TextView - android:id="@+id/key" - style="@style/TextAppearance.AppCompat.Widget.ActionBar.Title" - android:layout_width="16dp" - android:layout_height="16dp" - android:layout_marginEnd="2dp" - android:layout_marginRight="2dp" - android:layout_marginTop="2dp" - android:gravity="center" - android:textSize="16sp" - android:visibility="gone" /> - - <TextView - android:id="@+id/toolbar_title" - style="@style/TextAppearance.AppCompat.Widget.ActionBar.Title" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:ellipsize="end" - android:gravity="center_vertical" - android:singleLine="true" - android:text="@string/app_name" /> - - </LinearLayout> - - <TextView - android:id="@+id/toolbar_subtitle" - style="@style/TextAppearance.AppCompat.Widget.ActionBar.Subtitle" - android:layout_width="wrap_content" + android:theme="?attr/actionBarTheme"> + + <FrameLayout + android:layout_width="match_parent" + android:layout_height="?attr/actionBarSize"> + + <android.support.v7.widget.Toolbar + android:id="@+id/toolbar" + android:layout_width="match_parent" + android:layout_height="match_parent" + app:contentInsetStartWithNavigation="0dp" + app:popupTheme="@style/Widget.PopupOverlay"> + + <LinearLayout + android:id="@+id/toolbar_action_area" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:background="?attr/selectableItemBackgroundBorderless" + android:clickable="true" + android:focusable="true" + android:focusableInTouchMode="false" + android:gravity="center_vertical|start" + android:minHeight="?attr/actionBarSize" + android:orientation="vertical"> + + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical" + android:layout_marginTop="-2dp" + android:baselineAligned="false" + android:gravity="center_vertical"> + + <TextView + android:id="@+id/key" + style="@style/TextAppearance.AppCompat.Widget.ActionBar.Title" + android:layout_width="16dp" + android:layout_height="16dp" + android:layout_marginEnd="2dp" + android:layout_marginRight="2dp" + android:layout_marginTop="2dp" + android:gravity="center" + android:textSize="16sp" + android:visibility="gone" /> + + <TextView + android:id="@+id/toolbar_title" + style="@style/TextAppearance.AppCompat.Widget.ActionBar.Title" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:ellipsize="end" + android:gravity="center_vertical" + android:singleLine="true" + android:text="@string/app_name" /> + + </LinearLayout> + + <TextView + android:id="@+id/toolbar_subtitle" + style="@style/TextAppearance.AppCompat.Widget.ActionBar.Subtitle" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="-3dp" + android:ellipsize="end" + android:singleLine="true" + android:visibility="gone" /> + </LinearLayout> + + </android.support.v7.widget.Toolbar> + + <de.kuschku.quasseldroid_ng.util.ui.MaterialContentLoadingProgressBar + android:id="@+id/progressBar" + style="@style/Widget.MaterialProgressBar.ProgressBar.Horizontal.NoPadding" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_gravity="bottom" + app:mpb_progressStyle="horizontal" + app:mpb_setBothDrawables="true" + app:mpb_useIntrinsicPadding="false" + tools:indeterminate="true" /> + </FrameLayout> + + </android.support.design.widget.AppBarLayout> + + <fragment + android:id="@+id/contentMessages" + android:name="de.kuschku.quasseldroid_ng.ui.chat.MessageListFragment" + android:layout_width="match_parent" + android:layout_height="0dip" + android:layout_weight="1" + tools:layout="@layout/fragment_messages" /> + + <android.support.v7.widget.CardView + android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginTop="-3dp" - android:ellipsize="end" - android:singleLine="true" - android:visibility="gone" /> - </LinearLayout> - - </android.support.v7.widget.Toolbar> - - </android.support.design.widget.AppBarLayout> - - <fragment - android:id="@+id/contentMessages" - android:name="de.kuschku.quasseldroid_ng.ui.chat.MessageListFragment" - android:layout_width="match_parent" - android:layout_height="0dip" - android:layout_weight="1" - tools:layout="@layout/content_messages" /> + android:background="?colorBackgroundCard" + app:cardElevation="4dp"> - <android.support.v7.widget.CardView - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:background="?colorBackgroundCard" - app:cardElevation="4dp"> + <LinearLayout + android:layout_width="match_parent" + android:layout_height="?actionBarSize"> - <LinearLayout - android:layout_width="match_parent" - android:layout_height="?actionBarSize"> + <EditText + android:id="@+id/input" + android:layout_width="0dip" + android:layout_height="match_parent" + android:layout_weight="1" /> - <EditText - android:id="@+id/input" - android:layout_width="0dip" - android:layout_height="match_parent" - android:layout_weight="1" /> - - <Button - android:id="@+id/buttonSend" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center_vertical" - android:text="Send" /> - </LinearLayout> + <Button + android:id="@+id/buttonSend" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical" + android:text="Send" /> + </LinearLayout> - </android.support.v7.widget.CardView> + </android.support.v7.widget.CardView> - </LinearLayout> + </LinearLayout> - <include layout="@layout/content_nick_list" /> + <include layout="@layout/fragment_nick_list" /> - <de.kuschku.quasseldroid_ng.util.ui.NavigationDrawerLayout - android:layout_width="match_parent" - android:layout_height="match_parent" - android:layout_gravity="start" - android:background="?attr/colorBackground" - android:fitsSystemWindows="true" - app:insetForeground="?attr/colorPrimaryDark"> - - <fragment - android:id="@+id/chatListFragment" - android:name="de.kuschku.quasseldroid_ng.ui.chat.BufferViewConfigFragment" - android:layout_width="match_parent" + <de.kuschku.quasseldroid_ng.util.ui.NavigationDrawerLayout + android:layout_width="match_parent" android:layout_height="match_parent" - tools:layout="@layout/content_chat_list" /> - </de.kuschku.quasseldroid_ng.util.ui.NavigationDrawerLayout> + android:layout_gravity="start" + android:background="?attr/colorBackground" + android:fitsSystemWindows="true" + app:insetForeground="?attr/colorPrimaryDark"> + + <fragment + android:id="@+id/chatListFragment" + android:name="de.kuschku.quasseldroid_ng.ui.chat.BufferViewConfigFragment" + android:layout_width="match_parent" + android:layout_height="match_parent" + tools:layout="@layout/fragment_chat_list" /> + </de.kuschku.quasseldroid_ng.util.ui.NavigationDrawerLayout> </android.support.v4.widget.DrawerLayout> diff --git a/app/src/main/res/layout/content_messages.xml b/app/src/main/res/layout/content_messages.xml deleted file mode 100644 index cce02ed3b..000000000 --- a/app/src/main/res/layout/content_messages.xml +++ /dev/null @@ -1,8 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<android.support.v7.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:tools="http://schemas.android.com/tools" - android:id="@+id/messageList" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:background="?attr/colorBackground" - tools:listitem="@android:layout/simple_list_item_1" /> \ No newline at end of file diff --git a/app/src/main/res/layout/content_chat_list.xml b/app/src/main/res/layout/fragment_chat_list.xml similarity index 100% rename from app/src/main/res/layout/content_chat_list.xml rename to app/src/main/res/layout/fragment_chat_list.xml diff --git a/app/src/main/res/layout/fragment_messages.xml b/app/src/main/res/layout/fragment_messages.xml new file mode 100644 index 000000000..57e8d5acd --- /dev/null +++ b/app/src/main/res/layout/fragment_messages.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="utf-8"?> +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="?attr/colorBackground"> + + <android.support.v7.widget.RecyclerView + android:id="@+id/messages" + android:layout_width="match_parent" + android:layout_height="match_parent" + app:stackFromEnd="true" + tools:listitem="@layout/widget_chatmessage_plain" /> + + <android.support.design.widget.FloatingActionButton + android:id="@+id/scrollDown" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="end|bottom" + android:layout_marginBottom="12dp" + android:layout_marginEnd="12dp" + android:layout_marginRight="12dp" + android:tint="@color/colorFillDark" + app:backgroundTint="#8A808080" + app:elevation="0dip" + app:fabSize="mini" + app:pressedTranslationZ="0dip" + app:srcCompat="@drawable/ic_scroll_down" /> + +</FrameLayout> \ No newline at end of file diff --git a/app/src/main/res/layout/content_nick_list.xml b/app/src/main/res/layout/fragment_nick_list.xml similarity index 100% rename from app/src/main/res/layout/content_nick_list.xml rename to app/src/main/res/layout/fragment_nick_list.xml diff --git a/app/src/main/res/layout/widget_chatmessage_plain.xml b/app/src/main/res/layout/widget_chatmessage_plain.xml index 0e16b66a1..75105c7d9 100644 --- a/app/src/main/res/layout/widget_chatmessage_plain.xml +++ b/app/src/main/res/layout/widget_chatmessage_plain.xml @@ -1,5 +1,6 @@ <?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:layout_width="match_parent" android:layout_height="wrap_content" android:clickable="true" @@ -21,7 +22,8 @@ android:layout_marginEnd="@dimen/message_horizontal" android:layout_marginRight="@dimen/message_horizontal" android:textColor="?attr/colorForegroundSecondary" - android:typeface="monospace" /> + android:typeface="monospace" + tools:text="[15:55]" /> <TextView android:id="@+id/content" @@ -29,5 +31,6 @@ android:layout_height="wrap_content" android:layout_weight="1" android:textColor="?attr/colorForeground" - android:textIsSelectable="true" /> + android:textIsSelectable="true" + tools:text="justJanne: hiii" /> </LinearLayout> diff --git a/app/src/main/res/menu/main.xml b/app/src/main/res/menu/activity_main.xml similarity index 51% rename from app/src/main/res/menu/main.xml rename to app/src/main/res/menu/activity_main.xml index 1f23bf3b4..2079987d4 100644 --- a/app/src/main/res/menu/main.xml +++ b/app/src/main/res/menu/activity_main.xml @@ -1,13 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<menu xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:app="http://schemas.android.com/apk/res-auto"> - <item - android:id="@+id/loadMore" - android:title="Load More" - app:showAsAction="always" /> - <item - android:id="@+id/clear" - android:title="Delete History" /> +<menu xmlns:android="http://schemas.android.com/apk/res/android"> <item android:id="@+id/settings" android:title="Settings" /> diff --git a/lib/src/main/java/de/kuschku/libquassel/quassel/syncables/BacklogManager.kt b/lib/src/main/java/de/kuschku/libquassel/quassel/syncables/BacklogManager.kt index 7d8b5c902..dd9150141 100644 --- a/lib/src/main/java/de/kuschku/libquassel/quassel/syncables/BacklogManager.kt +++ b/lib/src/main/java/de/kuschku/libquassel/quassel/syncables/BacklogManager.kt @@ -4,8 +4,7 @@ import de.kuschku.libquassel.protocol.* import de.kuschku.libquassel.quassel.syncables.interfaces.IBacklogManager import de.kuschku.libquassel.session.BacklogStorage import de.kuschku.libquassel.session.SignalProxy -import de.kuschku.libquassel.util.compatibility.LoggingHandler -import de.kuschku.libquassel.util.compatibility.log +import java.util.concurrent.atomic.AtomicInteger class BacklogManager( proxy: SignalProxy, @@ -15,21 +14,26 @@ class BacklogManager( initialized = true } + private var loading = AtomicInteger(-1) + + override fun requestBacklog(bufferId: BufferId, first: MsgId, last: MsgId, limit: Int, additional: Int) { + if (loading.getAndSet(bufferId) != bufferId) { + super.requestBacklog(bufferId, first, last, limit, additional) + } + } + + override fun requestBacklogAll(first: MsgId, last: MsgId, limit: Int, additional: Int) { + super.requestBacklogAll(first, last, limit, additional) + } + override fun receiveBacklog(bufferId: BufferId, first: MsgId, last: MsgId, limit: Int, additional: Int, messages: QVariantList) { - for (message: Message in messages.mapNotNull<QVariant_, Message>(QVariant_::value)) { - if (message.bufferInfo.bufferId != bufferId) { - // Check if it works here - log(LoggingHandler.LogLevel.ERROR, "message has inconsistent bufferid: $bufferId != ${message.bufferInfo.bufferId}") - } - backlogStorage.storeMessages(message) - } + loading.compareAndSet(bufferId, -1) + backlogStorage.storeMessages(messages.mapNotNull(QVariant_::value), initialLoad = true) } override fun receiveBacklogAll(first: MsgId, last: MsgId, limit: Int, additional: Int, messages: QVariantList) { - for (message: Message in messages.mapNotNull<QVariant_, Message>(QVariant_::value)) { - backlogStorage.storeMessages(message) - } + backlogStorage.storeMessages(messages.mapNotNull(QVariant_::value), initialLoad = true) } } diff --git a/lib/src/main/java/de/kuschku/libquassel/session/BacklogStorage.kt b/lib/src/main/java/de/kuschku/libquassel/session/BacklogStorage.kt index 52aaa577a..6e4c080ec 100644 --- a/lib/src/main/java/de/kuschku/libquassel/session/BacklogStorage.kt +++ b/lib/src/main/java/de/kuschku/libquassel/session/BacklogStorage.kt @@ -4,7 +4,8 @@ import de.kuschku.libquassel.protocol.BufferId import de.kuschku.libquassel.protocol.Message interface BacklogStorage { - fun storeMessages(vararg messages: Message) + fun storeMessages(vararg messages: Message, initialLoad: Boolean = false) + fun storeMessages(messages: Iterable<Message>, initialLoad: Boolean = false) fun clearMessages(bufferId: BufferId, idRange: IntRange) diff --git a/lib/src/main/java/de/kuschku/libquassel/session/ISession.kt b/lib/src/main/java/de/kuschku/libquassel/session/ISession.kt index 991c3f309..2b2a77740 100644 --- a/lib/src/main/java/de/kuschku/libquassel/session/ISession.kt +++ b/lib/src/main/java/de/kuschku/libquassel/session/ISession.kt @@ -23,6 +23,7 @@ interface ISession : Closeable { val networks: Map<NetworkId, Network> val networkConfig: NetworkConfig? val rpcHandler: RpcHandler? + val initStatus: Observable<Pair<Int, Int>> companion object { val NULL = object : ISession { @@ -39,6 +40,7 @@ interface ISession : Closeable { override val ircListHelper: IrcListHelper? = null override val networks: Map<NetworkId, Network> = emptyMap() override val networkConfig: NetworkConfig? = null + override val initStatus: Observable<Pair<Int, Int>> = Observable.just(0 to 0) override fun close() = Unit override val state = BehaviorSubject.createDefault(ConnectionState.DISCONNECTED) diff --git a/lib/src/main/java/de/kuschku/libquassel/session/ProtocolHandler.kt b/lib/src/main/java/de/kuschku/libquassel/session/ProtocolHandler.kt index 3edbb50b5..07a591686 100644 --- a/lib/src/main/java/de/kuschku/libquassel/session/ProtocolHandler.kt +++ b/lib/src/main/java/de/kuschku/libquassel/session/ProtocolHandler.kt @@ -30,6 +30,8 @@ abstract class ProtocolHandler : SignalProxy, AuthHandler, Closeable { abstract fun onInitDone() + private var totalInitCount = 0 + override fun handle(f: SignalProxyMessage): Boolean { try { if (!super<SignalProxy>.handle(f)) { @@ -67,6 +69,7 @@ abstract class ProtocolHandler : SignalProxy, AuthHandler, Closeable { } private fun checkForInitDone() { + onInitStatusChanged(totalInitCount - toInit.size, totalInitCount) if (isInitializing && toInit.isEmpty()) { isInitializing = false onInitDone() @@ -80,6 +83,8 @@ abstract class ProtocolHandler : SignalProxy, AuthHandler, Closeable { } } + open fun onInitStatusChanged(progress: Int, total: Int) {} + override fun handle(f: SignalProxyMessage.SyncMessage): Boolean { val obj = objectStorage.get(f.className, f.objectName) ?: if (isInitializing) { syncQueue.add(f) @@ -158,6 +163,7 @@ abstract class ProtocolHandler : SignalProxy, AuthHandler, Closeable { if (!syncableObject.initialized) { if (baseInit) { toInit.put(syncableObject, mutableListOf()) + totalInitCount++ } dispatch(SignalProxyMessage.InitRequest(syncableObject.className, syncableObject.objectName)) } diff --git a/lib/src/main/java/de/kuschku/libquassel/session/Session.kt b/lib/src/main/java/de/kuschku/libquassel/session/Session.kt index fbfbc2061..1faddcd31 100644 --- a/lib/src/main/java/de/kuschku/libquassel/session/Session.kt +++ b/lib/src/main/java/de/kuschku/libquassel/session/Session.kt @@ -10,6 +10,7 @@ import de.kuschku.libquassel.util.compatibility.LoggingHandler.LogLevel.DEBUG import de.kuschku.libquassel.util.compatibility.LoggingHandler.LogLevel.INFO import de.kuschku.libquassel.util.compatibility.log import de.kuschku.libquassel.util.hasFlag +import io.reactivex.subjects.BehaviorSubject import org.threeten.bp.Instant import javax.net.ssl.X509TrustManager @@ -41,6 +42,8 @@ class Session( override var rpcHandler: RpcHandler? = RpcHandler(this, backlogStorage) + override val initStatus = BehaviorSubject.createDefault(0 to 0) + init { coreConnection.start() } @@ -93,6 +96,10 @@ class Session( return true } + override fun onInitStatusChanged(progress: Int, total: Int) { + initStatus.onNext(progress to total) + } + override fun onInitDone() { coreConnection.setState(ConnectionState.CONNECTED) log(INFO, "Session", "Initialization finished") diff --git a/lib/src/main/java/de/kuschku/libquassel/session/SessionManager.kt b/lib/src/main/java/de/kuschku/libquassel/session/SessionManager.kt index 545a2da00..4de216bde 100644 --- a/lib/src/main/java/de/kuschku/libquassel/session/SessionManager.kt +++ b/lib/src/main/java/de/kuschku/libquassel/session/SessionManager.kt @@ -107,6 +107,7 @@ class SessionManager(offlineSession: ISession, val backlogStorage: BacklogStorag private var inProgressSession = BehaviorSubject.createDefault(ISession.NULL) private var lastSession: ISession = offlineSession override val state: Observable<ConnectionState> = inProgressSession.switchMap { it.state } + override val initStatus: Observable<Pair<Int, Int>> = inProgressSession.switchMap { it.initStatus } val session: Observable<ISession> = state.map { connectionState -> if (connectionState == ConnectionState.CONNECTED) inProgressSession.value -- GitLab