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 bb383d54da00f5c1d434669061cf84c0d300202d..8d18fac04a80a7e261bb6e989b8f9255e1d24453 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 @@ -77,6 +77,9 @@ class ChatActivity : ServiceBoundActivity(), SharedPreferences.OnSharedPreferenc @BindView(R.id.autocomplete_list2) lateinit var autocompleteList2: RecyclerView + @BindView(R.id.msg_history) + lateinit var msgHistory: RecyclerView + private lateinit var drawerToggle: ActionBarDrawerToggle private val handler = AndroidHandlerThread("Chat") @@ -179,6 +182,20 @@ class ChatActivity : ServiceBoundActivity(), SharedPreferences.OnSharedPreferenc } } + msgHistory.itemAnimator = DefaultItemAnimator() + msgHistory.layoutManager = LinearLayoutManager(this) + msgHistory.adapter = MessageHistoryAdapter( + this, + viewModel.recentlySentMessages, + handler::post, + ::runOnUiThread, + { text -> + chatline.setText(text) + chatline.setSelection(chatline.length()) + historyPanel.panelState = SlidingUpPanelLayout.PanelState.COLLAPSED + } + ) + database = QuasselDatabase.Creator.init(application) setSupportActionBar(toolbar) @@ -336,6 +353,7 @@ class ChatActivity : ServiceBoundActivity(), SharedPreferences.OnSharedPreferenc session.bufferSyncer?.bufferInfo(bufferId)?.also { bufferInfo -> val output = mutableListOf<IAliasManager.Command>() for (line in text.lineSequence()) { + viewModel.addRecentlySentMessage(line) session.aliasManager?.processInput( bufferInfo, inputEditor.formattedString, diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/MessageHistoryAdapter.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/MessageHistoryAdapter.kt new file mode 100644 index 0000000000000000000000000000000000000000..c88ed9581b1761009e0a9502f04f8c0f83541a70 --- /dev/null +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/MessageHistoryAdapter.kt @@ -0,0 +1,83 @@ +package de.kuschku.quasseldroid_ng.ui.chat + +import android.arch.lifecycle.LifecycleOwner +import android.arch.lifecycle.LiveData +import android.arch.lifecycle.Observer +import android.support.v7.util.DiffUtil +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.quasseldroid_ng.R + +class MessageHistoryAdapter( + lifecycleOwner: LifecycleOwner, + liveData: LiveData<List<CharSequence>?>, + runInBackground: (() -> Unit) -> Any, + runOnUiThread: (Runnable) -> Any, + private val clickListener: ((CharSequence) -> Unit)? = null +) : RecyclerView.Adapter<MessageHistoryAdapter.MessageViewHolder>() { + var data = mutableListOf<CharSequence>() + + init { + liveData.observe(lifecycleOwner, Observer { it: List<CharSequence>? -> + runInBackground { + val list = it ?: emptyList() + val old: List<CharSequence> = data + val new: List<CharSequence> = list + val result = DiffUtil.calculateDiff(object : DiffUtil.Callback() { + override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int) = + old[oldItemPosition] == new[newItemPosition] + + override fun getOldListSize() = old.size + override fun getNewListSize() = new.size + override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int) = + old[oldItemPosition] == new[newItemPosition] + }, true) + runOnUiThread(Runnable { + data.clear() + data.addAll(new) + result.dispatchUpdatesTo(this@MessageHistoryAdapter) + }) + } + }) + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = MessageViewHolder( + LayoutInflater.from(parent.context).inflate(R.layout.widget_history_message, parent, false), + clickListener = clickListener + ) + + override fun onBindViewHolder(holder: MessageViewHolder, position: Int) = + holder.bind(data[position]) + + override fun getItemCount() = data.size + + class MessageViewHolder( + itemView: View, + private val clickListener: ((CharSequence) -> Unit)? = null + ) : RecyclerView.ViewHolder(itemView) { + @BindView(R.id.content) + lateinit var content: TextView + + var value: CharSequence? = null + + init { + ButterKnife.bind(this, itemView) + itemView.setOnClickListener { + val value = value + if (value != null) + clickListener?.invoke(value) + } + } + + fun bind(data: CharSequence) { + value = data + + content.text = data + } + } +} \ No newline at end of file diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/viewmodel/QuasselViewModel.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/viewmodel/QuasselViewModel.kt index faf2619f0c116f727e2a5c17c7093d2d562cee71..32baa2206aff31d10ef24c3b8336a9d12c4621f9 100644 --- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/viewmodel/QuasselViewModel.kt +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/viewmodel/QuasselViewModel.kt @@ -43,6 +43,16 @@ class QuasselViewModel : ViewModel() { this.bufferViewConfig.value = bufferViewConfig } + val MAX_RECENT_MESSAGES = 20 + val recentlySentMessages = MutableLiveData<List<CharSequence>>() + fun addRecentlySentMessage(message: CharSequence) { + recentlySentMessages.value = + listOf(message) + + recentlySentMessages.value.orEmpty() + .filter { it == message } + .take(MAX_RECENT_MESSAGES - 1) + } + val backend = backendWrapper.switchMap { it } val sessionManager = backend.map { it.sessionManager() } val session = sessionManager.switchMapRx { it.session } @@ -371,92 +381,92 @@ class QuasselViewModel : ViewModel() { ids.mapNotNull { id -> bufferSyncer.bufferInfo(id) }.filter { - currentConfig.networkId() <= 0 || currentConfig.networkId() == it.networkId - }.filter { - (currentConfig.allowedBufferTypes() and it.type).isNotEmpty() || - it.type.hasFlag(Buffer_Type.StatusBuffer) - }.mapNotNull { - val network = session.networks[it.networkId] - if (network == null) { - null - } else { - it to network + currentConfig.networkId() <= 0 || currentConfig.networkId() == it.networkId + }.filter { + (currentConfig.allowedBufferTypes() and it.type).isNotEmpty() || + it.type.hasFlag(Buffer_Type.StatusBuffer) + }.mapNotNull { + val network = session.networks[it.networkId] + if (network == null) { + null + } else { + it to network + } + }.map<Pair<BufferInfo, Network>, Observable<BufferListAdapter.BufferProps>?> { (info, network) -> + bufferSyncer.liveActivity(info.bufferId).switchMap { activity -> + bufferSyncer.liveHighlightCount(info.bufferId).map { highlights -> + activity to highlights } - }.map<Pair<BufferInfo, Network>, Observable<BufferListAdapter.BufferProps>?> { (info, network) -> - bufferSyncer.liveActivity(info.bufferId).switchMap { activity -> - bufferSyncer.liveHighlightCount(info.bufferId).map { highlights -> - activity to highlights - } - }.switchMap { (activity, highlights) -> - when (info.type.toInt()) { - BufferInfo.Type.QueryBuffer.toInt() -> { - network.liveIrcUser(info.bufferName).switchMap { user -> - user.liveIsAway().switchMap { away -> - user.liveRealName().map { realName -> - BufferListAdapter.BufferProps( - info = info, - network = network.networkInfo(), - bufferStatus = when { - user == IrcUser.NULL -> BufferListAdapter.BufferStatus.OFFLINE - away -> BufferListAdapter.BufferStatus.AWAY - else -> BufferListAdapter.BufferStatus.ONLINE - }, - description = realName, - activity = activity, - highlights = highlights, - hiddenState = state - ) - } - } - } - } - BufferInfo.Type.ChannelBuffer.toInt() -> { - network.liveIrcChannel( - info.bufferName - ).switchMap { channel -> - channel.liveTopic().map { topic -> - BufferListAdapter.BufferProps( - info = info, - network = network.networkInfo(), - bufferStatus = when (channel) { - IrcChannel.NULL -> BufferListAdapter.BufferStatus.OFFLINE - else -> BufferListAdapter.BufferStatus.ONLINE - }, - description = topic, - activity = activity, - highlights = highlights, - hiddenState = state - ) - } - } - } - BufferInfo.Type.StatusBuffer.toInt() -> { - network.liveConnectionState.map { + }.switchMap { (activity, highlights) -> + when (info.type.toInt()) { + BufferInfo.Type.QueryBuffer.toInt() -> { + network.liveIrcUser(info.bufferName).switchMap { user -> + user.liveIsAway().switchMap { away -> + user.liveRealName().map { realName -> BufferListAdapter.BufferProps( info = info, network = network.networkInfo(), - bufferStatus = BufferListAdapter.BufferStatus.OFFLINE, - description = "", + bufferStatus = when { + user == IrcUser.NULL -> BufferListAdapter.BufferStatus.OFFLINE + away -> BufferListAdapter.BufferStatus.AWAY + else -> BufferListAdapter.BufferStatus.ONLINE + }, + description = realName, activity = activity, highlights = highlights, hiddenState = state ) } } - else -> Observable.just( + } + } + BufferInfo.Type.ChannelBuffer.toInt() -> { + network.liveIrcChannel( + info.bufferName + ).switchMap { channel -> + channel.liveTopic().map { topic -> BufferListAdapter.BufferProps( info = info, network = network.networkInfo(), - bufferStatus = BufferListAdapter.BufferStatus.OFFLINE, - description = "", + bufferStatus = when (channel) { + IrcChannel.NULL -> BufferListAdapter.BufferStatus.OFFLINE + else -> BufferListAdapter.BufferStatus.ONLINE + }, + description = topic, activity = activity, highlights = highlights, hiddenState = state ) + } + } + } + BufferInfo.Type.StatusBuffer.toInt() -> { + network.liveConnectionState.map { + BufferListAdapter.BufferProps( + info = info, + network = network.networkInfo(), + bufferStatus = BufferListAdapter.BufferStatus.OFFLINE, + description = "", + activity = activity, + highlights = highlights, + hiddenState = state ) } } + else -> Observable.just( + BufferListAdapter.BufferProps( + info = info, + network = network.networkInfo(), + bufferStatus = BufferListAdapter.BufferStatus.OFFLINE, + description = "", + activity = activity, + highlights = highlights, + hiddenState = state + ) + ) + } } + } bufferSyncer.liveBufferInfos().switchMap { val buffers = if (showHidden) { @@ -490,5 +500,6 @@ class QuasselViewModel : ViewModel() { showHidden.postValue(false) selectedBufferId.postValue(-1) collapsedNetworks.value = emptySet() + recentlySentMessages.value = emptyList() } } diff --git a/app/src/main/res/layout/widget_history_message.xml b/app/src/main/res/layout/widget_history_message.xml new file mode 100644 index 0000000000000000000000000000000000000000..c0df0098cea2206c6fed236a8b54461b1c28d211 --- /dev/null +++ b/app/src/main/res/layout/widget_history_message.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<TextView xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/content" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:background="?attr/backgroundMenuItem" + android:fontFamily="sans-serif-medium" + android:gravity="center_vertical" + android:minHeight="48dp" + android:paddingBottom="8dp" + android:paddingLeft="16dp" + android:paddingRight="16dp" + android:paddingTop="8dp" + android:singleLine="true" + android:textColor="?attr/colorTextPrimary" + android:textSize="13sp" + tools:text="Historical Message" /> \ No newline at end of file