From 166c71933b996e8f5cd0d0bf918c4c6002c9808c Mon Sep 17 00:00:00 2001 From: Janne Koschinski <janne@kuschku.de> Date: Sun, 10 Jun 2018 12:51:52 +0200 Subject: [PATCH] Implement channel links --- .../service/QuasselNotificationBackend.kt | 15 +--- .../quasseldroid/ui/chat/ChatActivity.kt | 68 +++++++++++++++++-- .../chat/info/channel/ChannelInfoFragment.kt | 3 +- .../ui/chat/info/user/UserInfoFragment.kt | 3 +- .../chat/messages/QuasselMessageRenderer.kt | 45 ++++++++---- .../util/irc/format/ContentFormatter.kt | 45 ++++++++++-- .../persistence/QuasselBacklogStorage.kt | 1 + .../persistence/QuasselDatabase.kt | 10 ++- 8 files changed, 150 insertions(+), 40 deletions(-) diff --git a/app/src/main/java/de/kuschku/quasseldroid/service/QuasselNotificationBackend.kt b/app/src/main/java/de/kuschku/quasseldroid/service/QuasselNotificationBackend.kt index 7c1a93138..eefcec978 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/service/QuasselNotificationBackend.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/service/QuasselNotificationBackend.kt @@ -49,6 +49,7 @@ import de.kuschku.quasseldroid.util.helper.loadWithFallbacks import de.kuschku.quasseldroid.util.helper.styledAttributes import de.kuschku.quasseldroid.util.irc.format.ContentFormatter import de.kuschku.quasseldroid.util.ui.TextDrawable +import de.kuschku.quasseldroid.viewmodel.EditorViewModel import javax.inject.Inject class QuasselNotificationBackend @Inject constructor( @@ -242,21 +243,11 @@ class QuasselNotificationBackend @Inject constructor( selfColor = selfColor )) } - val content = contentFormatter.formatContent(it.content, false) + val content = contentFormatter.formatContent(it.content, false, it.networkId) val nickName = HostmaskHelper.nick(it.sender) val senderColorIndex = SenderColorUtil.senderColor(nickName) - val rawInitial = nickName.trimStart('-', - '_', - '[', - ']', - '{', - '}', - '|', - '`', - '^', - '.', - '\\') + val rawInitial = nickName.trimStart(*EditorViewModel.IGNORED_CHARS) .firstOrNull() ?: nickName.firstOrNull() val initial = rawInitial?.toUpperCase().toString() val senderColor = when (messageSettings.colorizeNicknames) { diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/ChatActivity.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/ChatActivity.kt index c3af15170..140cc6156 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/ChatActivity.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/ChatActivity.kt @@ -52,6 +52,7 @@ import de.kuschku.libquassel.connection.QuasselSecurityException import de.kuschku.libquassel.protocol.Buffer_Type import de.kuschku.libquassel.protocol.Message import de.kuschku.libquassel.protocol.Message_Type +import de.kuschku.libquassel.protocol.NetworkId import de.kuschku.libquassel.protocol.message.HandshakeMessage import de.kuschku.libquassel.session.Error import de.kuschku.libquassel.util.Optional @@ -140,13 +141,13 @@ class ChatActivity : ServiceBoundActivity(), SharedPreferences.OnSharedPreferenc super.onNewIntent(intent) if (intent != null) { when { - intent.type == "text/plain" -> { + intent.type == "text/plain" -> { chatlineFragment?.replaceText(intent.getStringExtra(Intent.EXTRA_TEXT)) drawerLayout.closeDrawers() } - intent.hasExtra(KEY_BUFFER_ID) -> { + intent.hasExtra(KEY_BUFFER_ID) -> { viewModel.buffer.onNext(intent.getIntExtra(KEY_BUFFER_ID, -1)) - drawerLayout.closeDrawers() + viewModel.bufferOpened.onNext(Unit) if (intent.hasExtra(KEY_ACCOUNT_ID)) { val accountId = intent.getLongExtra(ChatActivity.KEY_ACCOUNT_ID, -1) if (accountId != this.accountId) { @@ -159,13 +160,51 @@ class ChatActivity : ServiceBoundActivity(), SharedPreferences.OnSharedPreferenc } } } - intent.hasExtra(KEY_AUTOCOMPLETE_TEXT) -> { + intent.hasExtra(KEY_AUTOCOMPLETE_TEXT) -> { chatlineFragment?.editorHelper?.appendText( intent.getStringExtra(KEY_AUTOCOMPLETE_TEXT), intent.getStringExtra(KEY_AUTOCOMPLETE_SUFFIX) ) drawerLayout.closeDrawers() } + intent.hasExtra(KEY_NETWORK_ID) && intent.hasExtra(KEY_CHANNEL) -> { + val networkId = intent.getIntExtra(KEY_NETWORK_ID, -1) + val channel = intent.getStringExtra(KEY_CHANNEL) + + viewModel.session.value?.orNull()?.also { session -> + val info = session.bufferSyncer?.find( + bufferName = channel, + networkId = networkId, + type = Buffer_Type.of(Buffer_Type.QueryBuffer) + ) + + if (info != null) { + ChatActivity.launch(this, bufferId = info.bufferId) + } else { + + viewModel.allBuffers.map { + listOfNotNull(it.find { + it.networkId == networkId && it.bufferName == channel + }) + }.filter { + it.isNotEmpty() + }.firstElement().toLiveData().observe(this, Observer { + it?.firstOrNull()?.let { info -> + ChatActivity.launch(this, bufferId = info.bufferId) + } + }) + + session.bufferSyncer?.find( + networkId = networkId, + type = Buffer_Type.of(Buffer_Type.StatusBuffer) + )?.let { statusInfo -> + session.rpcHandler?.sendInput( + statusInfo, "/join $channel" + ) + } + } + } + } } } } @@ -891,6 +930,8 @@ class ChatActivity : ServiceBoundActivity(), SharedPreferences.OnSharedPreferenc private const val KEY_AUTOCOMPLETE_SUFFIX = "autocomplete_suffix" private const val KEY_BUFFER_ID = "buffer_id" private const val KEY_ACCOUNT_ID = "account_id" + private const val KEY_NETWORK_ID = "network_id" + private const val KEY_CHANNEL = "channel" // Instance state keys private const val KEY_OPEN_BUFFER = "open_buffer" @@ -904,10 +945,19 @@ class ChatActivity : ServiceBoundActivity(), SharedPreferences.OnSharedPreferenc sharedText: CharSequence? = null, autoCompleteText: CharSequence? = null, autoCompleteSuffix: String? = null, + channel: String? = null, + networkId: NetworkId? = null, bufferId: Int? = null, - accountId: Int? = null + accountId: Long? = null ) = context.startActivity( - intent(context, sharedText, autoCompleteText, autoCompleteSuffix, bufferId) + intent(context, + sharedText, + autoCompleteText, + autoCompleteSuffix, + channel, + networkId, + bufferId, + accountId) ) fun intent( @@ -915,6 +965,8 @@ class ChatActivity : ServiceBoundActivity(), SharedPreferences.OnSharedPreferenc sharedText: CharSequence? = null, autoCompleteText: CharSequence? = null, autoCompleteSuffix: String? = null, + channel: String? = null, + networkId: NetworkId? = null, bufferId: Int? = null, accountId: Long? = null ) = Intent(context, ChatActivity::class.java).apply { @@ -934,6 +986,10 @@ class ChatActivity : ServiceBoundActivity(), SharedPreferences.OnSharedPreferenc putExtra(KEY_ACCOUNT_ID, accountId) } } + if (networkId != null && channel != null) { + putExtra(KEY_NETWORK_ID, networkId) + putExtra(KEY_CHANNEL, channel) + } } } } 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 7c82f433a..8671616b9 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 @@ -89,7 +89,8 @@ class ChannelInfoFragment : ServiceBoundFragment() { }.switchMap(IrcChannel::updates).toLiveData().observe(this, Observer { channel -> if (channel != null) { name.text = channel.name() - topic.text = contentFormatter.formatContent(channel.topic()) + topic.text = contentFormatter.formatContent(channel.topic(), + networkId = channel.network().networkId()) 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 0907e70fb..f169cb4c1 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 @@ -162,7 +162,8 @@ class UserInfoFragment : ServiceBoundFragment() { ) nick.text = user.nick - realName.text = contentFormatter.formatContent(user.realName ?: "") + realName.text = contentFormatter.formatContent(user.realName ?: "", + networkId = user.networkId) realName.visibleIf(!user.realName.isNullOrBlank() && user.realName != user.nick) awayMessage.text = user.awayMessage.nullIf { it.isNullOrBlank() } ?: SpannableString( 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 113af97d8..15d7c1785 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 @@ -46,6 +46,7 @@ import de.kuschku.quasseldroid.util.helper.visibleIf import de.kuschku.quasseldroid.util.irc.format.ContentFormatter import de.kuschku.quasseldroid.util.irc.format.IrcFormatDeserializer import de.kuschku.quasseldroid.util.ui.SpanFormatter +import de.kuschku.quasseldroid.viewmodel.EditorViewModel import de.kuschku.quasseldroid.viewmodel.data.FormattedMessage import org.threeten.bp.ZoneId import org.threeten.bp.format.DateTimeFormatter @@ -249,10 +250,12 @@ class QuasselMessageRenderer @Inject constructor( false )) } - val content = contentFormatter.formatContent(message.content.content, monochromeForeground) + val content = contentFormatter.formatContent(message.content.content, + monochromeForeground, + message.content.networkId) val nickName = HostmaskHelper.nick(message.content.sender) val senderColorIndex = SenderColorUtil.senderColor(nickName) - val rawInitial = nickName.trimStart('-', '_', '[', ']', '{', '}', '|', '`', '^', '.', '\\') + val rawInitial = nickName.trimStart(*EditorViewModel.IGNORED_CHARS) .firstOrNull() ?: nickName.firstOrNull() val initial = rawInitial?.toUpperCase().toString() val useSelfColor = when (messageSettings.colorizeNicknames) { @@ -286,7 +289,7 @@ class QuasselMessageRenderer @Inject constructor( Message_Type.Action -> { val nickName = HostmaskHelper.nick(message.content.sender) val senderColorIndex = SenderColorUtil.senderColor(nickName) - val rawInitial = nickName.trimStart('-', '_', '[', ']', '{', '}', '|', '`', '^', '.', '\\') + val rawInitial = nickName.trimStart(*EditorViewModel.IGNORED_CHARS) .firstOrNull() ?: nickName.firstOrNull() val initial = rawInitial?.toUpperCase().toString() val useSelfColor = when (messageSettings.colorizeNicknames) { @@ -305,7 +308,9 @@ class QuasselMessageRenderer @Inject constructor( context.getString(R.string.message_format_action), contentFormatter.formatPrefix(message.content.senderPrefixes), contentFormatter.formatNick(message.content.sender, self, monochromeForeground, false), - contentFormatter.formatContent(message.content.content, monochromeForeground) + contentFormatter.formatContent(message.content.content, + monochromeForeground, + message.content.networkId) ), avatarUrls = AvatarHelper.avatar(messageSettings, message.content, avatarSize), fallbackDrawable = colorContext.buildTextDrawable(initial, senderColor), @@ -323,7 +328,9 @@ class QuasselMessageRenderer @Inject constructor( context.getString(R.string.message_format_notice), contentFormatter.formatPrefix(message.content.senderPrefixes), contentFormatter.formatNick(message.content.sender, self, monochromeForeground, false), - contentFormatter.formatContent(message.content.content, monochromeForeground) + contentFormatter.formatContent(message.content.content, + monochromeForeground, + message.content.networkId) ), hasDayChange = message.hasDayChange, isMarkerLine = message.isMarkerLine, @@ -432,7 +439,9 @@ class QuasselMessageRenderer @Inject constructor( monochromeForeground, messageSettings.showHostmaskActions ), - contentFormatter.formatContent(message.content.content, monochromeForeground) + contentFormatter.formatContent(message.content.content, + monochromeForeground, + message.content.networkId) ) }, hasDayChange = message.hasDayChange, @@ -465,7 +474,9 @@ class QuasselMessageRenderer @Inject constructor( monochromeForeground, messageSettings.showHostmaskActions ), - contentFormatter.formatContent(message.content.content, monochromeForeground) + contentFormatter.formatContent(message.content.content, + monochromeForeground, + message.content.networkId) ) }, hasDayChange = message.hasDayChange, @@ -502,7 +513,9 @@ class QuasselMessageRenderer @Inject constructor( monochromeForeground, messageSettings.showHostmaskActions ), - contentFormatter.formatContent(reason, monochromeForeground) + contentFormatter.formatContent(reason, + monochromeForeground, + message.content.networkId) ) }, hasDayChange = message.hasDayChange, @@ -540,7 +553,9 @@ class QuasselMessageRenderer @Inject constructor( monochromeForeground, messageSettings.showHostmaskActions ), - contentFormatter.formatContent(reason, monochromeForeground) + contentFormatter.formatContent(reason, + monochromeForeground, + message.content.networkId) ) }, hasDayChange = message.hasDayChange, @@ -589,7 +604,9 @@ class QuasselMessageRenderer @Inject constructor( id = message.content.messageId, time = timeFormatter.format(message.content.time.atZone(zoneId)), dayChange = formatDayChange(message), - combined = contentFormatter.formatContent(message.content.content, monochromeForeground), + combined = contentFormatter.formatContent(message.content.content, + monochromeForeground, + message.content.networkId), hasDayChange = message.hasDayChange, isMarkerLine = message.isMarkerLine, isExpanded = message.isExpanded, @@ -599,7 +616,9 @@ class QuasselMessageRenderer @Inject constructor( id = message.content.messageId, time = timeFormatter.format(message.content.time.atZone(zoneId)), dayChange = formatDayChange(message), - combined = contentFormatter.formatContent(message.content.content, monochromeForeground), + combined = contentFormatter.formatContent(message.content.content, + monochromeForeground, + message.content.networkId), hasDayChange = message.hasDayChange, isMarkerLine = message.isMarkerLine, isExpanded = message.isExpanded, @@ -619,7 +638,9 @@ class QuasselMessageRenderer @Inject constructor( id = message.content.messageId, time = timeFormatter.format(message.content.time.atZone(zoneId)), dayChange = formatDayChange(message), - combined = contentFormatter.formatContent(message.content.content, monochromeForeground), + combined = contentFormatter.formatContent(message.content.content, + monochromeForeground, + message.content.networkId), hasDayChange = message.hasDayChange, isMarkerLine = message.isMarkerLine, isExpanded = message.isExpanded, 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 6cbd8fbbb..9437ecc90 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 @@ -25,13 +25,17 @@ import android.support.annotation.ColorInt import android.text.SpannableString import android.text.Spanned import android.text.TextPaint +import android.text.style.ClickableSpan import android.text.style.ForegroundColorSpan import android.text.style.StyleSpan import android.text.style.URLSpan +import android.view.View +import de.kuschku.libquassel.protocol.NetworkId import de.kuschku.libquassel.util.irc.HostmaskHelper import de.kuschku.libquassel.util.irc.SenderColorUtil import de.kuschku.quasseldroid.R import de.kuschku.quasseldroid.settings.MessageSettings +import de.kuschku.quasseldroid.ui.chat.ChatActivity import de.kuschku.quasseldroid.util.helper.styledAttributes import de.kuschku.quasseldroid.util.ui.SpanFormatter import org.intellij.lang.annotations.Language @@ -80,14 +84,33 @@ class ContentFormatter @Inject constructor( class QuasselURLSpan(text: String, private val highlight: Boolean) : URLSpan(text) { override fun updateDrawState(ds: TextPaint?) { if (ds != null) { - if (!highlight) - ds.color = ds.linkColor + if (!highlight) ds.color = ds.linkColor ds.isUnderlineText = true } } } - fun formatContent(content: String, highlight: Boolean = false): CharSequence { + class ChannelLinkSpan(private val networkId: NetworkId, private val text: String, + private val highlight: Boolean) : ClickableSpan() { + override fun updateDrawState(ds: TextPaint?) { + if (ds != null) { + if (!highlight) ds.color = ds.linkColor + ds.isUnderlineText = true + } + } + + override fun onClick(widget: View) { + ChatActivity.launch( + widget.context, + networkId = networkId, + channel = text + ) + } + } + + fun formatContent(content: String, + highlight: Boolean = false, + networkId: NetworkId?): CharSequence { val formattedText = ircFormatDeserializer.formatString(content, messageSettings.colorizeMirc) val text = SpannableString(formattedText) @@ -101,10 +124,18 @@ class ContentFormatter @Inject constructor( ) } } - /* - for (result in channelPattern.findAll(content)) { - text.setSpan(URLSpan(result.value), result.range.start, result.range.endInclusive, Spanned.SPAN_INCLUSIVE_INCLUSIVE)} - */ + + if (networkId != null) { + for (result in channelPattern.findAll(formattedText)) { + val group = result.groups[1] + if (group != null) { + text.setSpan(ChannelLinkSpan(networkId, group.value, highlight), + group.range.start, + group.range.endInclusive + 1, + Spanned.SPAN_INCLUSIVE_EXCLUSIVE) + } + } + } return text } 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 b79e27b9c..c429c4376 100644 --- a/persistence/src/main/java/de/kuschku/quasseldroid/persistence/QuasselBacklogStorage.kt +++ b/persistence/src/main/java/de/kuschku/quasseldroid/persistence/QuasselBacklogStorage.kt @@ -46,6 +46,7 @@ class QuasselBacklogStorage(private val db: QuasselDatabase) : BacklogStorage { type = it.type, flag = it.flag, bufferId = it.bufferInfo.bufferId, + networkId = it.bufferInfo.networkId, sender = it.sender, senderPrefixes = it.senderPrefixes, realName = it.realName, 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 f2bf858a6..cc98904f8 100644 --- a/persistence/src/main/java/de/kuschku/quasseldroid/persistence/QuasselDatabase.kt +++ b/persistence/src/main/java/de/kuschku/quasseldroid/persistence/QuasselDatabase.kt @@ -32,7 +32,7 @@ import io.reactivex.Flowable import org.threeten.bp.Instant @Database(entities = [MessageData::class, Filtered::class, SslValidityWhitelistEntry::class, SslHostnameWhitelistEntry::class, NotificationData::class], - version = 14) + version = 15) @TypeConverters(MessageTypeConverter::class) abstract class QuasselDatabase : RoomDatabase() { abstract fun message(): MessageDao @@ -48,6 +48,7 @@ abstract class QuasselDatabase : RoomDatabase() { var type: Message_Types, var flag: Message_Flags, var bufferId: BufferId, + var networkId: NetworkId, var sender: String, var senderPrefixes: String, var realName: String, @@ -323,6 +324,13 @@ abstract class QuasselDatabase : RoomDatabase() { 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`);") } + }, + object : Migration(14, 15) { + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL( + "ALTER TABLE message ADD networkId INT DEFAULT 0 NOT NULL;" + ) + } } ).build() } -- GitLab