From 22fd71a3eb05b353628c68129cac99f3267e6cf9 Mon Sep 17 00:00:00 2001
From: Janne Koschinski <janne@kuschku.de>
Date: Wed, 6 Dec 2017 04:51:42 +0100
Subject: [PATCH] Implemented rudimentary chatting

---
 app/build.gradle.kts                          |   2 +-
 .../kuschku/quasseldroid_ng/QuasseldroidNG.kt |   7 +-
 .../persistence/QuasselDatabase.kt            |  11 +-
 .../quasseldroid_ng/service/QuasselService.kt |   8 +-
 .../ui/chat/BufferListAdapter.kt              |  21 ++-
 .../ui/chat/BufferViewConfigFragment.kt       |  13 +-
 .../quasseldroid_ng/ui/chat/ChatActivity.kt   | 123 ++++++++++--------
 .../ui/chat/MessageListFragment.kt            |  80 +++++++-----
 app/src/main/res/layout/activity_main.xml     |  40 +++++-
 app/src/main/res/layout/content_main.xml      |  36 -----
 app/src/main/res/layout/content_messages.xml  |   8 ++
 app/src/main/res/menu/main.xml                |  10 +-
 app/src/main/res/values/strings.xml           |   2 +
 .../quassel/syncables/BacklogManager.kt       |   4 +
 .../de/kuschku/libquassel/session/Backend.kt  |   2 +-
 .../de/kuschku/libquassel/session/ISession.kt |   2 +
 .../de/kuschku/libquassel/session/Session.kt  |   2 +
 .../libquassel/session/SessionManager.kt      |   9 +-
 .../util/compatibility/LoggingHandler.kt      |   2 +-
 19 files changed, 230 insertions(+), 152 deletions(-)
 delete mode 100644 app/src/main/res/layout/content_main.xml
 create mode 100644 app/src/main/res/layout/content_messages.xml

diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 44ca3080c..bce3a7a92 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -34,7 +34,7 @@ android {
     minSdkVersion(16)
     targetSdkVersion(26)
 
-    applicationId = "de.kuschku.quasseldroid_ng.test"
+    applicationId = "de.kuschku.quasseldroid_ng"
     versionCode = 1
     versionName = cmd("git", "describe", "--tags", "HEAD") ?: "1.0.0"
 
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/QuasseldroidNG.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/QuasseldroidNG.kt
index 8eac65731..f03aecc1f 100644
--- a/app/src/main/java/de/kuschku/quasseldroid_ng/QuasseldroidNG.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/QuasseldroidNG.kt
@@ -1,7 +1,6 @@
 package de.kuschku.quasseldroid_ng
 
 import android.app.Application
-import android.content.Context
 import android.content.pm.ShortcutInfo
 import android.content.pm.ShortcutManager
 import android.graphics.drawable.Icon
@@ -13,11 +12,9 @@ import de.kuschku.quasseldroid_ng.util.compatibility.AndroidStreamChannelFactory
 import de.kuschku.quasseldroid_ng.util.helper.systemService
 
 class QuasseldroidNG : Application() {
-  override fun attachBaseContext(base: Context?) {
-    super.attachBaseContext(base)
-  }
-
   override fun onCreate() {
+    println("QuasseldroidNG::onCreate")
+
     CrashHandler.init(
       application = this,
       buildConfig = BuildConfig::class.java
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 bcf7feb38..89d9c7a8e 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
@@ -42,16 +42,19 @@ abstract class QuasselDatabase : RoomDatabase() {
   @Dao
   interface MessageDao {
     @Query("SELECT * FROM message WHERE messageId = :messageId")
-    fun find(messageId: Int): DatabaseMessage
+    fun find(messageId: Int): DatabaseMessage?
 
-    @Query("SELECT * FROM message WHERE bufferId = :bufferId")
+    @Query("SELECT * FROM message WHERE bufferId = :bufferId ORDER BY messageId ASC")
     fun findByBufferId(bufferId: Int): List<DatabaseMessage>
 
-    @Query("SELECT * FROM message WHERE bufferId = :bufferId AND messageId < :messageId")
+    @Query("SELECT * FROM message WHERE bufferId = :bufferId ORDER BY messageId ASC")
     fun findByBufferIdPaged(bufferId: Int): LivePagedListProvider<Int, DatabaseMessage>
 
     @Query("SELECT * FROM message WHERE bufferId = :bufferId ORDER BY messageId DESC LIMIT 1")
-    fun findLastByBufferId(bufferId: Int): DatabaseMessage
+    fun findLastByBufferId(bufferId: Int): DatabaseMessage?
+
+    @Query("SELECT * FROM message WHERE bufferId = :bufferId ORDER BY messageId ASC LIMIT 1")
+    fun findFirstByBufferId(bufferId: Int): DatabaseMessage?
 
     @Insert(onConflict = OnConflictStrategy.REPLACE)
     fun save(vararg entities: DatabaseMessage)
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/service/QuasselService.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/service/QuasselService.kt
index 76a57ee88..b1394e95c 100644
--- a/app/src/main/java/de/kuschku/quasseldroid_ng/service/QuasselService.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/service/QuasselService.kt
@@ -53,8 +53,8 @@ class QuasselService : LifecycleService() {
       sessionManager.reconnect()
     }
 
-    override fun disconnect() {
-      sessionManager.disconnect()
+    override fun disconnect(forever: Boolean) {
+      sessionManager.disconnect(forever)
     }
   }
 
@@ -80,9 +80,9 @@ class QuasselService : LifecycleService() {
       }
     }
 
-    override fun disconnect() {
+    override fun disconnect(forever: Boolean) {
       handler.post {
-        backendImplementation.disconnect()
+        backendImplementation.disconnect(forever)
       }
     }
 
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/BufferListAdapter.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/BufferListAdapter.kt
index f11f3f84c..4eec89285 100644
--- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/BufferListAdapter.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/BufferListAdapter.kt
@@ -11,6 +11,7 @@ import android.view.ViewGroup
 import android.widget.TextView
 import butterknife.BindView
 import butterknife.ButterKnife
+import de.kuschku.libquassel.protocol.BufferId
 import de.kuschku.libquassel.quassel.BufferInfo
 import de.kuschku.libquassel.util.hasFlag
 
@@ -18,7 +19,8 @@ class BufferListAdapter(
   lifecycleOwner: LifecycleOwner,
   liveData: LiveData<List<BufferInfo>?>,
   runInBackground: (() -> Unit) -> Any,
-  runOnUiThread: (Runnable) -> Any
+  runOnUiThread: (Runnable) -> Any,
+  private val clickListener: ((BufferId) -> Unit)? = null
 ) : RecyclerView.Adapter<BufferListAdapter.BufferViewHolder>() {
   var data = mutableListOf<BufferInfo>()
 
@@ -48,8 +50,8 @@ class BufferListAdapter(
   }
 
   override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = BufferViewHolder(
-    LayoutInflater.from(parent.context)
-      .inflate(android.R.layout.simple_list_item_1, parent, false)
+    LayoutInflater.from(parent.context).inflate(android.R.layout.simple_list_item_1, parent, false),
+    clickListener = clickListener
   )
 
   override fun onBindViewHolder(holder: BufferViewHolder, position: Int)
@@ -57,12 +59,22 @@ class BufferListAdapter(
 
   override fun getItemCount() = data.size
 
-  class BufferViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
+  class BufferViewHolder(
+    itemView: View,
+    private val clickListener: ((BufferId) -> Unit)? = null
+  ) : RecyclerView.ViewHolder(itemView) {
     @BindView(android.R.id.text1)
     lateinit var text: TextView
 
+    var bufferId: BufferId? = null
+
     init {
       ButterKnife.bind(this, itemView)
+      itemView.setOnClickListener {
+        val buffer = bufferId
+        if (buffer != null)
+          clickListener?.invoke(buffer)
+      }
     }
 
     fun bind(info: BufferInfo) {
@@ -70,6 +82,7 @@ class BufferListAdapter(
         info.type.hasFlag(BufferInfo.Type.StatusBuffer) -> "Network ${info.networkId}"
         else -> "${info.networkId}/${info.bufferName}"
       }
+      bufferId = info.bufferId
     }
   }
 }
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 d689fa5a8..bcb8e99ed 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
@@ -38,7 +38,7 @@ class BufferViewConfigFragment : ServiceBoundFragment() {
   @BindView(R.id.chatList)
   lateinit var chatList: RecyclerView
 
-  var currentBuffer: MutableLiveData<BufferId>? = null
+  val currentBuffer: MutableLiveData<LiveData<BufferId?>?> = MutableLiveData()
 
   private val sessionManager: LiveData<SessionManager?>
     = backend.map(Backend::sessionManager)
@@ -92,8 +92,7 @@ class BufferViewConfigFragment : ServiceBoundFragment() {
     chatListSpinner.adapter = adapter
     chatListSpinner.onItemSelectedListener = itemSelectedListener
 
-    chatList.adapter = BufferListAdapter(this, bufferList, handlerThread::post,
-                                         activity::runOnUiThread)
+    chatList.adapter = BufferListAdapter(this, bufferList, handlerThread::post, activity::runOnUiThread, clickListener)
     chatList.layoutManager = LinearLayoutManager(context)
     chatList.itemAnimator = DefaultItemAnimator()
     return view
@@ -103,4 +102,12 @@ class BufferViewConfigFragment : ServiceBoundFragment() {
     handlerThread.onDestroy()
     super.onDestroy()
   }
+
+  val clickListeners = mutableListOf<(BufferId) -> Unit>()
+
+  private val clickListener: ((BufferId) -> Unit)? = {
+    for (clickListener in clickListeners) {
+      clickListener.invoke(it)
+    }
+  }
 }
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 8767ac897..e51792e05 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
@@ -2,47 +2,54 @@ package de.kuschku.quasseldroid_ng.ui.chat
 
 import android.app.Activity
 import android.arch.lifecycle.LiveData
+import android.arch.lifecycle.MutableLiveData
 import android.arch.lifecycle.Observer
 import android.content.Context
 import android.os.Bundle
 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.util.Log
 import android.view.Menu
 import android.view.MenuItem
 import android.widget.Button
-import android.widget.TextView
+import android.widget.EditText
 import butterknife.BindView
 import butterknife.ButterKnife
+import de.kuschku.libquassel.protocol.BufferId
 import de.kuschku.libquassel.session.Backend
 import de.kuschku.libquassel.session.ConnectionState
 import de.kuschku.libquassel.session.SessionManager
 import de.kuschku.libquassel.session.SocketAddress
-import de.kuschku.libquassel.util.compatibility.LoggingHandler
-import de.kuschku.libquassel.util.compatibility.LoggingHandler.LogLevel.INFO
 import de.kuschku.quasseldroid_ng.Keys
 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.service.ServiceBoundActivity
-import org.threeten.bp.ZoneOffset
-import org.threeten.bp.ZonedDateTime
-import org.threeten.bp.format.DateTimeFormatter
 
 class ChatActivity : ServiceBoundActivity() {
-  @BindView(R.id.clear)
-  lateinit var clear: Button
+  var contentMessages: MessageListFragment? = null
+  var chatListFragment: BufferViewConfigFragment? = null
 
-  @BindView(R.id.errorList)
-  lateinit var errorList: TextView
+  @BindView(R.id.drawerLayout)
+  lateinit var drawerLayout: DrawerLayout
 
   @BindView(R.id.toolbar)
   lateinit var toolbar: Toolbar
 
+  @BindView(R.id.buttonSend)
+  lateinit var buttonSend: Button
+
+  @BindView(R.id.input)
+  lateinit var input: EditText
+
+  private lateinit var drawerToggle: ActionBarDrawerToggle
+
   private val handler = AndroidHandlerThread("Chat")
 
   private val sessionManager: LiveData<SessionManager?> = backend.map(Backend::sessionManager)
@@ -50,45 +57,36 @@ class ChatActivity : ServiceBoundActivity() {
 
   private var snackbar: Snackbar? = null
 
-  private val dateTimeFormatter: DateTimeFormatter = DateTimeFormatter.ISO_TIME
-  private val logHandler = object : LoggingHandler() {
-    override fun log(logLevel: LogLevel, tag: String, message: String?,
-                     throwable: Throwable?) {
-      val time = dateTimeFormatter.format(ZonedDateTime.now(ZoneOffset.UTC))
-      runOnUiThread {
-        errorList.append("$time $tag: ")
-        if (message != null) {
-          errorList.append(message)
-        }
-        if (throwable != null) {
-          errorList.append("\n")
-          errorList.append(Log.getStackTraceString(throwable))
-        }
-        errorList.append("\n")
-      }
-    }
-
-    override fun isLoggable(logLevel: LogLevel, tag: String)
-      = (logLevel.ordinal >= INFO.ordinal)
-  }
-
-  override fun onRestoreInstanceState(savedInstanceState: Bundle?) {
-    super.onRestoreInstanceState(savedInstanceState)
-    errorList.text = savedInstanceState?.getString("log", "") ?: ""
-  }
+  private val currentBuffer = MutableLiveData<BufferId>()
 
-  override fun onSaveInstanceState(outState: Bundle?) {
-    outState?.putString("log", errorList.text.toString())
-    super.onSaveInstanceState(outState)
-  }
+  private lateinit var database: QuasselDatabase
 
   override fun onCreate(savedInstanceState: Bundle?) {
+    println("ChatActivity::onCreate")
     handler.onCreate()
     super.onCreate(savedInstanceState)
     setContentView(R.layout.activity_main)
     ButterKnife.bind(this)
+
+    database = QuasselDatabase.Creator.init(application)
+
+    contentMessages = supportFragmentManager.findFragmentById(R.id.contentMessages) as? MessageListFragment
+    chatListFragment = supportFragmentManager.findFragmentById(R.id.chatListFragment) as? BufferViewConfigFragment
+
     setSupportActionBar(toolbar)
 
+    chatListFragment?.currentBuffer?.value = currentBuffer
+    contentMessages?.currentBuffer?.value = currentBuffer
+
+    chatListFragment?.clickListeners?.add {
+      currentBuffer.value = it
+      println("Changed buffer to $it")
+    }
+
+    //drawerToggle = ActionBarDrawerToggle(this, drawerLayout, R.string.drawer_open, R.string.drawer_close)
+    //actionBar.setDisplayHomeAsUpEnabled(true)
+    //actionBar.setHomeButtonEnabled(true)
+
     backend.observeSticky(this, Observer { backendValue ->
       if (backendValue != null) {
         val database = AccountDatabase.Creator.init(this)
@@ -115,15 +113,22 @@ class ChatActivity : ServiceBoundActivity() {
       }
     })
 
-    clear.setOnClickListener {
-      errorList.text = ""
+    buttonSend.setOnClickListener {
+      sessionManager.value?.also { sessionManager ->
+        currentBuffer.value?.also { bufferId ->
+          sessionManager.bufferSyncer?.bufferInfo(bufferId)?.also { bufferInfo ->
+            sessionManager.rpcHandler?.sendInput(bufferInfo, input.text.toString())
+          }
+        }
+      }
+      input.text.clear()
     }
 
     state.observe(this, Observer {
       val status = it ?: ConnectionState.DISCONNECTED
 
       snackbar?.dismiss()
-      snackbar = Snackbar.make(errorList, status.name, Snackbar.LENGTH_SHORT)
+      snackbar = Snackbar.make(window.decorView.rootView, status.name, Snackbar.LENGTH_SHORT)
       snackbar?.show()
     })
   }
@@ -139,27 +144,35 @@ class ChatActivity : ServiceBoundActivity() {
         getSharedPreferences(Keys.Status.NAME, Context.MODE_PRIVATE).editApply {
           putBoolean(Keys.Status.reconnect, false)
         }
-        backend.value?.disconnect()
+        backend.value?.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)
   }
 
-  override fun onStart() {
-    super.onStart()
-    LoggingHandler.loggingHandlers.add(logHandler)
-  }
-
   override fun onDestroy() {
     handler.onDestroy()
     super.onDestroy()
   }
-
-  override fun onStop() {
-    LoggingHandler.loggingHandlers.remove(logHandler)
-    super.onStop()
-  }
 }
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 9a1d83d1e..3e98bb186 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
@@ -7,67 +7,85 @@ import android.arch.paging.PagedList
 import android.arch.paging.PagedListAdapter
 import android.os.Bundle
 import android.support.v7.recyclerview.extensions.DiffCallback
+import android.support.v7.widget.DefaultItemAnimator
+import android.support.v7.widget.LinearLayoutManager
 import android.support.v7.widget.RecyclerView
 import android.view.LayoutInflater
 import android.view.View
 import android.view.ViewGroup
 import android.widget.TextView
+import butterknife.BindView
+import butterknife.ButterKnife
 import de.kuschku.libquassel.protocol.BufferId
+import de.kuschku.quasseldroid_ng.R
 import de.kuschku.quasseldroid_ng.persistence.QuasselDatabase
 import de.kuschku.quasseldroid_ng.util.helper.switchMap
 import de.kuschku.quasseldroid_ng.util.service.ServiceBoundFragment
 
 class MessageListFragment : ServiceBoundFragment() {
-  var currentBuffer: LiveData<BufferId?> = MutableLiveData()
+  val currentBuffer: MutableLiveData<LiveData<BufferId?>?> = MutableLiveData()
 
   private lateinit var database: QuasselDatabase
 
   private val adapter = MessageAdapter()
 
-  override fun onCreate(savedInstanceState: Bundle?) {
-    super.onCreate(savedInstanceState)
+  @BindView(R.id.messageList)
+  lateinit var messageList: RecyclerView
+
+  override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
+    val view = inflater.inflate(R.layout.content_messages, container, false)
+    ButterKnife.bind(this, view)
 
     database = QuasselDatabase.Creator.init(context.applicationContext)
     val data = currentBuffer.switchMap {
-      database.message().findByBufferIdPaged(it).create(Int.MAX_VALUE,
-        PagedList.Config.Builder()
-          .setPageSize(20)
-          .setEnablePlaceholders(true)
-          .setPrefetchDistance(20)
-          .build()
-      )
+      it.switchMap {
+        database.message().findByBufferIdPaged(it).create(Int.MAX_VALUE,
+          PagedList.Config.Builder()
+            .setPageSize(20)
+            .setEnablePlaceholders(true)
+            .setPrefetchDistance(20)
+            .build()
+        )
+      }
     }
+
     data.observe(this, Observer { list ->
       adapter.setList(list)
     })
-  }
 
-  class MessageAdapter : PagedListAdapter<QuasselDatabase.DatabaseMessage, MessageViewHolder>(MessageDiffCallback) {
-    override fun onBindViewHolder(holder: MessageViewHolder?, position: Int) {
-      holder?.bind(getItem(position))
-    }
+    messageList.adapter = adapter
+    messageList.layoutManager = LinearLayoutManager(context)
+    messageList.itemAnimator = DefaultItemAnimator()
 
-    override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): MessageViewHolder {
-      return MessageViewHolder(LayoutInflater.from(parent?.context).inflate(android.R.layout.simple_list_item_1, parent, false))
-    }
+    return view
   }
+}
 
-  object MessageDiffCallback : DiffCallback<QuasselDatabase.DatabaseMessage>() {
-    override fun areContentsTheSame(oldItem: QuasselDatabase.DatabaseMessage, newItem: QuasselDatabase.DatabaseMessage)
-      = oldItem == newItem
+class MessageAdapter : PagedListAdapter<QuasselDatabase.DatabaseMessage, MessageViewHolder>(MessageDiffCallback) {
+  override fun onBindViewHolder(holder: MessageViewHolder?, position: Int) {
+    holder?.bind(getItem(position))
+  }
 
-    override fun areItemsTheSame(oldItem: QuasselDatabase.DatabaseMessage, newItem: QuasselDatabase.DatabaseMessage)
-      = oldItem.messageId == newItem.messageId
+  override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): MessageViewHolder {
+    return MessageViewHolder(LayoutInflater.from(parent?.context).inflate(android.R.layout.simple_list_item_1, parent, false))
   }
+}
 
-  class MessageViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
-    fun bind(message: QuasselDatabase.DatabaseMessage?) {
-      val text = (itemView as TextView)
-      if (message == null) {
-        text.text = "null"
-      } else {
-        text.text = "[${message.time}] <${message.senderPrefixes}${message.sender}> ${message.content}"
-      }
+object MessageDiffCallback : DiffCallback<QuasselDatabase.DatabaseMessage>() {
+  override fun areContentsTheSame(oldItem: QuasselDatabase.DatabaseMessage, newItem: QuasselDatabase.DatabaseMessage)
+    = oldItem == newItem
+
+  override fun areItemsTheSame(oldItem: QuasselDatabase.DatabaseMessage, newItem: QuasselDatabase.DatabaseMessage)
+    = oldItem.messageId == newItem.messageId
+}
+
+class MessageViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
+  fun bind(message: QuasselDatabase.DatabaseMessage?) {
+    val text = (itemView as TextView)
+    if (message == null) {
+      text.text = "null"
+    } else {
+      text.text = "[${message.time}] <${message.senderPrefixes}${message.sender}> ${message.content}"
     }
   }
 }
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
index b5f90adae..2f9777bab 100644
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_main.xml
@@ -1,6 +1,7 @@
 <android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:app="http://schemas.android.com/apk/res-auto"
-  android:id="@+id/drawer_layout"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:id="@+id/drawerLayout"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   android:fitsSystemWindows="true">
@@ -83,7 +84,39 @@
 
     </android.support.design.widget.AppBarLayout>
 
-    <include layout="@layout/content_main" />
+    <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.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">
+
+        <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>
+
+    </android.support.v7.widget.CardView>
 
   </LinearLayout>
 
@@ -101,6 +134,7 @@
       android:id="@+id/chatListFragment"
       android:name="de.kuschku.quasseldroid_ng.ui.chat.BufferViewConfigFragment"
       android:layout_width="match_parent"
-      android:layout_height="match_parent" />
+        android:layout_height="match_parent"
+        tools:layout="@layout/content_chat_list" />
   </de.kuschku.quasseldroid_ng.util.ui.NavigationDrawerLayout>
 </android.support.v4.widget.DrawerLayout>
diff --git a/app/src/main/res/layout/content_main.xml b/app/src/main/res/layout/content_main.xml
deleted file mode 100644
index 4c0a9bf4f..000000000
--- a/app/src/main/res/layout/content_main.xml
+++ /dev/null
@@ -1,36 +0,0 @@
-<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
-  xmlns:tools="http://schemas.android.com/tools"
-  android:layout_width="match_parent"
-  android:layout_height="match_parent"
-  android:background="?attr/colorBackground"
-  tools:showIn="@layout/activity_main">
-
-  <LinearLayout
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:layout_marginBottom="8dp"
-    android:layout_marginTop="8dp"
-    android:orientation="vertical"
-    android:paddingLeft="24dp"
-    android:paddingRight="24dp">
-
-    <LinearLayout
-      android:layout_width="match_parent"
-      android:layout_height="wrap_content">
-
-      <Button
-        android:id="@+id/clear"
-        style="@style/Widget.Button"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:minWidth="88dp"
-        android:text="Clear" />
-    </LinearLayout>
-
-    <TextView
-      android:id="@+id/errorList"
-      android:layout_width="match_parent"
-      android:layout_height="wrap_content"
-      android:textIsSelectable="true" />
-  </LinearLayout>
-</ScrollView>
diff --git a/app/src/main/res/layout/content_messages.xml b/app/src/main/res/layout/content_messages.xml
new file mode 100644
index 000000000..cce02ed3b
--- /dev/null
+++ b/app/src/main/res/layout/content_messages.xml
@@ -0,0 +1,8 @@
+<?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/menu/main.xml b/app/src/main/res/menu/main.xml
index 2079987d4..1f23bf3b4 100644
--- a/app/src/main/res/menu/main.xml
+++ b/app/src/main/res/menu/main.xml
@@ -1,5 +1,13 @@
 <?xml version="1.0" encoding="utf-8"?>
-<menu xmlns:android="http://schemas.android.com/apk/res/android">
+<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" />
   <item
     android:id="@+id/settings"
     android:title="Settings" />
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 5748b2baf..8e98b5f95 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -5,4 +5,6 @@
   <string name="connection_service_description">Keeps a connection to your core to allow for notifications, and to transmit messages.</string>
 
   <string name="crash_text">QD-NG has crashed</string>
+    <string name="drawer_open">Open</string>
+    <string name="drawer_close">Close</string>
 </resources>
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 598f70e39..7d8b5c902 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
@@ -11,6 +11,10 @@ class BacklogManager(
   proxy: SignalProxy,
   private val backlogStorage: BacklogStorage
 ) : SyncableObject(proxy, "BacklogManager"), IBacklogManager {
+  init {
+    initialized = true
+  }
+
   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)) {
diff --git a/lib/src/main/java/de/kuschku/libquassel/session/Backend.kt b/lib/src/main/java/de/kuschku/libquassel/session/Backend.kt
index ff51c4acb..529c0210a 100644
--- a/lib/src/main/java/de/kuschku/libquassel/session/Backend.kt
+++ b/lib/src/main/java/de/kuschku/libquassel/session/Backend.kt
@@ -4,6 +4,6 @@ interface Backend {
   fun connectUnlessConnected(address: SocketAddress, user: String, pass: String, reconnect: Boolean)
   fun connect(address: SocketAddress, user: String, pass: String, reconnect: Boolean)
   fun reconnect()
-  fun disconnect()
+  fun disconnect(forever: Boolean = false)
   fun sessionManager(): SessionManager
 }
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 b5f4c0990..991c3f309 100644
--- a/lib/src/main/java/de/kuschku/libquassel/session/ISession.kt
+++ b/lib/src/main/java/de/kuschku/libquassel/session/ISession.kt
@@ -22,9 +22,11 @@ interface ISession : Closeable {
   val ircListHelper: IrcListHelper?
   val networks: Map<NetworkId, Network>
   val networkConfig: NetworkConfig?
+  val rpcHandler: RpcHandler?
 
   companion object {
     val NULL = object : ISession {
+      override val rpcHandler: RpcHandler? = null
       override val aliasManager: AliasManager? = null
       override val backlogManager: BacklogManager? = null
       override val bufferSyncer: BufferSyncer? = null
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 14edd30e5..fbfbc2061 100644
--- a/lib/src/main/java/de/kuschku/libquassel/session/Session.kt
+++ b/lib/src/main/java/de/kuschku/libquassel/session/Session.kt
@@ -88,6 +88,8 @@ class Session(
     synchronize(ircListHelper, true)
     synchronize(networkConfig, true)
 
+    synchronize(backlogManager)
+
     return true
   }
 
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 0385cf9c6..545a2da00 100644
--- a/lib/src/main/java/de/kuschku/libquassel/session/SessionManager.kt
+++ b/lib/src/main/java/de/kuschku/libquassel/session/SessionManager.kt
@@ -13,7 +13,7 @@ import io.reactivex.Observable
 import io.reactivex.subjects.BehaviorSubject
 import javax.net.ssl.X509TrustManager
 
-class SessionManager(offlineSession: ISession, private val backlogStorage: BacklogStorage) : ISession {
+class SessionManager(offlineSession: ISession, val backlogStorage: BacklogStorage) : ISession {
   override val aliasManager: AliasManager?
     get() = session.or(lastSession).aliasManager
   override val backlogManager: BacklogManager?
@@ -38,6 +38,8 @@ class SessionManager(offlineSession: ISession, private val backlogStorage: Backl
     get() = session.or(lastSession).networks
   override val networkConfig: NetworkConfig?
     get() = session.or(lastSession).networkConfig
+  override val rpcHandler: RpcHandler?
+    get() = session.or(lastSession).rpcHandler
 
   override fun close() = session.or(lastSession).close()
 
@@ -95,8 +97,9 @@ class SessionManager(offlineSession: ISession, private val backlogStorage: Backl
     }
   }
 
-  fun disconnect() {
-    inProgressSession.value
+  fun disconnect(forever: Boolean) {
+    if (forever)
+      backlogStorage.clearMessages()
     inProgressSession.value.close()
     inProgressSession.onNext(ISession.NULL)
   }
diff --git a/lib/src/main/java/de/kuschku/libquassel/util/compatibility/LoggingHandler.kt b/lib/src/main/java/de/kuschku/libquassel/util/compatibility/LoggingHandler.kt
index 261b46ea0..73aa925a3 100644
--- a/lib/src/main/java/de/kuschku/libquassel/util/compatibility/LoggingHandler.kt
+++ b/lib/src/main/java/de/kuschku/libquassel/util/compatibility/LoggingHandler.kt
@@ -11,7 +11,7 @@ abstract class LoggingHandler {
     if (isLoggable(logLevel, tag)) {
       object : LogContext {
         override fun log(message: String?, throwable: Throwable?) {
-          log(logLevel, tag, message, throwable)
+          this@LoggingHandler.log(logLevel, tag, message, throwable)
         }
       }.f()
     }
-- 
GitLab