From 1ed6ed5ec2407e80a87a6204bd55955be0cb8b56 Mon Sep 17 00:00:00 2001 From: Janne Koschinski <janne@kuschku.de> Date: Tue, 1 May 2018 02:12:39 +0200 Subject: [PATCH] Early implementation of notification handling Signed-off-by: Janne Koschinski <janne@kuschku.de> --- .../service/QuasselNotificationBackend.kt | 173 ++++++++++++++ .../quasseldroid/service/QuasselService.kt | 91 +++++-- .../service/QuasselServiceModule.kt | 5 + .../chat/info/channel/ChannelInfoFragment.kt | 2 +- .../ui/chat/info/user/UserInfoFragment.kt | 2 +- .../ui/chat/input/EditorHelper.kt | 15 +- .../ui/chat/input/RichEditText.kt | 56 +++-- .../ui/chat/messages/DisplayMessage.kt | 2 +- .../ui/chat/messages/MessageAdapter.kt | 20 +- .../ui/chat/messages/MessageListFragment.kt | 4 +- .../ui/chat/messages/MessageRenderer.kt | 2 +- .../chat/messages/QuasselMessageRenderer.kt | 173 +++++--------- .../kuschku/quasseldroid/util/AvatarHelper.kt | 13 +- .../quasseldroid/util/NotificationMessage.kt | 32 +++ .../util/QuasseldroidNotificationManager.kt | 130 ++++++++-- .../util/irc/format/ContentFormatter.kt | 73 +++++- .../util/irc/format/IrcFormatDeserializer.kt | 60 +++-- .../util/irc/format/IrcFormatSerializer.kt | 59 +++-- app/src/main/res/values/attrs.xml | 101 -------- app/src/main/res/values/colors.xml | 131 ++++++++++ app/src/main/res/values/dimens.xml | 3 + app/src/main/res/values/themes_base.xml | 226 ------------------ .../quasseldroid/util/AvatarHelperTest.kt | 6 +- .../quassel/syncables/BufferSyncer.kt | 12 +- .../quassel/syncables/RpcHandler.kt | 5 +- .../libquassel/session/NotificationManager.kt | 30 +++ .../de/kuschku/libquassel/session/Session.kt | 9 +- .../libquassel/session/SessionManager.kt | 2 + .../persistence/QuasselBacklogStorage.kt | 11 +- .../persistence/QuasselDatabase.kt | 119 ++++++--- 30 files changed, 925 insertions(+), 642 deletions(-) create mode 100644 app/src/main/java/de/kuschku/quasseldroid/service/QuasselNotificationBackend.kt create mode 100644 app/src/main/java/de/kuschku/quasseldroid/util/NotificationMessage.kt create mode 100644 lib/src/main/java/de/kuschku/libquassel/session/NotificationManager.kt diff --git a/app/src/main/java/de/kuschku/quasseldroid/service/QuasselNotificationBackend.kt b/app/src/main/java/de/kuschku/quasseldroid/service/QuasselNotificationBackend.kt new file mode 100644 index 000000000..07459942d --- /dev/null +++ b/app/src/main/java/de/kuschku/quasseldroid/service/QuasselNotificationBackend.kt @@ -0,0 +1,173 @@ +/* + * Quasseldroid - Quassel client for Android + * + * Copyright (c) 2018 Janne Koschinski + * Copyright (c) 2018 The Quassel Project + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3 as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package de.kuschku.quasseldroid.service + +import android.content.Context +import android.text.SpannableStringBuilder +import de.kuschku.libquassel.protocol.* +import de.kuschku.libquassel.quassel.BufferInfo +import de.kuschku.libquassel.quassel.syncables.IgnoreListManager +import de.kuschku.libquassel.session.NotificationManager +import de.kuschku.libquassel.session.Session +import de.kuschku.libquassel.util.IrcUserUtils +import de.kuschku.libquassel.util.flag.hasFlag +import de.kuschku.libquassel.util.irc.HostmaskHelper +import de.kuschku.quasseldroid.GlideApp +import de.kuschku.quasseldroid.R +import de.kuschku.quasseldroid.persistence.QuasselDatabase +import de.kuschku.quasseldroid.settings.MessageSettings +import de.kuschku.quasseldroid.util.AvatarHelper +import de.kuschku.quasseldroid.util.NotificationMessage +import de.kuschku.quasseldroid.util.QuasseldroidNotificationManager +import de.kuschku.quasseldroid.util.helper.getColorCompat +import de.kuschku.quasseldroid.util.helper.loadWithFallbacks +import de.kuschku.quasseldroid.util.irc.format.ContentFormatter +import de.kuschku.quasseldroid.util.ui.TextDrawable +import javax.inject.Inject + +class QuasselNotificationBackend @Inject constructor( + private val context: Context, + private val database: QuasselDatabase, + private val messageSettings: MessageSettings, + private val contentFormatter: ContentFormatter, + private val notificationHandler: QuasseldroidNotificationManager +) : NotificationManager { + private val senderColors = listOf( + R.color.senderColor0, R.color.senderColor1, R.color.senderColor2, R.color.senderColor3, + R.color.senderColor4, R.color.senderColor5, R.color.senderColor6, R.color.senderColor7, + R.color.senderColor8, R.color.senderColor9, R.color.senderColorA, R.color.senderColorB, + R.color.senderColorC, R.color.senderColorD, R.color.senderColorE, R.color.senderColorF + ).map(context::getColorCompat).toIntArray() + + + private val selfColor = context.getColorCompat(android.R.color.background_dark) + + override fun init(session: Session) { + // TODO + } + + @Synchronized + override fun processMessages(session: Session, vararg messages: Message) { + val results = messages.filter { + it.flag.hasFlag(Message_Flag.Highlight) || + it.bufferInfo.type.hasFlag(Buffer_Type.QueryBuffer) + }.filter { + val bufferName = it.bufferInfo.bufferName ?: "" + val networkId = it.bufferInfo.networkId + val networkName = session.network(networkId)?.networkName() ?: "" + + session.ignoreListManager.match( + it.content, it.sender, it.type, networkName, bufferName + ) == IgnoreListManager.StrictnessType.UnmatchedStrictness + }.filter { + it.type.hasFlag(Message_Type.Plain) || + it.type.hasFlag(Message_Type.Notice) || + it.type.hasFlag(Message_Type.Action) + }.filter { + !it.flag.hasFlag(Message_Flag.Self) + }.map { + QuasselDatabase.NotificationData( + messageId = it.messageId, + time = it.time, + type = it.type, + flag = it.flag, + bufferId = it.bufferInfo.bufferId, + bufferName = it.bufferInfo.bufferName ?: "", + bufferType = it.bufferInfo.type, + networkId = it.bufferInfo.networkId, + sender = it.sender, + senderPrefixes = it.senderPrefixes, + realName = it.realName, + avatarUrl = it.avatarUrl, + content = it.content + ) + } + database.notifications().save(*results.toTypedArray()) + results.map(QuasselDatabase.NotificationData::bufferId).distinct().forEach(this::showNotification) + } + + @Synchronized + private fun showNotification(buffer: BufferId) { + val data = database.notifications().all(buffer) + data.lastOrNull()?.let { + val bufferInfo = BufferInfo( + bufferId = it.bufferId, + bufferName = it.bufferName, + type = it.bufferType, + networkId = it.networkId, + groupId = 0 + ) + val notification = notificationHandler.notificationGroup(bufferInfo, data.map { + val nick = SpannableStringBuilder().apply { + append(contentFormatter.formatPrefix(it.senderPrefixes)) + append(contentFormatter.formatNick( + it.sender, + senderColors = senderColors + )) + } + val content = contentFormatter.formatContent(it.content, false) + + val nickName = HostmaskHelper.nick(it.sender) + val senderColorIndex = IrcUserUtils.senderColor(nickName) + val rawInitial = nickName.trimStart('-', + '_', + '[', + ']', + '{', + '}', + '|', + '`', + '^', + '.', + '\\') + .firstOrNull() ?: nickName.firstOrNull() + val initial = rawInitial?.toUpperCase().toString() + val senderColor = if (it.flag.hasFlag(Message_Flag.Self)) + selfColor + else + senderColors[senderColorIndex] + + val size = context.resources.getDimensionPixelSize(R.dimen.notification_avatar_width) + val avatarList = AvatarHelper.avatar(messageSettings, it) + val avatarResult = GlideApp.with(context).loadWithFallbacks(avatarList) + ?.optionalCircleCrop() + ?.placeholder(TextDrawable.builder().buildRound(initial, senderColor)) + ?.submit(size, size) + ?.get() + val avatar = avatarResult ?: TextDrawable.builder().buildRound(initial, senderColor) + + NotificationMessage( + messageId = it.messageId, + sender = nick, + content = content, + time = it.time, + avatar = avatar + ) + }) + notificationHandler.notify(notification) + } ?: notificationHandler.remove(buffer) + } + + @Synchronized + override fun clear(buffer: BufferId, lastRead: MsgId) { + database.notifications().markRead(buffer, lastRead) + showNotification(buffer) + } +} diff --git a/app/src/main/java/de/kuschku/quasseldroid/service/QuasselService.kt b/app/src/main/java/de/kuschku/quasseldroid/service/QuasselService.kt index 9143fa3bf..1f18744fd 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/service/QuasselService.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/service/QuasselService.kt @@ -25,17 +25,18 @@ package de.kuschku.quasseldroid.service import android.arch.lifecycle.Observer import android.content.* import android.net.ConnectivityManager +import android.support.v4.app.RemoteInput +import android.text.SpannableString import de.kuschku.libquassel.connection.ConnectionState import de.kuschku.libquassel.connection.HostnameVerifier import de.kuschku.libquassel.connection.SocketAddress -import de.kuschku.libquassel.protocol.ClientData -import de.kuschku.libquassel.protocol.Protocol -import de.kuschku.libquassel.protocol.Protocol_Feature -import de.kuschku.libquassel.protocol.Protocol_Features +import de.kuschku.libquassel.protocol.* import de.kuschku.libquassel.quassel.QuasselFeatures +import de.kuschku.libquassel.quassel.syncables.interfaces.IAliasManager import de.kuschku.libquassel.session.Backend import de.kuschku.libquassel.session.ISession import de.kuschku.libquassel.session.SessionManager +import de.kuschku.libquassel.util.helpers.value import de.kuschku.malheur.CrashHandler import de.kuschku.quasseldroid.BuildConfig import de.kuschku.quasseldroid.Keys @@ -44,6 +45,7 @@ import de.kuschku.quasseldroid.persistence.AccountDatabase import de.kuschku.quasseldroid.persistence.QuasselBacklogStorage import de.kuschku.quasseldroid.persistence.QuasselDatabase import de.kuschku.quasseldroid.settings.ConnectionSettings +import de.kuschku.quasseldroid.settings.MessageSettings import de.kuschku.quasseldroid.settings.Settings import de.kuschku.quasseldroid.ssl.QuasselHostnameVerifier import de.kuschku.quasseldroid.ssl.QuasselTrustManager @@ -52,10 +54,9 @@ import de.kuschku.quasseldroid.ssl.custom.QuasselHostnameManager import de.kuschku.quasseldroid.util.QuasseldroidNotificationManager import de.kuschku.quasseldroid.util.backport.DaggerLifecycleService import de.kuschku.quasseldroid.util.compatibility.AndroidHandlerService -import de.kuschku.quasseldroid.util.helper.editApply -import de.kuschku.quasseldroid.util.helper.editCommit -import de.kuschku.quasseldroid.util.helper.sharedPreferences -import de.kuschku.quasseldroid.util.helper.toLiveData +import de.kuschku.quasseldroid.util.helper.* +import de.kuschku.quasseldroid.util.irc.format.ContentFormatter +import de.kuschku.quasseldroid.util.irc.format.IrcFormatSerializer import io.reactivex.subjects.PublishSubject import org.threeten.bp.Instant import java.util.concurrent.TimeUnit @@ -96,7 +97,15 @@ class QuasselService : DaggerLifecycleService(), private var accountId: Long = -1 private var reconnect: Boolean = false - private lateinit var notificationManager: QuasseldroidNotificationManager + @Inject + lateinit var notificationManager: QuasseldroidNotificationManager + + @Inject + lateinit var notificationBackend: QuasselNotificationBackend + + @Inject + lateinit var ircFormatSerializer: IrcFormatSerializer + private var notificationHandle: QuasseldroidNotificationManager.Handle? = null private var progress = Triple(ConnectionState.DISCONNECTED, 0, 0) @@ -114,18 +123,46 @@ class QuasselService : DaggerLifecycleService(), override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { val result = super.onStartCommand(intent, flags, startId) - handleIntent(intent) + intent?.let(this@QuasselService::handleIntent) return result } - private fun handleIntent(intent: Intent?) { - if (intent?.getBooleanExtra("disconnect", false) == true) { + private fun handleIntent(intent: Intent) { + if (intent.getBooleanExtra("disconnect", false)) { sharedPreferences(Keys.Status.NAME, Context.MODE_PRIVATE) { editApply { putBoolean(Keys.Status.reconnect, false) } } } + + val bufferId = intent.getIntExtra("buffer", -1) + + val inputResults = RemoteInput.getResultsFromIntent(intent)?.getCharSequence("reply_content") + if (inputResults != null && bufferId != -1) { + if (inputResults.isNotBlank()) { + val lines = inputResults.lineSequence().map { + it.toString() to ircFormatSerializer.toEscapeCodes(SpannableString(it)) + } + + sessionManager.session.value?.let { session -> + session.bufferSyncer?.bufferInfo(bufferId)?.also { bufferInfo -> + val output = mutableListOf<IAliasManager.Command>() + for ((_, formatted) in lines) { + session.aliasManager?.processInput(bufferInfo, formatted, output) + } + for (command in output) { + session.rpcHandler?.sendInput(command.buffer, command.message) + } + } + } + } + } + + val clearMessageId = intent.getIntExtra("mark_read_message", -1) + if (bufferId != -1 && clearMessageId != -1) { + sessionManager.session.value?.bufferSyncer?.requestSetLastSeenMsg(bufferId, clearMessageId) + } } private fun updateNotification(handle: QuasseldroidNotificationManager.Handle, @@ -236,7 +273,13 @@ class QuasselService : DaggerLifecycleService(), @Inject lateinit var accountDatabase: AccountDatabase - private val receiver = object : BroadcastReceiver() { + @Inject + lateinit var contentFormatter: ContentFormatter + + @Inject + lateinit var messageSettings: MessageSettings + + private val connectivityReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context?, intent: Intent?) { if (context != null && intent != null) { connectivity.onNext(Unit) @@ -261,6 +304,7 @@ class QuasselService : DaggerLifecycleService(), sessionManager = SessionManager( ISession.NULL, QuasselBacklogStorage(database), + notificationBackend, handlerService, ::disconnectFromCore, CrashHandler::handle @@ -328,9 +372,8 @@ class QuasselService : DaggerLifecycleService(), registerOnSharedPreferenceChangeListener(this@QuasselService) } - registerReceiver(receiver, IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)) + registerReceiver(connectivityReceiver, IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)) - notificationManager = QuasseldroidNotificationManager(this) notificationManager.init() update() @@ -345,7 +388,7 @@ class QuasselService : DaggerLifecycleService(), unregisterOnSharedPreferenceChangeListener(this@QuasselService) } - unregisterReceiver(receiver) + unregisterReceiver(connectivityReceiver) notificationHandle?.let { notificationManager.remove(it) } super.onDestroy() @@ -359,16 +402,26 @@ class QuasselService : DaggerLifecycleService(), companion object { fun launch( context: Context, - disconnect: Boolean? = null - ): ComponentName = context.startService(intent(context, disconnect)) + disconnect: Boolean? = null, + markRead: BufferId? = null, + markReadMessage: MsgId? = null + ): ComponentName = context.startService(intent(context, disconnect, markRead, markReadMessage)) fun intent( context: Context, - disconnect: Boolean? = null + disconnect: Boolean? = null, + bufferId: BufferId? = null, + markReadMessage: MsgId? = null ) = Intent(context, QuasselService::class.java).apply { if (disconnect != null) { putExtra("disconnect", disconnect) } + if (bufferId != null) { + putExtra("buffer", bufferId) + } + if (markReadMessage != null) { + putExtra("mark_read_message", markReadMessage) + } } } } diff --git a/app/src/main/java/de/kuschku/quasseldroid/service/QuasselServiceModule.kt b/app/src/main/java/de/kuschku/quasseldroid/service/QuasselServiceModule.kt index ba91d912d..180848023 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/service/QuasselServiceModule.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/service/QuasselServiceModule.kt @@ -25,9 +25,14 @@ package de.kuschku.quasseldroid.service import android.content.Context import dagger.Binds import dagger.Module +import de.kuschku.quasseldroid.ui.chat.messages.MessageRenderer +import de.kuschku.quasseldroid.ui.chat.messages.QuasselMessageRenderer @Module abstract class QuasselServiceModule { @Binds abstract fun bindContext(service: QuasselService): Context + + @Binds + abstract fun bindMessageRenderer(messageRenderer: QuasselMessageRenderer): MessageRenderer } diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/info/channel/ChannelInfoFragment.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/info/channel/ChannelInfoFragment.kt index 027161385..efa92b8ac 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/info/channel/ChannelInfoFragment.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/info/channel/ChannelInfoFragment.kt @@ -92,7 +92,7 @@ class ChannelInfoFragment : ServiceBoundFragment() { }.switchMap(IrcChannel::updates).toLiveData().observe(this, Observer { channel -> if (channel != null) { name.text = channel.name() - topic.text = contentFormatter.format(channel.topic()) + topic.text = contentFormatter.formatContent(channel.topic()) actionEditTopic.setOnClickListener { TopicActivity.launch(requireContext(), buffer = arguments?.getInt("bufferId") ?: -1) diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/info/user/UserInfoFragment.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/info/user/UserInfoFragment.kt index fff72805b..ba471e87f 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/info/user/UserInfoFragment.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/info/user/UserInfoFragment.kt @@ -156,7 +156,7 @@ class UserInfoFragment : ServiceBoundFragment() { ) nick.text = user.nick() - realName.text = contentFormatter.format(user.realName()) + realName.text = contentFormatter.formatContent(user.realName()) realName.visibleIf(user.realName().isNotBlank() && user.realName() != user.nick()) awayMessage.text = user.awayMessage() diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/input/EditorHelper.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/input/EditorHelper.kt index f7c8e3aee..ef858f3bb 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/input/EditorHelper.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/input/EditorHelper.kt @@ -32,6 +32,7 @@ import android.view.inputmethod.EditorInfo import de.kuschku.quasseldroid.R import de.kuschku.quasseldroid.settings.AppearanceSettings import de.kuschku.quasseldroid.settings.AutoCompleteSettings +import de.kuschku.quasseldroid.util.helper.getColorCompat import de.kuschku.quasseldroid.util.helper.lastWordIndices import de.kuschku.quasseldroid.util.helper.styledAttributes import de.kuschku.quasseldroid.util.ui.ColorChooserDialog @@ -47,14 +48,12 @@ class EditorHelper( ) { private var enterListener: (() -> Unit)? = null - private val mircColors = editText.context.theme.styledAttributes( - R.attr.mircColor00, R.attr.mircColor01, R.attr.mircColor02, R.attr.mircColor03, - R.attr.mircColor04, R.attr.mircColor05, R.attr.mircColor06, R.attr.mircColor07, - R.attr.mircColor08, R.attr.mircColor09, R.attr.mircColor10, R.attr.mircColor11, - R.attr.mircColor12, R.attr.mircColor13, R.attr.mircColor14, R.attr.mircColor15 - ) { - IntArray(length(), { getColor(it, 0) }) - } + private val mircColors = listOf( + R.color.mircColor00, R.color.mircColor01, R.color.mircColor02, R.color.mircColor03, + R.color.mircColor04, R.color.mircColor05, R.color.mircColor06, R.color.mircColor07, + R.color.mircColor08, R.color.mircColor09, R.color.mircColor10, R.color.mircColor11, + R.color.mircColor12, R.color.mircColor13, R.color.mircColor14, R.color.mircColor15 + ).map(activity::getColorCompat).toIntArray() private val defaultForegroundColor = editText.context.theme.styledAttributes(R.attr.colorForeground) { getColor(0, 0) diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/input/RichEditText.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/input/RichEditText.kt index 4f6cb6569..a0b16801b 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/input/RichEditText.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/input/RichEditText.kt @@ -36,35 +36,33 @@ import de.kuschku.quasseldroid.util.ui.DoubleClickHelper import de.kuschku.quasseldroid.util.ui.EditTextSelectionChange class RichEditText : EditTextSelectionChange { - private val mircColors = this.context.theme.styledAttributes( - R.attr.mircColor00, R.attr.mircColor01, R.attr.mircColor02, R.attr.mircColor03, - R.attr.mircColor04, R.attr.mircColor05, R.attr.mircColor06, R.attr.mircColor07, - R.attr.mircColor08, R.attr.mircColor09, R.attr.mircColor10, R.attr.mircColor11, - R.attr.mircColor12, R.attr.mircColor13, R.attr.mircColor14, R.attr.mircColor15, - R.attr.mircColor16, R.attr.mircColor17, R.attr.mircColor18, R.attr.mircColor19, - R.attr.mircColor20, R.attr.mircColor21, R.attr.mircColor22, R.attr.mircColor23, - R.attr.mircColor24, R.attr.mircColor25, R.attr.mircColor26, R.attr.mircColor27, - R.attr.mircColor28, R.attr.mircColor29, R.attr.mircColor30, R.attr.mircColor31, - R.attr.mircColor32, R.attr.mircColor33, R.attr.mircColor34, R.attr.mircColor35, - R.attr.mircColor36, R.attr.mircColor37, R.attr.mircColor38, R.attr.mircColor39, - R.attr.mircColor40, R.attr.mircColor41, R.attr.mircColor42, R.attr.mircColor43, - R.attr.mircColor44, R.attr.mircColor45, R.attr.mircColor46, R.attr.mircColor47, - R.attr.mircColor48, R.attr.mircColor49, R.attr.mircColor50, R.attr.mircColor51, - R.attr.mircColor52, R.attr.mircColor53, R.attr.mircColor54, R.attr.mircColor55, - R.attr.mircColor56, R.attr.mircColor57, R.attr.mircColor58, R.attr.mircColor59, - R.attr.mircColor60, R.attr.mircColor61, R.attr.mircColor62, R.attr.mircColor63, - R.attr.mircColor64, R.attr.mircColor65, R.attr.mircColor66, R.attr.mircColor67, - R.attr.mircColor68, R.attr.mircColor69, R.attr.mircColor70, R.attr.mircColor71, - R.attr.mircColor72, R.attr.mircColor73, R.attr.mircColor74, R.attr.mircColor75, - R.attr.mircColor76, R.attr.mircColor77, R.attr.mircColor78, R.attr.mircColor79, - R.attr.mircColor80, R.attr.mircColor81, R.attr.mircColor82, R.attr.mircColor83, - R.attr.mircColor84, R.attr.mircColor85, R.attr.mircColor86, R.attr.mircColor87, - R.attr.mircColor88, R.attr.mircColor89, R.attr.mircColor90, R.attr.mircColor91, - R.attr.mircColor92, R.attr.mircColor93, R.attr.mircColor94, R.attr.mircColor95, - R.attr.mircColor96, R.attr.mircColor97, R.attr.mircColor98 - ) { - IntArray(length(), { getColor(it, 0) }) - } + val mircColors = listOf( + R.color.mircColor00, R.color.mircColor01, R.color.mircColor02, R.color.mircColor03, + R.color.mircColor04, R.color.mircColor05, R.color.mircColor06, R.color.mircColor07, + R.color.mircColor08, R.color.mircColor09, R.color.mircColor10, R.color.mircColor11, + R.color.mircColor12, R.color.mircColor13, R.color.mircColor14, R.color.mircColor15, + R.color.mircColor16, R.color.mircColor17, R.color.mircColor18, R.color.mircColor19, + R.color.mircColor20, R.color.mircColor21, R.color.mircColor22, R.color.mircColor23, + R.color.mircColor24, R.color.mircColor25, R.color.mircColor26, R.color.mircColor27, + R.color.mircColor28, R.color.mircColor29, R.color.mircColor30, R.color.mircColor31, + R.color.mircColor32, R.color.mircColor33, R.color.mircColor34, R.color.mircColor35, + R.color.mircColor36, R.color.mircColor37, R.color.mircColor38, R.color.mircColor39, + R.color.mircColor40, R.color.mircColor41, R.color.mircColor42, R.color.mircColor43, + R.color.mircColor44, R.color.mircColor45, R.color.mircColor46, R.color.mircColor47, + R.color.mircColor48, R.color.mircColor49, R.color.mircColor50, R.color.mircColor51, + R.color.mircColor52, R.color.mircColor53, R.color.mircColor54, R.color.mircColor55, + R.color.mircColor56, R.color.mircColor57, R.color.mircColor58, R.color.mircColor59, + R.color.mircColor60, R.color.mircColor61, R.color.mircColor62, R.color.mircColor63, + R.color.mircColor64, R.color.mircColor65, R.color.mircColor66, R.color.mircColor67, + R.color.mircColor68, R.color.mircColor69, R.color.mircColor70, R.color.mircColor71, + R.color.mircColor72, R.color.mircColor73, R.color.mircColor74, R.color.mircColor75, + R.color.mircColor76, R.color.mircColor77, R.color.mircColor78, R.color.mircColor79, + R.color.mircColor80, R.color.mircColor81, R.color.mircColor82, R.color.mircColor83, + R.color.mircColor84, R.color.mircColor85, R.color.mircColor86, R.color.mircColor87, + R.color.mircColor88, R.color.mircColor89, R.color.mircColor90, R.color.mircColor91, + R.color.mircColor92, R.color.mircColor93, R.color.mircColor94, R.color.mircColor95, + R.color.mircColor96, R.color.mircColor97, R.color.mircColor98 + ).map(context::getColorCompat).toIntArray() private val mircColorMap = mircColors.withIndex().map { (key, value) -> key to value }.toMap() private var formattingListener: ((Boolean, Boolean, Boolean, Boolean, Boolean, Int?, Int?) -> Unit)? = null diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/DisplayMessage.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/DisplayMessage.kt index 82ecdbd85..7f500dba8 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/DisplayMessage.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/DisplayMessage.kt @@ -26,7 +26,7 @@ import de.kuschku.libquassel.protocol.MsgId import de.kuschku.quasseldroid.persistence.QuasselDatabase data class DisplayMessage( - val content: QuasselDatabase.DatabaseMessage, + val content: QuasselDatabase.MessageData, val hasDayChange: Boolean, val isFollowUp: Boolean, val isSelected: Boolean, diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/MessageAdapter.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/MessageAdapter.kt index a1b33bdb4..c631e7e90 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/MessageAdapter.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/MessageAdapter.kt @@ -58,8 +58,8 @@ class MessageAdapter @Inject constructor( private val movementMethod = BetterLinkMovementMethod.newInstance() private var clickListener: ((FormattedMessage) -> Unit)? = null private var longClickListener: ((FormattedMessage) -> Unit)? = null - private var doubleClickListener: ((QuasselDatabase.DatabaseMessage) -> Unit)? = null - private var expansionListener: ((QuasselDatabase.DatabaseMessage) -> Unit)? = null + private var doubleClickListener: ((QuasselDatabase.MessageData) -> Unit)? = null + private var expansionListener: ((QuasselDatabase.MessageData) -> Unit)? = null private var urlLongClickListener: ((TextView, String) -> Boolean)? = null fun setOnClickListener(listener: ((FormattedMessage) -> Unit)?) { @@ -70,11 +70,11 @@ class MessageAdapter @Inject constructor( this.longClickListener = listener } - fun setOnDoubleClickListener(listener: ((QuasselDatabase.DatabaseMessage) -> Unit)?) { + fun setOnDoubleClickListener(listener: ((QuasselDatabase.MessageData) -> Unit)?) { this.doubleClickListener = listener } - fun setOnExpansionListener(listener: ((QuasselDatabase.DatabaseMessage) -> Unit)?) { + fun setOnExpansionListener(listener: ((QuasselDatabase.MessageData) -> Unit)?) { this.expansionListener = listener } @@ -107,8 +107,8 @@ class MessageAdapter @Inject constructor( } override fun getItemViewType(position: Int) = getItem(position)?.let { - Message_Flag.of(it.content.type).value or - (if (Message_Flag.of(it.content.flag).hasFlag(Message_Flag.Highlight)) MASK_HIGHLIGHT else 0x00) or + it.content.type.value or + (if (it.content.flag.hasFlag(Message_Flag.Highlight)) MASK_HIGHLIGHT else 0x00) or (if (it.isFollowUp) MASK_FOLLOWUP else 0x00) or (if (it.isEmoji) MASK_EMOJI else 0x00) } ?: 0 @@ -167,8 +167,8 @@ class MessageAdapter @Inject constructor( itemView: View, clickListener: ((FormattedMessage) -> Unit)? = null, longClickListener: ((FormattedMessage) -> Unit)? = null, - doubleClickListener: ((QuasselDatabase.DatabaseMessage) -> Unit)? = null, - expansionListener: ((QuasselDatabase.DatabaseMessage) -> Unit)? = null, + doubleClickListener: ((QuasselDatabase.MessageData) -> Unit)? = null, + expansionListener: ((QuasselDatabase.MessageData) -> Unit)? = null, movementMethod: BetterLinkMovementMethod ) : RecyclerView.ViewHolder(itemView) { @BindView(R.id.time_left) @@ -200,7 +200,7 @@ class MessageAdapter @Inject constructor( var combined: TextView? = null private var message: FormattedMessage? = null - private var original: QuasselDatabase.DatabaseMessage? = null + private var original: QuasselDatabase.MessageData? = null private var selectable: Boolean = false private var clickable: Boolean = false @@ -241,7 +241,7 @@ class MessageAdapter @Inject constructor( }) } - fun bind(message: FormattedMessage, original: QuasselDatabase.DatabaseMessage, + fun bind(message: FormattedMessage, original: QuasselDatabase.MessageData, selectable: Boolean = true, clickable: Boolean = true) { this.message = message this.original = original diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/MessageListFragment.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/MessageListFragment.kt index d32b5ec04..2f45fac3a 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/MessageListFragment.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/MessageListFragment.kt @@ -266,9 +266,9 @@ class MessageListFragment : ServiceBoundFragment() { } }) - fun processMessages(list: List<QuasselDatabase.DatabaseMessage>, selected: Set<MsgId>, + fun processMessages(list: List<QuasselDatabase.MessageData>, selected: Set<MsgId>, expanded: Set<MsgId>, markerLine: MsgId?): List<DisplayMessage> { - var previous: QuasselDatabase.DatabaseMessage? = null + var previous: QuasselDatabase.MessageData? = null var previousDate: ZonedDateTime? = null return list.mapReverse { val date = it.time.atZone(ZoneId.systemDefault()).truncatedTo(ChronoUnit.DAYS) diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/MessageRenderer.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/MessageRenderer.kt index 5c04e7070..75d87e028 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/MessageRenderer.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/MessageRenderer.kt @@ -33,7 +33,7 @@ interface MessageRenderer { fun layout(type: Message_Type?, hasHighlight: Boolean, isFollowUp: Boolean, isEmoji: Boolean): Int fun bind(holder: MessageAdapter.QuasselMessageViewHolder, message: FormattedMessage, - original: QuasselDatabase.DatabaseMessage) + original: QuasselDatabase.MessageData) fun render(context: Context, message: DisplayMessage): FormattedMessage diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/QuasselMessageRenderer.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/QuasselMessageRenderer.kt index 6e749e16f..aa6db697e 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/QuasselMessageRenderer.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/QuasselMessageRenderer.kt @@ -27,10 +27,7 @@ import android.graphics.Typeface import android.graphics.drawable.ColorDrawable import android.graphics.drawable.LayerDrawable import android.os.Build -import android.text.SpannableString import android.text.SpannableStringBuilder -import android.text.style.ForegroundColorSpan -import android.text.style.StyleSpan import android.util.TypedValue import android.view.View import android.widget.FrameLayout @@ -44,8 +41,6 @@ import de.kuschku.libquassel.util.irc.HostmaskHelper import de.kuschku.quasseldroid.R import de.kuschku.quasseldroid.persistence.QuasselDatabase import de.kuschku.quasseldroid.settings.MessageSettings -import de.kuschku.quasseldroid.settings.MessageSettings.ColorizeNicknamesMode -import de.kuschku.quasseldroid.settings.MessageSettings.ShowPrefixMode import de.kuschku.quasseldroid.util.AvatarHelper import de.kuschku.quasseldroid.util.helper.styledAttributes import de.kuschku.quasseldroid.util.helper.visibleIf @@ -190,8 +185,8 @@ class QuasselMessageRenderer @Inject constructor( } override fun bind(holder: MessageAdapter.QuasselMessageViewHolder, message: FormattedMessage, - original: QuasselDatabase.DatabaseMessage) = - Message_Type.of(original.type).hasFlag(DayChange).let { isDayChange -> + original: QuasselDatabase.MessageData) = + original.type.hasFlag(DayChange).let { isDayChange -> holder.bind(message, original, !isDayChange, !isDayChange) } @@ -202,27 +197,27 @@ class QuasselMessageRenderer @Inject constructor( context.resources.displayMetrics ).roundToInt() - val self = Message_Flag.of(message.content.flag).hasFlag(Message_Flag.Self) - val highlight = Message_Flag.of(message.content.flag).hasFlag(Message_Flag.Highlight) - return when (Message_Type.of(message.content.type).enabledValues().firstOrNull()) { + val self = message.content.flag.hasFlag(Message_Flag.Self) + val highlight = message.content.flag.hasFlag(Message_Flag.Highlight) + return when (message.content.type.enabledValues().firstOrNull()) { Message_Type.Plain -> { - val realName = contentFormatter.format(message.content.realName, highlight) + val realName = contentFormatter.formatContent(message.content.realName, highlight) val nick = SpannableStringBuilder().apply { - append(formatPrefix(message.content.senderPrefixes, highlight)) - append(formatNick( + append(contentFormatter.formatPrefix(message.content.senderPrefixes)) + append(contentFormatter.formatNick( message.content.sender, self, highlight, messageSettings.showHostmaskPlain && messageSettings.nicksOnNewLine )) } - val content = contentFormatter.format(message.content.content, highlight) + val content = contentFormatter.formatContent(message.content.content, highlight) val nickName = HostmaskHelper.nick(message.content.sender) val senderColorIndex = IrcUserUtils.senderColor(nickName) val rawInitial = nickName.trimStart('-', '_', '[', ']', '{', '}', '|', '`', '^', '.', '\\') .firstOrNull() ?: nickName.firstOrNull() val initial = rawInitial?.toUpperCase().toString() - val senderColor = if (Message_Flag.of(message.content.flag).hasFlag(Message_Flag.Self)) + val senderColor = if (message.content.flag.hasFlag(Message_Flag.Self)) selfColor else senderColors[senderColorIndex] @@ -250,9 +245,9 @@ class QuasselMessageRenderer @Inject constructor( time = timeFormatter.format(message.content.time.atZone(zoneId)), combined = SpanFormatter.format( context.getString(R.string.message_format_action), - formatPrefix(message.content.senderPrefixes, highlight), - formatNick(message.content.sender, self, highlight, false), - contentFormatter.format(message.content.content, highlight) + contentFormatter.formatPrefix(message.content.senderPrefixes), + contentFormatter.formatNick(message.content.sender, self, highlight, false), + contentFormatter.formatContent(message.content.content, highlight) ), isMarkerLine = message.isMarkerLine, isExpanded = message.isExpanded, @@ -263,9 +258,9 @@ class QuasselMessageRenderer @Inject constructor( time = timeFormatter.format(message.content.time.atZone(zoneId)), combined = SpanFormatter.format( context.getString(R.string.message_format_notice), - formatPrefix(message.content.senderPrefixes, highlight), - formatNick(message.content.sender, self, highlight, false), - contentFormatter.format(message.content.content, highlight) + contentFormatter.formatPrefix(message.content.senderPrefixes), + contentFormatter.formatNick(message.content.sender, self, highlight, false), + contentFormatter.formatContent(message.content.content, highlight) ), isMarkerLine = message.isMarkerLine, isExpanded = message.isExpanded, @@ -279,16 +274,16 @@ class QuasselMessageRenderer @Inject constructor( combined = if (nickSelf) { SpanFormatter.format( context.getString(R.string.message_format_nick_self), - formatPrefix(message.content.senderPrefixes, highlight), - formatNick(message.content.sender, nickSelf, highlight, false) + contentFormatter.formatPrefix(message.content.senderPrefixes), + contentFormatter.formatNick(message.content.sender, nickSelf, highlight, false) ) } else { SpanFormatter.format( context.getString(R.string.message_format_nick), - formatPrefix(message.content.senderPrefixes, highlight), - formatNick(message.content.sender, nickSelf, highlight, false), - formatPrefix(message.content.senderPrefixes, highlight), - formatNick(message.content.content, nickSelf, highlight, false) + contentFormatter.formatPrefix(message.content.senderPrefixes), + contentFormatter.formatNick(message.content.sender, nickSelf, highlight, false), + contentFormatter.formatPrefix(message.content.senderPrefixes), + contentFormatter.formatNick(message.content.content, nickSelf, highlight, false) ) }, isMarkerLine = message.isMarkerLine, @@ -302,8 +297,8 @@ class QuasselMessageRenderer @Inject constructor( combined = SpanFormatter.format( context.getString(R.string.message_format_mode), message.content.content, - formatPrefix(message.content.senderPrefixes, highlight), - formatNick(message.content.sender, self, highlight, false) + contentFormatter.formatPrefix(message.content.senderPrefixes), + contentFormatter.formatNick(message.content.sender, self, highlight, false) ), isMarkerLine = message.isMarkerLine, isExpanded = message.isExpanded, @@ -314,8 +309,11 @@ class QuasselMessageRenderer @Inject constructor( time = timeFormatter.format(message.content.time.atZone(zoneId)), combined = SpanFormatter.format( context.getString(R.string.message_format_join), - formatPrefix(message.content.senderPrefixes, highlight), - formatNick(message.content.sender, self, highlight, messageSettings.showHostmaskActions), + contentFormatter.formatPrefix(message.content.senderPrefixes), + contentFormatter.formatNick(message.content.sender, + self, + highlight, + messageSettings.showHostmaskActions), message.content.content ), isMarkerLine = message.isMarkerLine, @@ -328,18 +326,21 @@ class QuasselMessageRenderer @Inject constructor( combined = if (message.content.content.isBlank()) { SpanFormatter.format( context.getString(R.string.message_format_part_1), - formatPrefix(message.content.senderPrefixes, highlight), - formatNick(message.content.sender, self, highlight, messageSettings.showHostmaskActions) + contentFormatter.formatPrefix(message.content.senderPrefixes), + contentFormatter.formatNick(message.content.sender, + self, + highlight, + messageSettings.showHostmaskActions) ) } else { SpanFormatter.format( context.getString(R.string.message_format_part_2), - formatPrefix(message.content.senderPrefixes, highlight), - formatNick(message.content.sender, + contentFormatter.formatPrefix(message.content.senderPrefixes), + contentFormatter.formatNick(message.content.sender, self, highlight, messageSettings.showHostmaskActions), - contentFormatter.format(message.content.content, highlight) + contentFormatter.formatContent(message.content.content, highlight) ) }, isMarkerLine = message.isMarkerLine, @@ -352,18 +353,21 @@ class QuasselMessageRenderer @Inject constructor( combined = if (message.content.content.isBlank()) { SpanFormatter.format( context.getString(R.string.message_format_quit_1), - formatPrefix(message.content.senderPrefixes, highlight), - formatNick(message.content.sender, self, highlight, messageSettings.showHostmaskActions) + contentFormatter.formatPrefix(message.content.senderPrefixes), + contentFormatter.formatNick(message.content.sender, + self, + highlight, + messageSettings.showHostmaskActions) ) } else { SpanFormatter.format( context.getString(R.string.message_format_quit_2), - formatPrefix(message.content.senderPrefixes, highlight), - formatNick(message.content.sender, + contentFormatter.formatPrefix(message.content.senderPrefixes), + contentFormatter.formatNick(message.content.sender, self, highlight, messageSettings.showHostmaskActions), - contentFormatter.format(message.content.content, highlight) + contentFormatter.formatContent(message.content.content, highlight) ) }, isMarkerLine = message.isMarkerLine, @@ -378,9 +382,9 @@ class QuasselMessageRenderer @Inject constructor( combined = if (reason.isBlank()) { SpanFormatter.format( context.getString(R.string.message_format_kick_1), - formatNick(user, false, highlight, false), - formatPrefix(message.content.senderPrefixes, highlight), - formatNick(message.content.sender, + contentFormatter.formatNick(user, false, highlight, false), + contentFormatter.formatPrefix(message.content.senderPrefixes), + contentFormatter.formatNick(message.content.sender, self, highlight, messageSettings.showHostmaskActions) @@ -388,13 +392,13 @@ class QuasselMessageRenderer @Inject constructor( } else { SpanFormatter.format( context.getString(R.string.message_format_kick_2), - formatNick(user, false, highlight, false), - formatPrefix(message.content.senderPrefixes, highlight), - formatNick(message.content.sender, + contentFormatter.formatNick(user, false, highlight, false), + contentFormatter.formatPrefix(message.content.senderPrefixes), + contentFormatter.formatNick(message.content.sender, self, highlight, messageSettings.showHostmaskActions), - contentFormatter.format(reason, highlight) + contentFormatter.formatContent(reason, highlight) ) }, isMarkerLine = message.isMarkerLine, @@ -410,9 +414,9 @@ class QuasselMessageRenderer @Inject constructor( combined = if (reason.isBlank()) { SpanFormatter.format( context.getString(R.string.message_format_kill_1), - formatNick(user, false, highlight, false), - formatPrefix(message.content.senderPrefixes, highlight), - formatNick(message.content.sender, + contentFormatter.formatNick(user, false, highlight, false), + contentFormatter.formatPrefix(message.content.senderPrefixes), + contentFormatter.formatNick(message.content.sender, self, highlight, messageSettings.showHostmaskActions) @@ -420,13 +424,13 @@ class QuasselMessageRenderer @Inject constructor( } else { SpanFormatter.format( context.getString(R.string.message_format_kill_2), - formatNick(user, false, highlight, false), - formatPrefix(message.content.senderPrefixes, highlight), - formatNick(message.content.sender, + contentFormatter.formatNick(user, false, highlight, false), + contentFormatter.formatPrefix(message.content.senderPrefixes), + contentFormatter.formatNick(message.content.sender, self, highlight, messageSettings.showHostmaskActions), - contentFormatter.format(reason, highlight) + contentFormatter.formatContent(reason, highlight) ) }, isMarkerLine = message.isMarkerLine, @@ -471,7 +475,7 @@ class QuasselMessageRenderer @Inject constructor( Message_Type.Error -> FormattedMessage( id = message.content.messageId, time = timeFormatter.format(message.content.time.atZone(zoneId)), - combined = contentFormatter.format(message.content.content, highlight), + combined = contentFormatter.formatContent(message.content.content, highlight), isMarkerLine = message.isMarkerLine, isExpanded = message.isExpanded, isSelected = message.isSelected @@ -479,7 +483,7 @@ class QuasselMessageRenderer @Inject constructor( Message_Type.Topic -> FormattedMessage( id = message.content.messageId, time = timeFormatter.format(message.content.time.atZone(zoneId)), - combined = contentFormatter.format(message.content.content, highlight), + combined = contentFormatter.formatContent(message.content.content, highlight), isMarkerLine = message.isMarkerLine, isExpanded = message.isExpanded, isSelected = message.isSelected @@ -498,8 +502,11 @@ class QuasselMessageRenderer @Inject constructor( combined = SpanFormatter.format( "[%d] %s%s: %s", message.content.type, - formatPrefix(message.content.senderPrefixes, highlight), - formatNick(message.content.sender, self, highlight, messageSettings.showHostmaskActions), + contentFormatter.formatPrefix(message.content.senderPrefixes), + contentFormatter.formatNick(message.content.sender, + self, + highlight, + messageSettings.showHostmaskActions), message.content.content ), isMarkerLine = message.isMarkerLine, @@ -508,52 +515,4 @@ class QuasselMessageRenderer @Inject constructor( ) } } - - private fun formatNickNickImpl(nick: String, colorize: Boolean): CharSequence { - val spannableString = SpannableString(nick) - if (colorize) { - val senderColor = IrcUserUtils.senderColor(nick) - spannableString.setSpan( - ForegroundColorSpan(senderColors[(senderColor + senderColors.size) % senderColors.size]), - 0, - nick.length, - SpannableString.SPAN_INCLUSIVE_EXCLUSIVE - ) - } - spannableString.setSpan( - StyleSpan(Typeface.BOLD), - 0, - nick.length, - SpannableString.SPAN_INCLUSIVE_EXCLUSIVE - ) - return spannableString - } - - private fun formatNickImpl(sender: String, colorize: Boolean, hostmask: Boolean): CharSequence { - val nick = IrcUserUtils.nick(sender) - val mask = IrcUserUtils.mask(sender) - val formattedNick = formatNickNickImpl(nick, colorize) - - return if (hostmask) { - SpanFormatter.format("%s (%s)", formattedNick, mask) - } else { - formattedNick - } - } - - private fun formatNick(sender: String, self: Boolean, highlight: Boolean, showHostmask: Boolean) = - when (messageSettings.colorizeNicknames) { - ColorizeNicknamesMode.ALL -> - formatNickImpl(sender, !highlight, showHostmask) - ColorizeNicknamesMode.ALL_BUT_MINE -> - formatNickImpl(sender, !self && !highlight, showHostmask) - ColorizeNicknamesMode.NONE -> - formatNickImpl(sender, false, showHostmask) - } - - private fun formatPrefix(prefix: String, highlight: Boolean) = when (messageSettings.showPrefix) { - ShowPrefixMode.ALL -> prefix - ShowPrefixMode.HIGHEST -> prefix.substring(0, Math.min(prefix.length, 1)) - ShowPrefixMode.NONE -> "" - } } diff --git a/app/src/main/java/de/kuschku/quasseldroid/util/AvatarHelper.kt b/app/src/main/java/de/kuschku/quasseldroid/util/AvatarHelper.kt index 7bc4535e9..b2a8af08a 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/util/AvatarHelper.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/util/AvatarHelper.kt @@ -34,7 +34,18 @@ import de.kuschku.quasseldroid.viewmodel.data.IrcUserItem import org.apache.commons.codec.digest.DigestUtils object AvatarHelper { - fun avatar(settings: MessageSettings, message: QuasselDatabase.DatabaseMessage, + fun avatar(settings: MessageSettings, message: QuasselDatabase.NotificationData, + size: Int? = null) = listOfNotNull( + message.avatarUrl.notBlank()?.let { listOf(it) }, + settings.showIRCCloudAvatars.letIf { + ircCloudFallback(HostmaskHelper.user(message.sender), size) + }, + settings.showGravatarAvatars.letIf { + gravatarFallback(message.realName, size) + } + ).flatten() + + fun avatar(settings: MessageSettings, message: QuasselDatabase.MessageData, size: Int? = null) = listOfNotNull( message.avatarUrl.notBlank()?.let { listOf(it) }, settings.showIRCCloudAvatars.letIf { diff --git a/app/src/main/java/de/kuschku/quasseldroid/util/NotificationMessage.kt b/app/src/main/java/de/kuschku/quasseldroid/util/NotificationMessage.kt new file mode 100644 index 000000000..3d0563758 --- /dev/null +++ b/app/src/main/java/de/kuschku/quasseldroid/util/NotificationMessage.kt @@ -0,0 +1,32 @@ +/* + * Quasseldroid - Quassel client for Android + * + * Copyright (c) 2018 Janne Koschinski + * Copyright (c) 2018 The Quassel Project + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3 as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package de.kuschku.quasseldroid.util + +import android.graphics.drawable.Drawable +import de.kuschku.libquassel.protocol.MsgId +import org.threeten.bp.Instant + +data class NotificationMessage( + val messageId: MsgId, + val sender: CharSequence, + val content: CharSequence, + val time: Instant, + val avatar: Drawable? +) diff --git a/app/src/main/java/de/kuschku/quasseldroid/util/QuasseldroidNotificationManager.kt b/app/src/main/java/de/kuschku/quasseldroid/util/QuasseldroidNotificationManager.kt index a75a08ecf..b59d49ef6 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/util/QuasseldroidNotificationManager.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/util/QuasseldroidNotificationManager.kt @@ -28,17 +28,25 @@ import android.app.NotificationManager import android.app.PendingIntent import android.content.Context import android.content.Intent +import android.graphics.Bitmap +import android.graphics.Canvas +import android.graphics.drawable.Drawable import android.os.Build import android.support.v4.app.NotificationCompat import android.support.v4.app.NotificationManagerCompat +import android.support.v4.app.RemoteInput +import de.kuschku.libquassel.protocol.Buffer_Type +import de.kuschku.libquassel.quassel.BufferInfo +import de.kuschku.libquassel.util.flag.hasFlag import de.kuschku.quasseldroid.R import de.kuschku.quasseldroid.service.QuasselService import de.kuschku.quasseldroid.ui.chat.ChatActivity -import de.kuschku.quasseldroid.util.helper.editApply import de.kuschku.quasseldroid.util.helper.getColorCompat -import de.kuschku.quasseldroid.util.helper.sharedPreferences +import javax.inject.Inject + +class QuasseldroidNotificationManager @Inject constructor(private val context: Context) { + private val notificationManagerCompat = NotificationManagerCompat.from(context) -class QuasseldroidNotificationManager(private val context: Context) { fun init() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) prepareChannels() @@ -63,19 +71,107 @@ class QuasseldroidNotificationManager(private val context: Context) { ) } - private fun id(): Int = context.sharedPreferences { - val key = context.getString(R.string.preference_notification_id_key) - val id = getInt(key, 1) + 1 - editApply { - putInt(key, id) - } - id + private fun bitmapFromDrawable(drawable: Drawable): Bitmap { + val bitmap = Bitmap.createBitmap( + context.resources.getDimensionPixelSize(R.dimen.notification_avatar_width), + context.resources.getDimensionPixelSize(R.dimen.notification_avatar_height), + Bitmap.Config.ARGB_8888 + ) + + val canvas = Canvas(bitmap) + drawable.setBounds(0, 0, canvas.width, canvas.height) + drawable.draw(canvas) + return bitmap + } + + fun notificationGroup(bufferInfo: BufferInfo, notifications: List<NotificationMessage>): Handle { + val pendingIntentOpen = PendingIntent.getActivity( + context.applicationContext, + System.currentTimeMillis().toInt(), + ChatActivity.intent(context.applicationContext).apply { + flags = Intent.FLAG_ACTIVITY_CLEAR_TOP + }, + 0 + ) + + val remoteInput = RemoteInput.Builder("reply_content") + .setLabel("Reply") + .build() + + val replyPendingIntent = PendingIntent.getService( + context.applicationContext, + System.currentTimeMillis().toInt(), + QuasselService.intent( + context, + bufferId = bufferInfo.bufferId, + markReadMessage = notifications.last().messageId + ), + 0 + ) + + val markReadPendingIntent = PendingIntent.getService( + context.applicationContext, + System.currentTimeMillis().toInt(), + QuasselService.intent( + context, + bufferId = bufferInfo.bufferId, + markReadMessage = notifications.last().messageId + ), + 0 + ) + + val deletePendingIntent = PendingIntent.getService( + context.applicationContext, + System.currentTimeMillis().toInt(), + QuasselService.intent( + context, + bufferId = bufferInfo.bufferId, + markReadMessage = notifications.last().messageId + ), + 0 + ) + + val notification = NotificationCompat.Builder( + context.applicationContext, + context.getString(R.string.notification_channel_highlight) + ) + .setContentIntent(pendingIntentOpen) + .setDeleteIntent(deletePendingIntent) + .setSmallIcon(R.mipmap.ic_logo) + .setColor(context.getColorCompat(R.color.colorPrimary)) + .setPriority(NotificationCompat.PRIORITY_HIGH) + .setStyle(NotificationCompat.MessagingStyle("") + .setConversationTitle(bufferInfo.bufferName) + .also { + for (notification in notifications) { + it.addMessage( + notification.content, + notification.time.toEpochMilli(), + notification.sender + ) + } + } + ) + .addAction(0, "Mark Read", markReadPendingIntent) + .addAction( + NotificationCompat.Action.Builder(0, "Reply", replyPendingIntent) + .addRemoteInput(remoteInput) + .build() + ) + .apply { + if (bufferInfo.type.hasFlag(Buffer_Type.QueryBuffer)) { + notifications.lastOrNull()?.avatar?.let { + setLargeIcon(bitmapFromDrawable(it)) + } + } + } + return Handle(bufferInfo.bufferId, notification) } fun notificationBackground(): Handle { val pendingIntentOpen = PendingIntent.getActivity( context.applicationContext, - 0, + System.currentTimeMillis().toInt(), ChatActivity.intent(context.applicationContext).apply { flags = Intent.FLAG_ACTIVITY_CLEAR_TOP }, @@ -84,9 +180,9 @@ class QuasseldroidNotificationManager(private val context: Context) { val pendingIntentDisconnect = PendingIntent.getService( context, - 0, + System.currentTimeMillis().toInt(), QuasselService.intent(context.applicationContext, disconnect = true), - PendingIntent.FLAG_UPDATE_CURRENT + 0 ) val notification = NotificationCompat.Builder( @@ -103,11 +199,15 @@ class QuasseldroidNotificationManager(private val context: Context) { } fun notify(handle: Handle) { - NotificationManagerCompat.from(context).notify(handle.id, handle.builder.build()) + notificationManagerCompat.notify(handle.id, handle.builder.build()) } fun remove(handle: Handle) { - NotificationManagerCompat.from(context).cancel(handle.id) + notificationManagerCompat.cancel(handle.id) + } + + fun remove(id: Int) { + notificationManagerCompat.cancel(id) } companion object { diff --git a/app/src/main/java/de/kuschku/quasseldroid/util/irc/format/ContentFormatter.kt b/app/src/main/java/de/kuschku/quasseldroid/util/irc/format/ContentFormatter.kt index b869d9703..f1ca02767 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/util/irc/format/ContentFormatter.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/util/irc/format/ContentFormatter.kt @@ -22,15 +22,24 @@ package de.kuschku.quasseldroid.util.irc.format +import android.content.Context +import android.graphics.Typeface import android.text.SpannableString import android.text.Spanned import android.text.TextPaint +import android.text.style.ForegroundColorSpan +import android.text.style.StyleSpan import android.text.style.URLSpan +import de.kuschku.libquassel.util.IrcUserUtils +import de.kuschku.quasseldroid.R import de.kuschku.quasseldroid.settings.MessageSettings +import de.kuschku.quasseldroid.util.helper.styledAttributes +import de.kuschku.quasseldroid.util.ui.SpanFormatter import org.intellij.lang.annotations.Language import javax.inject.Inject class ContentFormatter @Inject constructor( + private val context: Context, private val ircFormatDeserializer: IrcFormatDeserializer, private val messageSettings: MessageSettings ) { @@ -53,6 +62,17 @@ class ContentFormatter @Inject constructor( RegexOption.IGNORE_CASE ) + private val senderColors: IntArray = context.theme.styledAttributes( + R.attr.senderColor0, R.attr.senderColor1, R.attr.senderColor2, R.attr.senderColor3, + R.attr.senderColor4, R.attr.senderColor5, R.attr.senderColor6, R.attr.senderColor7, + R.attr.senderColor8, R.attr.senderColor9, R.attr.senderColorA, R.attr.senderColorB, + R.attr.senderColorC, R.attr.senderColorD, R.attr.senderColorE, R.attr.senderColorF + ) { + IntArray(16) { + getColor(it, 0) + } + } + class QuasselURLSpan(text: String, private val highlight: Boolean) : URLSpan(text) { override fun updateDrawState(ds: TextPaint?) { if (ds != null) { @@ -63,7 +83,7 @@ class ContentFormatter @Inject constructor( } } - fun format(content: String, highlight: Boolean = false): CharSequence { + fun formatContent(content: String, highlight: Boolean = false): CharSequence { val formattedText = ircFormatDeserializer.formatString(content, messageSettings.colorizeMirc) val text = SpannableString(formattedText) @@ -84,4 +104,55 @@ class ContentFormatter @Inject constructor( return text } + + private fun formatNickNickImpl(nick: String, colorize: Boolean, + senderColors: IntArray): CharSequence { + val spannableString = SpannableString(nick) + if (colorize) { + val senderColor = IrcUserUtils.senderColor(nick) + spannableString.setSpan( + ForegroundColorSpan(senderColors[(senderColor + senderColors.size) % senderColors.size]), + 0, + nick.length, + SpannableString.SPAN_INCLUSIVE_EXCLUSIVE + ) + } + spannableString.setSpan( + StyleSpan(Typeface.BOLD), + 0, + nick.length, + SpannableString.SPAN_INCLUSIVE_EXCLUSIVE + ) + return spannableString + } + + private fun formatNickImpl(sender: String, colorize: Boolean, hostmask: Boolean, + senderColors: IntArray): CharSequence { + val nick = IrcUserUtils.nick(sender) + val mask = IrcUserUtils.mask(sender) + val formattedNick = formatNickNickImpl(nick, colorize, senderColors) + + return if (hostmask) { + SpanFormatter.format("%s (%s)", formattedNick, mask) + } else { + formattedNick + } + } + + fun formatNick(sender: String, self: Boolean = false, highlight: Boolean = false, + showHostmask: Boolean = false, senderColors: IntArray = this.senderColors) = + when (messageSettings.colorizeNicknames) { + MessageSettings.ColorizeNicknamesMode.ALL -> + formatNickImpl(sender, !highlight, showHostmask, senderColors) + MessageSettings.ColorizeNicknamesMode.ALL_BUT_MINE -> + formatNickImpl(sender, !self && !highlight, showHostmask, senderColors) + MessageSettings.ColorizeNicknamesMode.NONE -> + formatNickImpl(sender, false, showHostmask, senderColors) + } + + fun formatPrefix(prefix: String) = when (messageSettings.showPrefix) { + MessageSettings.ShowPrefixMode.ALL -> prefix + MessageSettings.ShowPrefixMode.HIGHEST -> prefix.substring(0, Math.min(prefix.length, 1)) + MessageSettings.ShowPrefixMode.NONE -> "" + } } diff --git a/app/src/main/java/de/kuschku/quasseldroid/util/irc/format/IrcFormatDeserializer.kt b/app/src/main/java/de/kuschku/quasseldroid/util/irc/format/IrcFormatDeserializer.kt index c986c7c9f..d666f9828 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/util/irc/format/IrcFormatDeserializer.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/util/irc/format/IrcFormatDeserializer.kt @@ -26,7 +26,7 @@ import android.content.Context import android.text.SpannableStringBuilder import android.text.Spanned import de.kuschku.quasseldroid.R -import de.kuschku.quasseldroid.util.helper.styledAttributes +import de.kuschku.quasseldroid.util.helper.getColorCompat import de.kuschku.quasseldroid.util.irc.format.spans.* import javax.inject.Inject @@ -35,37 +35,33 @@ import javax.inject.Inject * color and format codes */ class IrcFormatDeserializer @Inject constructor(context: Context) { - val mircColors = context.theme.styledAttributes( - R.attr.mircColor00, R.attr.mircColor01, R.attr.mircColor02, R.attr.mircColor03, - R.attr.mircColor04, R.attr.mircColor05, R.attr.mircColor06, R.attr.mircColor07, - R.attr.mircColor08, R.attr.mircColor09, R.attr.mircColor10, R.attr.mircColor11, - R.attr.mircColor12, R.attr.mircColor13, R.attr.mircColor14, R.attr.mircColor15, - R.attr.mircColor16, R.attr.mircColor17, R.attr.mircColor18, R.attr.mircColor19, - R.attr.mircColor20, R.attr.mircColor21, R.attr.mircColor22, R.attr.mircColor23, - R.attr.mircColor24, R.attr.mircColor25, R.attr.mircColor26, R.attr.mircColor27, - R.attr.mircColor28, R.attr.mircColor29, R.attr.mircColor30, R.attr.mircColor31, - R.attr.mircColor32, R.attr.mircColor33, R.attr.mircColor34, R.attr.mircColor35, - R.attr.mircColor36, R.attr.mircColor37, R.attr.mircColor38, R.attr.mircColor39, - R.attr.mircColor40, R.attr.mircColor41, R.attr.mircColor42, R.attr.mircColor43, - R.attr.mircColor44, R.attr.mircColor45, R.attr.mircColor46, R.attr.mircColor47, - R.attr.mircColor48, R.attr.mircColor49, R.attr.mircColor50, R.attr.mircColor51, - R.attr.mircColor52, R.attr.mircColor53, R.attr.mircColor54, R.attr.mircColor55, - R.attr.mircColor56, R.attr.mircColor57, R.attr.mircColor58, R.attr.mircColor59, - R.attr.mircColor60, R.attr.mircColor61, R.attr.mircColor62, R.attr.mircColor63, - R.attr.mircColor64, R.attr.mircColor65, R.attr.mircColor66, R.attr.mircColor67, - R.attr.mircColor68, R.attr.mircColor69, R.attr.mircColor70, R.attr.mircColor71, - R.attr.mircColor72, R.attr.mircColor73, R.attr.mircColor74, R.attr.mircColor75, - R.attr.mircColor76, R.attr.mircColor77, R.attr.mircColor78, R.attr.mircColor79, - R.attr.mircColor80, R.attr.mircColor81, R.attr.mircColor82, R.attr.mircColor83, - R.attr.mircColor84, R.attr.mircColor85, R.attr.mircColor86, R.attr.mircColor87, - R.attr.mircColor88, R.attr.mircColor89, R.attr.mircColor90, R.attr.mircColor91, - R.attr.mircColor92, R.attr.mircColor93, R.attr.mircColor94, R.attr.mircColor95, - R.attr.mircColor96, R.attr.mircColor97, R.attr.mircColor98 - ) { - IntArray(99) { - getColor(it, 0) - } - } + val mircColors = listOf( + R.color.mircColor00, R.color.mircColor01, R.color.mircColor02, R.color.mircColor03, + R.color.mircColor04, R.color.mircColor05, R.color.mircColor06, R.color.mircColor07, + R.color.mircColor08, R.color.mircColor09, R.color.mircColor10, R.color.mircColor11, + R.color.mircColor12, R.color.mircColor13, R.color.mircColor14, R.color.mircColor15, + R.color.mircColor16, R.color.mircColor17, R.color.mircColor18, R.color.mircColor19, + R.color.mircColor20, R.color.mircColor21, R.color.mircColor22, R.color.mircColor23, + R.color.mircColor24, R.color.mircColor25, R.color.mircColor26, R.color.mircColor27, + R.color.mircColor28, R.color.mircColor29, R.color.mircColor30, R.color.mircColor31, + R.color.mircColor32, R.color.mircColor33, R.color.mircColor34, R.color.mircColor35, + R.color.mircColor36, R.color.mircColor37, R.color.mircColor38, R.color.mircColor39, + R.color.mircColor40, R.color.mircColor41, R.color.mircColor42, R.color.mircColor43, + R.color.mircColor44, R.color.mircColor45, R.color.mircColor46, R.color.mircColor47, + R.color.mircColor48, R.color.mircColor49, R.color.mircColor50, R.color.mircColor51, + R.color.mircColor52, R.color.mircColor53, R.color.mircColor54, R.color.mircColor55, + R.color.mircColor56, R.color.mircColor57, R.color.mircColor58, R.color.mircColor59, + R.color.mircColor60, R.color.mircColor61, R.color.mircColor62, R.color.mircColor63, + R.color.mircColor64, R.color.mircColor65, R.color.mircColor66, R.color.mircColor67, + R.color.mircColor68, R.color.mircColor69, R.color.mircColor70, R.color.mircColor71, + R.color.mircColor72, R.color.mircColor73, R.color.mircColor74, R.color.mircColor75, + R.color.mircColor76, R.color.mircColor77, R.color.mircColor78, R.color.mircColor79, + R.color.mircColor80, R.color.mircColor81, R.color.mircColor82, R.color.mircColor83, + R.color.mircColor84, R.color.mircColor85, R.color.mircColor86, R.color.mircColor87, + R.color.mircColor88, R.color.mircColor89, R.color.mircColor90, R.color.mircColor91, + R.color.mircColor92, R.color.mircColor93, R.color.mircColor94, R.color.mircColor95, + R.color.mircColor96, R.color.mircColor97, R.color.mircColor98 + ).map(context::getColorCompat).toIntArray() /** * Function to handle mIRC formatted strings diff --git a/app/src/main/java/de/kuschku/quasseldroid/util/irc/format/IrcFormatSerializer.kt b/app/src/main/java/de/kuschku/quasseldroid/util/irc/format/IrcFormatSerializer.kt index 897b73630..952d589cd 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/util/irc/format/IrcFormatSerializer.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/util/irc/format/IrcFormatSerializer.kt @@ -27,42 +27,39 @@ import android.graphics.Typeface import android.text.Spanned import android.text.style.* import de.kuschku.quasseldroid.R +import de.kuschku.quasseldroid.util.helper.getColorCompat import de.kuschku.quasseldroid.util.helper.styledAttributes import java.util.* import javax.inject.Inject class IrcFormatSerializer @Inject constructor(context: Context) { - private val mircColors = context.theme.styledAttributes( - R.attr.mircColor00, R.attr.mircColor01, R.attr.mircColor02, R.attr.mircColor03, - R.attr.mircColor04, R.attr.mircColor05, R.attr.mircColor06, R.attr.mircColor07, - R.attr.mircColor08, R.attr.mircColor09, R.attr.mircColor10, R.attr.mircColor11, - R.attr.mircColor12, R.attr.mircColor13, R.attr.mircColor14, R.attr.mircColor15, - R.attr.mircColor16, R.attr.mircColor17, R.attr.mircColor18, R.attr.mircColor19, - R.attr.mircColor20, R.attr.mircColor21, R.attr.mircColor22, R.attr.mircColor23, - R.attr.mircColor24, R.attr.mircColor25, R.attr.mircColor26, R.attr.mircColor27, - R.attr.mircColor28, R.attr.mircColor29, R.attr.mircColor30, R.attr.mircColor31, - R.attr.mircColor32, R.attr.mircColor33, R.attr.mircColor34, R.attr.mircColor35, - R.attr.mircColor36, R.attr.mircColor37, R.attr.mircColor38, R.attr.mircColor39, - R.attr.mircColor40, R.attr.mircColor41, R.attr.mircColor42, R.attr.mircColor43, - R.attr.mircColor44, R.attr.mircColor45, R.attr.mircColor46, R.attr.mircColor47, - R.attr.mircColor48, R.attr.mircColor49, R.attr.mircColor50, R.attr.mircColor51, - R.attr.mircColor52, R.attr.mircColor53, R.attr.mircColor54, R.attr.mircColor55, - R.attr.mircColor56, R.attr.mircColor57, R.attr.mircColor58, R.attr.mircColor59, - R.attr.mircColor60, R.attr.mircColor61, R.attr.mircColor62, R.attr.mircColor63, - R.attr.mircColor64, R.attr.mircColor65, R.attr.mircColor66, R.attr.mircColor67, - R.attr.mircColor68, R.attr.mircColor69, R.attr.mircColor70, R.attr.mircColor71, - R.attr.mircColor72, R.attr.mircColor73, R.attr.mircColor74, R.attr.mircColor75, - R.attr.mircColor76, R.attr.mircColor77, R.attr.mircColor78, R.attr.mircColor79, - R.attr.mircColor80, R.attr.mircColor81, R.attr.mircColor82, R.attr.mircColor83, - R.attr.mircColor84, R.attr.mircColor85, R.attr.mircColor86, R.attr.mircColor87, - R.attr.mircColor88, R.attr.mircColor89, R.attr.mircColor90, R.attr.mircColor91, - R.attr.mircColor92, R.attr.mircColor93, R.attr.mircColor94, R.attr.mircColor95, - R.attr.mircColor96, R.attr.mircColor97, R.attr.mircColor98 - ) { - IntArray(99) { - getColor(it, 0) - } - } + val mircColors = listOf( + R.color.mircColor00, R.color.mircColor01, R.color.mircColor02, R.color.mircColor03, + R.color.mircColor04, R.color.mircColor05, R.color.mircColor06, R.color.mircColor07, + R.color.mircColor08, R.color.mircColor09, R.color.mircColor10, R.color.mircColor11, + R.color.mircColor12, R.color.mircColor13, R.color.mircColor14, R.color.mircColor15, + R.color.mircColor16, R.color.mircColor17, R.color.mircColor18, R.color.mircColor19, + R.color.mircColor20, R.color.mircColor21, R.color.mircColor22, R.color.mircColor23, + R.color.mircColor24, R.color.mircColor25, R.color.mircColor26, R.color.mircColor27, + R.color.mircColor28, R.color.mircColor29, R.color.mircColor30, R.color.mircColor31, + R.color.mircColor32, R.color.mircColor33, R.color.mircColor34, R.color.mircColor35, + R.color.mircColor36, R.color.mircColor37, R.color.mircColor38, R.color.mircColor39, + R.color.mircColor40, R.color.mircColor41, R.color.mircColor42, R.color.mircColor43, + R.color.mircColor44, R.color.mircColor45, R.color.mircColor46, R.color.mircColor47, + R.color.mircColor48, R.color.mircColor49, R.color.mircColor50, R.color.mircColor51, + R.color.mircColor52, R.color.mircColor53, R.color.mircColor54, R.color.mircColor55, + R.color.mircColor56, R.color.mircColor57, R.color.mircColor58, R.color.mircColor59, + R.color.mircColor60, R.color.mircColor61, R.color.mircColor62, R.color.mircColor63, + R.color.mircColor64, R.color.mircColor65, R.color.mircColor66, R.color.mircColor67, + R.color.mircColor68, R.color.mircColor69, R.color.mircColor70, R.color.mircColor71, + R.color.mircColor72, R.color.mircColor73, R.color.mircColor74, R.color.mircColor75, + R.color.mircColor76, R.color.mircColor77, R.color.mircColor78, R.color.mircColor79, + R.color.mircColor80, R.color.mircColor81, R.color.mircColor82, R.color.mircColor83, + R.color.mircColor84, R.color.mircColor85, R.color.mircColor86, R.color.mircColor87, + R.color.mircColor88, R.color.mircColor89, R.color.mircColor90, R.color.mircColor91, + R.color.mircColor92, R.color.mircColor93, R.color.mircColor94, R.color.mircColor95, + R.color.mircColor96, R.color.mircColor97, R.color.mircColor98 + ).map(context::getColorCompat).toIntArray() private val mircColorMap = mircColors.take(16).mapIndexed { index: Int, color: Int -> color to index diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml index 69aeb9d26..8483fc96c 100644 --- a/app/src/main/res/values/attrs.xml +++ b/app/src/main/res/values/attrs.xml @@ -39,107 +39,6 @@ <attr name="senderColorE" format="color" /> <attr name="senderColorF" format="color" /> - <!-- mirc colors --> - <attr name="mircColor00" format="color" /> - <attr name="mircColor01" format="color" /> - <attr name="mircColor02" format="color" /> - <attr name="mircColor03" format="color" /> - <attr name="mircColor04" format="color" /> - <attr name="mircColor05" format="color" /> - <attr name="mircColor06" format="color" /> - <attr name="mircColor07" format="color" /> - <attr name="mircColor08" format="color" /> - <attr name="mircColor09" format="color" /> - <attr name="mircColor10" format="color" /> - <attr name="mircColor11" format="color" /> - <attr name="mircColor12" format="color" /> - <attr name="mircColor13" format="color" /> - <attr name="mircColor14" format="color" /> - <attr name="mircColor15" format="color" /> - <attr name="mircColor16" format="color" /> - <attr name="mircColor17" format="color" /> - <attr name="mircColor18" format="color" /> - <attr name="mircColor19" format="color" /> - <attr name="mircColor20" format="color" /> - <attr name="mircColor21" format="color" /> - <attr name="mircColor22" format="color" /> - <attr name="mircColor23" format="color" /> - <attr name="mircColor24" format="color" /> - <attr name="mircColor25" format="color" /> - <attr name="mircColor26" format="color" /> - <attr name="mircColor27" format="color" /> - <attr name="mircColor28" format="color" /> - <attr name="mircColor29" format="color" /> - <attr name="mircColor30" format="color" /> - <attr name="mircColor31" format="color" /> - <attr name="mircColor32" format="color" /> - <attr name="mircColor33" format="color" /> - <attr name="mircColor34" format="color" /> - <attr name="mircColor35" format="color" /> - <attr name="mircColor36" format="color" /> - <attr name="mircColor37" format="color" /> - <attr name="mircColor38" format="color" /> - <attr name="mircColor39" format="color" /> - <attr name="mircColor40" format="color" /> - <attr name="mircColor41" format="color" /> - <attr name="mircColor42" format="color" /> - <attr name="mircColor43" format="color" /> - <attr name="mircColor44" format="color" /> - <attr name="mircColor45" format="color" /> - <attr name="mircColor46" format="color" /> - <attr name="mircColor47" format="color" /> - <attr name="mircColor48" format="color" /> - <attr name="mircColor49" format="color" /> - <attr name="mircColor50" format="color" /> - <attr name="mircColor51" format="color" /> - <attr name="mircColor52" format="color" /> - <attr name="mircColor53" format="color" /> - <attr name="mircColor54" format="color" /> - <attr name="mircColor55" format="color" /> - <attr name="mircColor56" format="color" /> - <attr name="mircColor57" format="color" /> - <attr name="mircColor58" format="color" /> - <attr name="mircColor59" format="color" /> - <attr name="mircColor60" format="color" /> - <attr name="mircColor61" format="color" /> - <attr name="mircColor62" format="color" /> - <attr name="mircColor63" format="color" /> - <attr name="mircColor64" format="color" /> - <attr name="mircColor65" format="color" /> - <attr name="mircColor66" format="color" /> - <attr name="mircColor67" format="color" /> - <attr name="mircColor68" format="color" /> - <attr name="mircColor69" format="color" /> - <attr name="mircColor70" format="color" /> - <attr name="mircColor71" format="color" /> - <attr name="mircColor72" format="color" /> - <attr name="mircColor73" format="color" /> - <attr name="mircColor74" format="color" /> - <attr name="mircColor75" format="color" /> - <attr name="mircColor76" format="color" /> - <attr name="mircColor77" format="color" /> - <attr name="mircColor78" format="color" /> - <attr name="mircColor79" format="color" /> - <attr name="mircColor80" format="color" /> - <attr name="mircColor81" format="color" /> - <attr name="mircColor82" format="color" /> - <attr name="mircColor83" format="color" /> - <attr name="mircColor84" format="color" /> - <attr name="mircColor85" format="color" /> - <attr name="mircColor86" format="color" /> - <attr name="mircColor87" format="color" /> - <attr name="mircColor88" format="color" /> - <attr name="mircColor89" format="color" /> - <attr name="mircColor90" format="color" /> - <attr name="mircColor91" format="color" /> - <attr name="mircColor92" format="color" /> - <attr name="mircColor93" format="color" /> - <attr name="mircColor94" format="color" /> - <attr name="mircColor95" format="color" /> - <attr name="mircColor96" format="color" /> - <attr name="mircColor97" format="color" /> - <attr name="mircColor98" format="color" /> - <!-- Background and foreground colors for UI --> <attr name="colorForeground" format="color" /> <attr name="colorForegroundHighlight" format="color" /> diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 58bf27b77..994f06dc2 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -39,4 +39,135 @@ <color name="ripple_dark">#33ffffff</color> <color name="ripple_light">#1f000000</color> + + <color name="senderColor0">#F44336</color> + <color name="senderColor1">#2196F3</color> + <color name="senderColor2">#7CB342</color> + <color name="senderColor3">#7B1FA2</color> + <color name="senderColor4">#DA8E00</color> + <color name="senderColor5">#4CAF50</color> + <color name="senderColor6">#3F51B5</color> + <color name="senderColor7">#E91E63</color> + <color name="senderColor8">#b94600</color> + <color name="senderColor9">#9E9D24</color> + <color name="senderColorA">#558B2F</color> + <color name="senderColorB">#009688</color> + <color name="senderColorC">#0277BD</color> + <color name="senderColorD">#00838F</color> + <color name="senderColorE">#9C27B0</color> + <color name="senderColorF">#C51162</color> + + <!-- mirc colors --> + <color name="mircColor00">#ffffff</color> + <color name="mircColor01">#000000</color> + <color name="mircColor02">#000080</color> + <color name="mircColor03">#008000</color> + <color name="mircColor04">#ff0000</color> + <color name="mircColor05">#800000</color> + <color name="mircColor06">#800080</color> + <color name="mircColor07">#ffa500</color> + <color name="mircColor08">#ffff00</color> + <color name="mircColor09">#00ff00</color> + <color name="mircColor10">#008080</color> + <color name="mircColor11">#00ffff</color> + <color name="mircColor12">#4169e1</color> + <color name="mircColor13">#ff00ff</color> + <color name="mircColor14">#808080</color> + <color name="mircColor15">#c0c0c0</color> + + <color name="mircColor16">#470000</color> + <color name="mircColor28">#740000</color> + <color name="mircColor40">#b50000</color> + <color name="mircColor52">#ff0000</color> + <color name="mircColor64">#ff5959</color> + <color name="mircColor76">#ff9c9c</color> + + <color name="mircColor17">#472100</color> + <color name="mircColor29">#743a00</color> + <color name="mircColor41">#b56300</color> + <color name="mircColor53">#ff8c00</color> + <color name="mircColor65">#ffb459</color> + <color name="mircColor77">#ffd39c</color> + + <color name="mircColor18">#474700</color> + <color name="mircColor30">#747400</color> + <color name="mircColor42">#b5b500</color> + <color name="mircColor54">#ffff00</color> + <color name="mircColor66">#ffff71</color> + <color name="mircColor78">#ffff9c</color> + + <color name="mircColor19">#324700</color> + <color name="mircColor31">#517400</color> + <color name="mircColor43">#7db500</color> + <color name="mircColor55">#b2ff00</color> + <color name="mircColor67">#cfff60</color> + <color name="mircColor79">#e2ff9c</color> + + <color name="mircColor20">#004700</color> + <color name="mircColor32">#007400</color> + <color name="mircColor44">#00b500</color> + <color name="mircColor56">#00ff00</color> + <color name="mircColor68">#6fff6f</color> + <color name="mircColor80">#9cff9c</color> + + <color name="mircColor21">#00472c</color> + <color name="mircColor33">#007449</color> + <color name="mircColor45">#00b571</color> + <color name="mircColor57">#00ffa0</color> + <color name="mircColor69">#65ffc9</color> + <color name="mircColor81">#9cffdb</color> + + <color name="mircColor22">#004747</color> + <color name="mircColor34">#007474</color> + <color name="mircColor46">#00b5b5</color> + <color name="mircColor58">#00ffff</color> + <color name="mircColor70">#6dffff</color> + <color name="mircColor82">#9cffff</color> + + <color name="mircColor23">#002747</color> + <color name="mircColor35">#004074</color> + <color name="mircColor47">#0063b5</color> + <color name="mircColor59">#008cff</color> + <color name="mircColor71">#59b4ff</color> + <color name="mircColor83">#9cd3ff</color> + + <color name="mircColor24">#000047</color> + <color name="mircColor36">#000074</color> + <color name="mircColor48">#0000b5</color> + <color name="mircColor60">#0000ff</color> + <color name="mircColor72">#5959ff</color> + <color name="mircColor84">#9c9cff</color> + + <color name="mircColor25">#2e0047</color> + <color name="mircColor37">#4b0074</color> + <color name="mircColor49">#7500b5</color> + <color name="mircColor61">#a500ff</color> + <color name="mircColor73">#c459ff</color> + <color name="mircColor85">#dc9cff</color> + + <color name="mircColor26">#470047</color> + <color name="mircColor38">#740074</color> + <color name="mircColor50">#b500b5</color> + <color name="mircColor62">#ff00ff</color> + <color name="mircColor74">#ff66ff</color> + <color name="mircColor86">#ff9cff</color> + + <color name="mircColor27">#47002a</color> + <color name="mircColor39">#740045</color> + <color name="mircColor51">#b5006b</color> + <color name="mircColor63">#ff0098</color> + <color name="mircColor75">#ff59bc</color> + <color name="mircColor87">#ff94d3</color> + + <color name="mircColor88">#000000</color> + <color name="mircColor89">#131313</color> + <color name="mircColor90">#282828</color> + <color name="mircColor91">#363636</color> + <color name="mircColor92">#4d4d4d</color> + <color name="mircColor93">#656565</color> + <color name="mircColor94">#818181</color> + <color name="mircColor95">#9f9f9f</color> + <color name="mircColor96">#bcbcbc</color> + <color name="mircColor97">#e2e2e2</color> + <color name="mircColor98">#ffffff</color> </resources> diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index 7bb53ff86..9fd5754e9 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -37,4 +37,7 @@ <dimen name="colorchooser_circlesize">56dp</dimen> <dimen name="avatar_size">35sp</dimen> + + <dimen name="notification_avatar_width">64dp</dimen> + <dimen name="notification_avatar_height">64dp</dimen> </resources> diff --git a/app/src/main/res/values/themes_base.xml b/app/src/main/res/values/themes_base.xml index 4b7973c1a..18b370fb8 100644 --- a/app/src/main/res/values/themes_base.xml +++ b/app/src/main/res/values/themes_base.xml @@ -91,119 +91,6 @@ <item name="colorAway">@color/colorAwayDark</item> <item name="cardStyle">@style/CardView.Dark</item> - - <item name="mircColor00">#ffffff</item> - <item name="mircColor01">#000000</item> - <item name="mircColor02">#000080</item> - <item name="mircColor03">#008000</item> - <item name="mircColor04">#ff0000</item> - <item name="mircColor05">#800000</item> - <item name="mircColor06">#800080</item> - <item name="mircColor07">#ffa500</item> - <item name="mircColor08">#ffff00</item> - <item name="mircColor09">#00ff00</item> - <item name="mircColor10">#008080</item> - <item name="mircColor11">#00ffff</item> - <item name="mircColor12">#4169e1</item> - <item name="mircColor13">#ff00ff</item> - <item name="mircColor14">#808080</item> - <item name="mircColor15">#c0c0c0</item> - - <item name="mircColor16">#470000</item> - <item name="mircColor28">#740000</item> - <item name="mircColor40">#b50000</item> - <item name="mircColor52">#ff0000</item> - <item name="mircColor64">#ff5959</item> - <item name="mircColor76">#ff9c9c</item> - - <item name="mircColor17">#472100</item> - <item name="mircColor29">#743a00</item> - <item name="mircColor41">#b56300</item> - <item name="mircColor53">#ff8c00</item> - <item name="mircColor65">#ffb459</item> - <item name="mircColor77">#ffd39c</item> - - <item name="mircColor18">#474700</item> - <item name="mircColor30">#747400</item> - <item name="mircColor42">#b5b500</item> - <item name="mircColor54">#ffff00</item> - <item name="mircColor66">#ffff71</item> - <item name="mircColor78">#ffff9c</item> - - <item name="mircColor19">#324700</item> - <item name="mircColor31">#517400</item> - <item name="mircColor43">#7db500</item> - <item name="mircColor55">#b2ff00</item> - <item name="mircColor67">#cfff60</item> - <item name="mircColor79">#e2ff9c</item> - - <item name="mircColor20">#004700</item> - <item name="mircColor32">#007400</item> - <item name="mircColor44">#00b500</item> - <item name="mircColor56">#00ff00</item> - <item name="mircColor68">#6fff6f</item> - <item name="mircColor80">#9cff9c</item> - - <item name="mircColor21">#00472c</item> - <item name="mircColor33">#007449</item> - <item name="mircColor45">#00b571</item> - <item name="mircColor57">#00ffa0</item> - <item name="mircColor69">#65ffc9</item> - <item name="mircColor81">#9cffdb</item> - - <item name="mircColor22">#004747</item> - <item name="mircColor34">#007474</item> - <item name="mircColor46">#00b5b5</item> - <item name="mircColor58">#00ffff</item> - <item name="mircColor70">#6dffff</item> - <item name="mircColor82">#9cffff</item> - - <item name="mircColor23">#002747</item> - <item name="mircColor35">#004074</item> - <item name="mircColor47">#0063b5</item> - <item name="mircColor59">#008cff</item> - <item name="mircColor71">#59b4ff</item> - <item name="mircColor83">#9cd3ff</item> - - <item name="mircColor24">#000047</item> - <item name="mircColor36">#000074</item> - <item name="mircColor48">#0000b5</item> - <item name="mircColor60">#0000ff</item> - <item name="mircColor72">#5959ff</item> - <item name="mircColor84">#9c9cff</item> - - <item name="mircColor25">#2e0047</item> - <item name="mircColor37">#4b0074</item> - <item name="mircColor49">#7500b5</item> - <item name="mircColor61">#a500ff</item> - <item name="mircColor73">#c459ff</item> - <item name="mircColor85">#dc9cff</item> - - <item name="mircColor26">#470047</item> - <item name="mircColor38">#740074</item> - <item name="mircColor50">#b500b5</item> - <item name="mircColor62">#ff00ff</item> - <item name="mircColor74">#ff66ff</item> - <item name="mircColor86">#ff9cff</item> - - <item name="mircColor27">#47002a</item> - <item name="mircColor39">#740045</item> - <item name="mircColor51">#b5006b</item> - <item name="mircColor63">#ff0098</item> - <item name="mircColor75">#ff59bc</item> - <item name="mircColor87">#ff94d3</item> - - <item name="mircColor88">#000000</item> - <item name="mircColor89">#131313</item> - <item name="mircColor90">#282828</item> - <item name="mircColor91">#363636</item> - <item name="mircColor92">#4d4d4d</item> - <item name="mircColor93">#656565</item> - <item name="mircColor94">#818181</item> - <item name="mircColor95">#9f9f9f</item> - <item name="mircColor96">#bcbcbc</item> - <item name="mircColor97">#e2e2e2</item> - <item name="mircColor98">#ffffff</item> </style> <style name="Theme.ChatTheme.Auto" parent="Theme.ChatTheme"> @@ -238,119 +125,6 @@ <item name="colorAway">@color/colorAwayLight</item> <item name="cardStyle">@style/CardView.Light</item> - - <item name="mircColor00">#ffffff</item> - <item name="mircColor01">#000000</item> - <item name="mircColor02">#000080</item> - <item name="mircColor03">#008000</item> - <item name="mircColor04">#ff0000</item> - <item name="mircColor05">#800000</item> - <item name="mircColor06">#800080</item> - <item name="mircColor07">#ffa500</item> - <item name="mircColor08">#ffff00</item> - <item name="mircColor09">#00ff00</item> - <item name="mircColor10">#008080</item> - <item name="mircColor11">#00ffff</item> - <item name="mircColor12">#4169e1</item> - <item name="mircColor13">#ff00ff</item> - <item name="mircColor14">#808080</item> - <item name="mircColor15">#c0c0c0</item> - - <item name="mircColor16">#470000</item> - <item name="mircColor28">#740000</item> - <item name="mircColor40">#b50000</item> - <item name="mircColor52">#ff0000</item> - <item name="mircColor64">#ff5959</item> - <item name="mircColor76">#ff9c9c</item> - - <item name="mircColor17">#472100</item> - <item name="mircColor29">#743a00</item> - <item name="mircColor41">#b56300</item> - <item name="mircColor53">#ff8c00</item> - <item name="mircColor65">#ffb459</item> - <item name="mircColor77">#ffd39c</item> - - <item name="mircColor18">#474700</item> - <item name="mircColor30">#747400</item> - <item name="mircColor42">#b5b500</item> - <item name="mircColor54">#ffff00</item> - <item name="mircColor66">#ffff71</item> - <item name="mircColor78">#ffff9c</item> - - <item name="mircColor19">#324700</item> - <item name="mircColor31">#517400</item> - <item name="mircColor43">#7db500</item> - <item name="mircColor55">#b2ff00</item> - <item name="mircColor67">#cfff60</item> - <item name="mircColor79">#e2ff9c</item> - - <item name="mircColor20">#004700</item> - <item name="mircColor32">#007400</item> - <item name="mircColor44">#00b500</item> - <item name="mircColor56">#00ff00</item> - <item name="mircColor68">#6fff6f</item> - <item name="mircColor80">#9cff9c</item> - - <item name="mircColor21">#00472c</item> - <item name="mircColor33">#007449</item> - <item name="mircColor45">#00b571</item> - <item name="mircColor57">#00ffa0</item> - <item name="mircColor69">#65ffc9</item> - <item name="mircColor81">#9cffdb</item> - - <item name="mircColor22">#004747</item> - <item name="mircColor34">#007474</item> - <item name="mircColor46">#00b5b5</item> - <item name="mircColor58">#00ffff</item> - <item name="mircColor70">#6dffff</item> - <item name="mircColor82">#9cffff</item> - - <item name="mircColor23">#002747</item> - <item name="mircColor35">#004074</item> - <item name="mircColor47">#0063b5</item> - <item name="mircColor59">#008cff</item> - <item name="mircColor71">#59b4ff</item> - <item name="mircColor83">#9cd3ff</item> - - <item name="mircColor24">#000047</item> - <item name="mircColor36">#000074</item> - <item name="mircColor48">#0000b5</item> - <item name="mircColor60">#0000ff</item> - <item name="mircColor72">#5959ff</item> - <item name="mircColor84">#9c9cff</item> - - <item name="mircColor25">#2e0047</item> - <item name="mircColor37">#4b0074</item> - <item name="mircColor49">#7500b5</item> - <item name="mircColor61">#a500ff</item> - <item name="mircColor73">#c459ff</item> - <item name="mircColor85">#dc9cff</item> - - <item name="mircColor26">#470047</item> - <item name="mircColor38">#740074</item> - <item name="mircColor50">#b500b5</item> - <item name="mircColor62">#ff00ff</item> - <item name="mircColor74">#ff66ff</item> - <item name="mircColor86">#ff9cff</item> - - <item name="mircColor27">#47002a</item> - <item name="mircColor39">#740045</item> - <item name="mircColor51">#b5006b</item> - <item name="mircColor63">#ff0098</item> - <item name="mircColor75">#ff59bc</item> - <item name="mircColor87">#ff94d3</item> - - <item name="mircColor88">#000000</item> - <item name="mircColor89">#131313</item> - <item name="mircColor90">#282828</item> - <item name="mircColor91">#363636</item> - <item name="mircColor92">#4d4d4d</item> - <item name="mircColor93">#656565</item> - <item name="mircColor94">#818181</item> - <item name="mircColor95">#9f9f9f</item> - <item name="mircColor96">#bcbcbc</item> - <item name="mircColor97">#e2e2e2</item> - <item name="mircColor98">#ffffff</item> </style> <style name="Theme.ChatTheme.Light.Auto" parent="Theme.ChatTheme.Light"> diff --git a/app/src/test/java/de/kuschku/quasseldroid/util/AvatarHelperTest.kt b/app/src/test/java/de/kuschku/quasseldroid/util/AvatarHelperTest.kt index f186fa80f..8c9984ad4 100644 --- a/app/src/test/java/de/kuschku/quasseldroid/util/AvatarHelperTest.kt +++ b/app/src/test/java/de/kuschku/quasseldroid/util/AvatarHelperTest.kt @@ -32,7 +32,7 @@ import org.threeten.bp.Instant class AvatarHelperTest { @Test fun testGravatarAvatars() { - val message = QuasselDatabase.DatabaseMessage( + val message = QuasselDatabase.MessageData( messageId = 1, time = Instant.now(), type = Message_Type.of(Message_Type.Plain).toInt(), @@ -69,7 +69,7 @@ class AvatarHelperTest { @Test fun testIrcCloudAvatars() { - val message = QuasselDatabase.DatabaseMessage( + val message = QuasselDatabase.MessageData( messageId = 1, time = Instant.now(), type = Message_Type.of(Message_Type.Plain).toInt(), @@ -106,7 +106,7 @@ class AvatarHelperTest { @Test fun testActualAvatars() { - val message = QuasselDatabase.DatabaseMessage( + val message = QuasselDatabase.MessageData( messageId = 1, time = Instant.now(), type = Message_Type.of(Message_Type.Plain).toInt(), diff --git a/lib/src/main/java/de/kuschku/libquassel/quassel/syncables/BufferSyncer.kt b/lib/src/main/java/de/kuschku/libquassel/quassel/syncables/BufferSyncer.kt index cb222c566..50410fcc3 100644 --- a/lib/src/main/java/de/kuschku/libquassel/quassel/syncables/BufferSyncer.kt +++ b/lib/src/main/java/de/kuschku/libquassel/quassel/syncables/BufferSyncer.kt @@ -27,12 +27,14 @@ import de.kuschku.libquassel.protocol.Type import de.kuschku.libquassel.quassel.BufferInfo import de.kuschku.libquassel.quassel.syncables.interfaces.IBufferSyncer import de.kuschku.libquassel.session.ISession +import de.kuschku.libquassel.session.NotificationManager import de.kuschku.libquassel.util.irc.IrcCaseMappers import io.reactivex.Observable import io.reactivex.subjects.BehaviorSubject class BufferSyncer constructor( - private val session: ISession + private val session: ISession, + private val notificationManager: NotificationManager? ) : SyncableObject(session.proxy, "BufferSyncer"), IBufferSyncer { fun lastSeenMsg(buffer: BufferId): MsgId = _lastSeenMsg[buffer] ?: 0 fun liveLastSeenMsg(buffer: BufferId): Observable<MsgId> = live_lastSeenMsg.map { @@ -165,11 +167,7 @@ class BufferSyncer constructor( } override fun mergeBuffersPermanently(buffer1: BufferId, buffer2: BufferId) { - _lastSeenMsg.remove(buffer2);live_lastSeenMsg.onNext(Unit) - _markerLines.remove(buffer2);live_markerLines.onNext(Unit) - _bufferActivities.remove(buffer2);live_bufferActivities.onNext(Unit) - _highlightCounts.remove(buffer2);live_highlightCounts.onNext(Unit) - _bufferInfos.remove(buffer2);live_bufferInfos.onNext(Unit) + removeBuffer(buffer2) } override fun removeBuffer(buffer: BufferId) { @@ -179,6 +177,7 @@ class BufferSyncer constructor( _highlightCounts.remove(buffer);live_highlightCounts.onNext(Unit) _bufferInfos.remove(buffer);live_bufferInfos.onNext(Unit) session.backlogManager?.removeBuffer(buffer) + notificationManager?.clear(buffer) } override fun renameBuffer(buffer: BufferId, newName: String) { @@ -210,6 +209,7 @@ class BufferSyncer constructor( _lastSeenMsg[buffer] = msgId live_lastSeenMsg.onNext(Unit) super.setLastSeenMsg(buffer, msgId) + notificationManager?.clear(buffer, msgId) } } diff --git a/lib/src/main/java/de/kuschku/libquassel/quassel/syncables/RpcHandler.kt b/lib/src/main/java/de/kuschku/libquassel/quassel/syncables/RpcHandler.kt index f9a31b307..c3ab526bf 100644 --- a/lib/src/main/java/de/kuschku/libquassel/quassel/syncables/RpcHandler.kt +++ b/lib/src/main/java/de/kuschku/libquassel/quassel/syncables/RpcHandler.kt @@ -28,13 +28,15 @@ import de.kuschku.libquassel.quassel.BufferInfo import de.kuschku.libquassel.quassel.syncables.interfaces.INetwork import de.kuschku.libquassel.quassel.syncables.interfaces.IRpcHandler import de.kuschku.libquassel.session.BacklogStorage +import de.kuschku.libquassel.session.NotificationManager import de.kuschku.libquassel.session.Session import de.kuschku.libquassel.util.helpers.deserializeString import java.nio.ByteBuffer class RpcHandler( override val session: Session, - private val backlogStorage: BacklogStorage + private val backlogStorage: BacklogStorage, + private val notificationManager: NotificationManager? ) : IRpcHandler { override fun displayStatusMsg(net: String, msg: String) { } @@ -61,6 +63,7 @@ class RpcHandler( override fun displayMsg(message: Message) { session.bufferSyncer.bufferInfoUpdated(message.bufferInfo) backlogStorage.storeMessages(session, message) + notificationManager?.processMessages(session, message) } override fun createIdentity(identity: Identity, additional: QVariantMap) = diff --git a/lib/src/main/java/de/kuschku/libquassel/session/NotificationManager.kt b/lib/src/main/java/de/kuschku/libquassel/session/NotificationManager.kt new file mode 100644 index 000000000..b6e4f4085 --- /dev/null +++ b/lib/src/main/java/de/kuschku/libquassel/session/NotificationManager.kt @@ -0,0 +1,30 @@ +/* + * Quasseldroid - Quassel client for Android + * + * Copyright (c) 2018 Janne Koschinski + * Copyright (c) 2018 The Quassel Project + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3 as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package de.kuschku.libquassel.session + +import de.kuschku.libquassel.protocol.BufferId +import de.kuschku.libquassel.protocol.Message +import de.kuschku.libquassel.protocol.MsgId + +interface NotificationManager { + fun init(session: Session) + fun processMessages(session: Session, vararg messages: Message) + fun clear(buffer: BufferId, lastRead: MsgId = Int.MAX_VALUE) +} 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 251c6b403..09cfb79cf 100644 --- a/lib/src/main/java/de/kuschku/libquassel/session/Session.kt +++ b/lib/src/main/java/de/kuschku/libquassel/session/Session.kt @@ -44,14 +44,14 @@ class Session( address: SocketAddress, private val handlerService: HandlerService, backlogStorage: BacklogStorage, + private val notificationManager: NotificationManager?, private var userData: Pair<String, String>, val disconnectFromCore: () -> Unit, exceptionHandler: (Throwable) -> Unit ) : ProtocolHandler(exceptionHandler), ISession { override val objectStorage: ObjectStorage = ObjectStorage(this) override val proxy: SignalProxy = this - override val features = Features(clientData.clientFeatures, - QuasselFeatures.empty()) + override val features = Features(clientData.clientFeatures, QuasselFeatures.empty()) override val sslSession get() = coreConnection.sslSession @@ -67,7 +67,7 @@ class Session( override val aliasManager = AliasManager(this) override val backlogManager = BacklogManager(this, backlogStorage) override val bufferViewManager = BufferViewManager(this) - override val bufferSyncer = BufferSyncer(this) + override val bufferSyncer = BufferSyncer(this, notificationManager) override val certManagers = mutableMapOf<IdentityId, CertManager>() override val coreInfo = CoreInfo(this) override val dccConfig = DccConfig(this) @@ -86,7 +86,7 @@ class Session( override val networkConfig = NetworkConfig(this) - override var rpcHandler: RpcHandler? = RpcHandler(this, backlogStorage) + override var rpcHandler: RpcHandler? = RpcHandler(this, backlogStorage, notificationManager) override val initStatus = BehaviorSubject.createDefault(0 to 0) @@ -230,6 +230,7 @@ class Session( config.handleBuffer(info, bufferSyncer) } } + notificationManager?.init(this) coreConnection.setState(ConnectionState.CONNECTED) dispatch(SignalProxyMessage.HeartBeat(Instant.now())) } 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 53562b631..83d3188ce 100644 --- a/lib/src/main/java/de/kuschku/libquassel/session/SessionManager.kt +++ b/lib/src/main/java/de/kuschku/libquassel/session/SessionManager.kt @@ -41,6 +41,7 @@ import javax.net.ssl.X509TrustManager class SessionManager( offlineSession: ISession, val backlogStorage: BacklogStorage, + val notificationManager: NotificationManager?, val handlerService: HandlerService, private val disconnectFromCore: () -> Unit, private val exceptionHandler: (Throwable) -> Unit @@ -129,6 +130,7 @@ class SessionManager( address, handlerService, backlogStorage, + notificationManager, userData, disconnectFromCore, exceptionHandler diff --git a/persistence/src/main/java/de/kuschku/quasseldroid/persistence/QuasselBacklogStorage.kt b/persistence/src/main/java/de/kuschku/quasseldroid/persistence/QuasselBacklogStorage.kt index f448a8853..160fc01cb 100644 --- a/persistence/src/main/java/de/kuschku/quasseldroid/persistence/QuasselBacklogStorage.kt +++ b/persistence/src/main/java/de/kuschku/quasseldroid/persistence/QuasselBacklogStorage.kt @@ -24,7 +24,6 @@ package de.kuschku.quasseldroid.persistence import de.kuschku.libquassel.protocol.BufferId import de.kuschku.libquassel.protocol.Message -import de.kuschku.libquassel.protocol.Message_Type import de.kuschku.libquassel.quassel.syncables.IgnoreListManager import de.kuschku.libquassel.session.BacklogStorage import de.kuschku.libquassel.session.Session @@ -43,11 +42,11 @@ class QuasselBacklogStorage(private val db: QuasselDatabase) : BacklogStorage { override fun storeMessages(session: Session, messages: Iterable<Message>, initialLoad: Boolean) { db.message().save(*messages.map { - QuasselDatabase.DatabaseMessage( + QuasselDatabase.MessageData( messageId = it.messageId, time = it.time, - type = it.type.value, - flag = it.flag.value, + type = it.type, + flag = it.flag, bufferId = it.bufferInfo.bufferId, sender = it.sender, senderPrefixes = it.senderPrefixes, @@ -81,14 +80,14 @@ class QuasselBacklogStorage(private val db: QuasselDatabase) : BacklogStorage { ) != IgnoreListManager.StrictnessType.UnmatchedStrictness } - private fun isIgnored(session: Session, message: QuasselDatabase.DatabaseMessage): Boolean { + private fun isIgnored(session: Session, message: QuasselDatabase.MessageData): Boolean { val bufferInfo = session.bufferSyncer.bufferInfo(message.bufferId) val bufferName = bufferInfo?.bufferName ?: "" val networkId = bufferInfo?.networkId ?: -1 val networkName = session.network(networkId)?.networkName() ?: "" return session.ignoreListManager.match( - message.content, message.sender, Message_Type.of(message.type), networkName, bufferName + message.content, message.sender, message.type, networkName, bufferName ) != IgnoreListManager.StrictnessType.UnmatchedStrictness } } diff --git a/persistence/src/main/java/de/kuschku/quasseldroid/persistence/QuasselDatabase.kt b/persistence/src/main/java/de/kuschku/quasseldroid/persistence/QuasselDatabase.kt index 2a35c3c41..bc7a596e9 100644 --- a/persistence/src/main/java/de/kuschku/quasseldroid/persistence/QuasselDatabase.kt +++ b/persistence/src/main/java/de/kuschku/quasseldroid/persistence/QuasselDatabase.kt @@ -29,72 +29,81 @@ import android.arch.persistence.room.* import android.arch.persistence.room.migration.Migration import android.content.Context import android.support.annotation.IntRange -import de.kuschku.libquassel.protocol.Message_Flag -import de.kuschku.libquassel.protocol.Message_Type -import de.kuschku.libquassel.protocol.MsgId +import de.kuschku.libquassel.protocol.* import de.kuschku.quasseldroid.persistence.QuasselDatabase.* import io.reactivex.Flowable import org.threeten.bp.Instant -@Database(entities = [DatabaseMessage::class, Filtered::class, SslValidityWhitelistEntry::class, SslHostnameWhitelistEntry::class], - version = 13) -@TypeConverters(DatabaseMessage.MessageTypeConverters::class) +@Database(entities = [MessageData::class, Filtered::class, SslValidityWhitelistEntry::class, SslHostnameWhitelistEntry::class, NotificationData::class], + version = 14) +@TypeConverters(MessageTypeConverter::class) abstract class QuasselDatabase : RoomDatabase() { abstract fun message(): MessageDao abstract fun filtered(): FilteredDao abstract fun validityWhitelist(): SslValidityWhitelistDao abstract fun hostnameWhitelist(): SslHostnameWhitelistDao + abstract fun notifications(): NotificationDao + + class MessageTypeConverter { + @TypeConverter + fun convertInstant(value: Long): Instant = Instant.ofEpochMilli(value) + + @TypeConverter + fun convertInstant(value: Instant) = value.toEpochMilli() + + @TypeConverter + fun convertBufferTypes(value: Buffer_Types) = value.toShort() + + @TypeConverter + fun convertBufferTypes(value: Short) = Buffer_Type.of(value) + + @TypeConverter + fun convertMessageTypes(value: Message_Types) = value.toInt() + + @TypeConverter + fun convertMessageTypes(value: Int) = Message_Type.of(value) + + @TypeConverter + fun convertMessageFlags(value: Message_Flags) = value.toInt() + + @TypeConverter + fun convertMessageFlags(value: Int) = Message_Flag.of(value) + } @Entity(tableName = "message", indices = [Index("bufferId"), Index("ignored")]) - data class DatabaseMessage( + data class MessageData( @PrimaryKey var messageId: Int, var time: Instant, - var type: Int, - var flag: Int, - var bufferId: Int, + var type: Message_Types, + var flag: Message_Flags, + var bufferId: BufferId, var sender: String, var senderPrefixes: String, var realName: String, var avatarUrl: String, var content: String, var ignored: Boolean - ) { - class MessageTypeConverters { - @TypeConverter - fun convertInstant(value: Long): Instant = Instant.ofEpochMilli(value) - - @TypeConverter - fun convertInstant(value: Instant) = value.toEpochMilli() - } - - override fun toString(): String { - return "Message(messageId=$messageId, time=$time, type=${Message_Type.of( - type - )}, flag=${Message_Flag.of( - flag - )}, bufferId=$bufferId, sender='$sender', senderPrefixes='$senderPrefixes', realName='$realName', avatarUrl='$avatarUrl', content='$content')" - } - } + ) @Dao interface MessageDao { @Query("SELECT * FROM message") - fun all(): List<DatabaseMessage> + fun all(): List<MessageData> @Query("SELECT * FROM message WHERE messageId = :messageId") - fun find(messageId: Int): DatabaseMessage? + fun find(messageId: Int): MessageData? @Query("SELECT * FROM message WHERE bufferId = :bufferId ORDER BY messageId ASC") - fun findByBufferId(bufferId: Int): List<DatabaseMessage> + fun findByBufferId(bufferId: Int): List<MessageData> @Query("SELECT * FROM message WHERE bufferId = :bufferId AND type & ~ :type > 0 AND ignored = 0 ORDER BY messageId DESC") - fun findByBufferIdPaged(bufferId: Int, type: Int): DataSource.Factory<Int, DatabaseMessage> + fun findByBufferIdPaged(bufferId: Int, type: Int): DataSource.Factory<Int, MessageData> @Query("SELECT * FROM message WHERE bufferId = :bufferId ORDER BY messageId DESC LIMIT 1") - fun findLastByBufferId(bufferId: Int): DatabaseMessage? + fun findLastByBufferId(bufferId: Int): MessageData? @Query("SELECT * FROM message WHERE bufferId = :bufferId ORDER BY messageId DESC LIMIT 1") - fun lastMsgId(bufferId: Int): LiveData<DatabaseMessage> + fun lastMsgId(bufferId: Int): LiveData<MessageData> @Query("SELECT messageId FROM message WHERE bufferId = :bufferId ORDER BY messageId ASC LIMIT 1") fun firstMsgId(bufferId: Int): Flowable<MsgId> @@ -103,10 +112,10 @@ abstract class QuasselDatabase : RoomDatabase() { fun firstVisibleMsgId(bufferId: Int, type: Int): MsgId? @Query("SELECT * FROM message WHERE bufferId = :bufferId ORDER BY messageId ASC LIMIT 1") - fun findFirstByBufferId(bufferId: Int): DatabaseMessage? + fun findFirstByBufferId(bufferId: Int): MessageData? @Insert(onConflict = OnConflictStrategy.REPLACE) - fun save(vararg entities: DatabaseMessage) + fun save(vararg entities: MessageData) @Query("UPDATE message SET bufferId = :bufferId1 WHERE bufferId = :bufferId2") fun merge(@IntRange(from = 0) bufferId1: Int, @IntRange(from = 0) bufferId2: Int) @@ -129,7 +138,7 @@ abstract class QuasselDatabase : RoomDatabase() { @Entity(tableName = "filtered", primaryKeys = ["accountId", "bufferId"]) data class Filtered( var accountId: Long, - var bufferId: Int, + var bufferId: BufferId, var filtered: Int ) @@ -215,6 +224,38 @@ abstract class QuasselDatabase : RoomDatabase() { fun clear() } + @Entity(tableName = "notification", indices = [Index("bufferId")]) + data class NotificationData( + @PrimaryKey var messageId: Int, + var time: Instant, + var type: Message_Types, + var flag: Message_Flags, + var bufferId: BufferId, + var bufferName: String, + var bufferType: Buffer_Types, + var networkId: NetworkId, + var sender: String, + var senderPrefixes: String, + var realName: String, + var avatarUrl: String, + var content: String + ) + + @Dao + interface NotificationDao { + @Insert(onConflict = OnConflictStrategy.REPLACE) + fun save(vararg entities: NotificationData) + + @Query("SELECT * FROM notification WHERE bufferId = :bufferId ORDER BY time ASC") + fun all(bufferId: BufferId): List<NotificationData> + + @Query("DELETE FROM notification WHERE bufferId = :bufferId AND messageId <= :messageId") + fun markRead(bufferId: BufferId, messageId: MsgId) + + @Query("DELETE FROM notification") + fun clear() + } + object Creator { private var database: QuasselDatabase? = null @@ -298,6 +339,12 @@ abstract class QuasselDatabase : RoomDatabase() { database.execSQL("create table ssl_validity_whitelist (fingerprint TEXT not null, ignoreDate INTEGER not null, primary key(fingerprint));") database.execSQL("create table ssl_hostname_whitelist (fingerprint TEXT not null, hostname TEXT not null, primary key(fingerprint, hostname));") } + }, + object : Migration(13, 14) { + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL("CREATE TABLE IF NOT EXISTS `notification` (`messageId` INTEGER NOT NULL, `time` INTEGER NOT NULL, `type` INTEGER NOT NULL, `flag` INTEGER NOT NULL, `bufferId` INTEGER NOT NULL, `bufferName` TEXT NOT NULL, `bufferType` INTEGER NOT NULL, `networkId` INTEGER NOT NULL, `sender` TEXT NOT NULL, `senderPrefixes` TEXT NOT NULL, `realName` TEXT NOT NULL, `avatarUrl` TEXT NOT NULL, `content` TEXT NOT NULL, PRIMARY KEY(`messageId`));") + database.execSQL("CREATE INDEX `index_notification_bufferId` ON `notification` (`bufferId`);") + } } ).build() } -- GitLab