From 3fca452b0e7905191b98400c87c9d4fd1764e461 Mon Sep 17 00:00:00 2001 From: Janne Koschinski <janne@kuschku.de> Date: Thu, 14 Feb 2019 16:07:07 +0100 Subject: [PATCH] Implement attachments --- app/build.gradle.kts | 1 + app/src/main/AndroidManifest.xml | 15 +- .../quasseldroid/dagger/ActivityModule.kt | 6 + .../ui/chat/messages/MessageAdapter.kt | 47 ++++ .../ui/chat/messages/MessageListFragment.kt | 38 +++- .../chat/messages/QuasselMessageRenderer.kt | 73 ++++-- .../info/message/MessageAttachmentAdapter.kt | 103 +++++++++ .../ui/info/message/MessageInfoActivity.kt | 41 ++++ .../ui/info/message/MessageInfoFragment.kt | 124 ++++++++++ .../message/MessageInfoFragmentProvider.kt | 34 +++ .../util/attachments/AttachmentApi.kt | 30 +++ .../util/ui/view/MessageAttachmentView.kt | 211 ++++++++++++++++++ app/src/main/res/layout/info_message.xml | 23 ++ .../res/layout/widget_chatmessage_action.xml | 63 +++--- .../res/layout/widget_chatmessage_info.xml | 62 +++-- .../res/layout/widget_chatmessage_notice.xml | 60 +++-- .../res/layout/widget_chatmessage_plain.xml | 6 +- .../res/layout/widget_message_attachment.xml | 173 ++++++++++++++ .../layout/widget_message_attachment_item.xml | 23 ++ app/src/main/res/menu/context_messages.xml | 5 + app/src/main/res/values/strings.xml | 1 + gradle.properties | 4 +- .../de/kuschku/libquassel/protocol/Message.kt | 3 +- .../primitive/serializer/MessageSerializer.kt | 4 + .../libquassel/quassel/ExtendedFeature.kt | 4 +- .../persistence/dao/MessageDao.kt | 5 +- .../persistence/db/QuasselDatabase.kt | 7 +- .../persistence/models/MessageData.kt | 3 + .../persistence/util/QuasselBacklogStorage.kt | 1 + viewmodel/build.gradle.kts | 1 + .../util/attachment/AttachmentData.kt | 55 +++++ .../util/attachment/AttachmentDataField.kt | 26 +++ .../viewmodel/data/FormattedMessage.kt | 4 + 33 files changed, 1150 insertions(+), 106 deletions(-) create mode 100644 app/src/main/java/de/kuschku/quasseldroid/ui/info/message/MessageAttachmentAdapter.kt create mode 100644 app/src/main/java/de/kuschku/quasseldroid/ui/info/message/MessageInfoActivity.kt create mode 100644 app/src/main/java/de/kuschku/quasseldroid/ui/info/message/MessageInfoFragment.kt create mode 100644 app/src/main/java/de/kuschku/quasseldroid/ui/info/message/MessageInfoFragmentProvider.kt create mode 100644 app/src/main/java/de/kuschku/quasseldroid/util/attachments/AttachmentApi.kt create mode 100644 app/src/main/java/de/kuschku/quasseldroid/util/ui/view/MessageAttachmentView.kt create mode 100644 app/src/main/res/layout/info_message.xml create mode 100644 app/src/main/res/layout/widget_message_attachment.xml create mode 100644 app/src/main/res/layout/widget_message_attachment_item.xml create mode 100644 viewmodel/src/main/java/de/kuschku/quasseldroid/util/attachment/AttachmentData.kt create mode 100644 viewmodel/src/main/java/de/kuschku/quasseldroid/util/attachment/AttachmentDataField.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 907d1f2e6..ca7dc1b02 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -137,6 +137,7 @@ dependencies { implementation("commons-codec", "commons-codec", "1.11") implementation("com.squareup.retrofit2", "retrofit", "2.5.0") implementation("com.squareup.retrofit2", "converter-gson", "2.5.0") + implementation("com.squareup.retrofit2", "adapter-rxjava2", "2.5.0") withVersion("10.0.0") { implementation("com.jakewharton", "butterknife", version) kapt("com.jakewharton", "butterknife-compiler", version) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 6caf75e7f..63784789b 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -39,7 +39,8 @@ android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true" - android:theme="@style/Theme.SplashTheme"> + android:theme="@style/Theme.SplashTheme" + android:usesCleartextTraffic="true"> <meta-data android:name="WindowManagerPreference:FreeformWindowSize" @@ -112,6 +113,18 @@ android:exported="false" android:label="@string/label_info_certificate" android:windowSoftInputMode="adjustResize" /> + <activity + android:name="de.kuschku.quasseldroid.ui.info.message.MessageInfoActivity" + android:exported="true" + android:label="@string/label_info_message" + android:windowSoftInputMode="adjustResize"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <action android:name="android.intent.action.VIEW" /> + + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> <!-- Core Settings --> <activity diff --git a/app/src/main/java/de/kuschku/quasseldroid/dagger/ActivityModule.kt b/app/src/main/java/de/kuschku/quasseldroid/dagger/ActivityModule.kt index 3feea1e69..7427678b6 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/dagger/ActivityModule.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/dagger/ActivityModule.kt @@ -77,6 +77,8 @@ import de.kuschku.quasseldroid.ui.info.channellist.ChannelListActivity import de.kuschku.quasseldroid.ui.info.channellist.ChannelListFragmentProvider import de.kuschku.quasseldroid.ui.info.core.CoreInfoActivity import de.kuschku.quasseldroid.ui.info.core.CoreInfoFragmentProvider +import de.kuschku.quasseldroid.ui.info.message.MessageInfoActivity +import de.kuschku.quasseldroid.ui.info.message.MessageInfoFragmentProvider import de.kuschku.quasseldroid.ui.info.topic.TopicActivity import de.kuschku.quasseldroid.ui.info.user.UserInfoActivity import de.kuschku.quasseldroid.ui.info.user.UserInfoFragmentProvider @@ -123,6 +125,10 @@ abstract class ActivityModule { @ContributesAndroidInjector(modules = [CertificateInfoFragmentProvider::class, SettingsModule::class, DatabaseModule::class, ActivityBaseModule::class]) abstract fun bindCertificateInfoActivity(): CertificateInfoActivity + @ActivityScope + @ContributesAndroidInjector(modules = [MessageInfoFragmentProvider::class, SettingsModule::class, DatabaseModule::class, ActivityBaseModule::class]) + abstract fun bindMessageInfoActivity(): MessageInfoActivity + // Client Settings @ActivityScope 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 bbee9923a..52a07dd04 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 @@ -31,9 +31,11 @@ import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.RecyclerView import butterknife.BindView import butterknife.ButterKnife +import com.bumptech.glide.load.engine.DiskCacheStrategy import de.kuschku.libquassel.protocol.Message_Flag import de.kuschku.libquassel.protocol.Message_Type import de.kuschku.libquassel.util.flag.hasFlag +import de.kuschku.quasseldroid.GlideApp import de.kuschku.quasseldroid.R import de.kuschku.quasseldroid.persistence.models.MessageData import de.kuschku.quasseldroid.settings.MessageSettings @@ -42,6 +44,7 @@ import de.kuschku.quasseldroid.util.helper.loadAvatars import de.kuschku.quasseldroid.util.helper.visibleIf import de.kuschku.quasseldroid.util.ui.BetterLinkMovementMethod import de.kuschku.quasseldroid.util.ui.DoubleClickHelper +import de.kuschku.quasseldroid.util.ui.view.MessageAttachmentView import de.kuschku.quasseldroid.viewmodel.data.FormattedMessage import javax.inject.Inject @@ -225,6 +228,10 @@ class MessageAdapter @Inject constructor( @JvmField var combined: TextView? = null + @BindView(R.id.attachment) + @JvmField + var attachment: MessageAttachmentView? = null + private var message: FormattedMessage? = null private var original: MessageData? = null @@ -278,6 +285,46 @@ class MessageAdapter @Inject constructor( content?.text = message.content combined?.text = message.combined + attachment?.visibleIf(message.attachment != null) + attachment?.post { + attachment?.apply { + val attachment = message.attachment + reinitViews() + + if (attachment != null) { + setLink(attachment.fromUrl) + setColor(attachment.color) + GlideApp.with(itemView) + .load(attachment.authorIcon) + .diskCacheStrategy(DiskCacheStrategy.ALL) + .into(authorIconTarget) + setAuthor(attachment.authorName) + setTitle(attachment.title) + setDescription(attachment.text) + if (false) { + GlideApp.with(itemView) + .clear(thumbnailTarget) + GlideApp.with(itemView) + .load(attachment.imageUrl) + .diskCacheStrategy(DiskCacheStrategy.ALL) + .into(previewTarget) + } else { + GlideApp.with(itemView) + .clear(previewTarget) + GlideApp.with(itemView) + .load(attachment.imageUrl) + .diskCacheStrategy(DiskCacheStrategy.ALL) + .into(thumbnailTarget) + } + GlideApp.with(itemView) + .load(attachment.serviceIcon) + .diskCacheStrategy(DiskCacheStrategy.ALL) + .into(serviceIconTarget) + setService(attachment.serviceName) + } + } + } + this.messageContainer?.isSelected = message.isSelected if (hasDayChange) daychange?.text = message.dayChange 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 ed05bd91a..6d303ae9b 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 @@ -65,6 +65,7 @@ import de.kuschku.quasseldroid.settings.AutoCompleteSettings import de.kuschku.quasseldroid.settings.BacklogSettings import de.kuschku.quasseldroid.settings.MessageSettings import de.kuschku.quasseldroid.ui.chat.ChatActivity +import de.kuschku.quasseldroid.ui.info.message.MessageInfoActivity import de.kuschku.quasseldroid.ui.info.user.UserInfoActivity import de.kuschku.quasseldroid.util.Patterns import de.kuschku.quasseldroid.util.avatars.AvatarHelper @@ -122,7 +123,16 @@ class MessageListFragment : ServiceBoundFragment() { private val actionModeCallback = object : ActionMode.Callback { override fun onActionItemClicked(mode: ActionMode?, item: MenuItem?) = when (item?.itemId) { - R.id.action_user_info -> { + R.id.action_message_info -> { + viewModel.selectedMessages.value.values.firstOrNull()?.let { msg -> + MessageInfoActivity.launch( + requireContext(), + messageId = msg.original.messageId + ) + true + } ?: false + } + R.id.action_user_info -> { viewModel.selectedMessages.value.values.firstOrNull()?.let { msg -> viewModel.session.value?.orNull()?.bufferSyncer?.let { bufferSyncer -> viewModel.bufferData.value?.info?.let(BufferInfo::networkId)?.let { networkId -> @@ -143,7 +153,7 @@ class MessageListFragment : ServiceBoundFragment() { true } ?: false } - R.id.action_copy -> { + R.id.action_copy -> { val builder = SpannableStringBuilder() viewModel.selectedMessages.value.values.asSequence().sortedBy { it.original.messageId @@ -172,7 +182,7 @@ class MessageListFragment : ServiceBoundFragment() { actionMode?.finish() true } - R.id.action_share -> { + R.id.action_share -> { val builder = SpannableStringBuilder() viewModel.selectedMessages.value.values.asSequence().sortedBy { it.original.messageId @@ -207,7 +217,7 @@ class MessageListFragment : ServiceBoundFragment() { actionMode?.finish() true } - else -> false + else -> false } override fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean { @@ -250,8 +260,14 @@ class MessageListFragment : ServiceBoundFragment() { if (actionMode != null) { when (viewModel.selectedMessagesToggle(msg.original.messageId, msg)) { 0 -> actionMode?.finish() - 1 -> actionMode?.menu?.findItem(R.id.action_user_info)?.isVisible = true - else -> actionMode?.menu?.findItem(R.id.action_user_info)?.isVisible = false + 1 -> { + actionMode?.menu?.findItem(R.id.action_user_info)?.isVisible = true + //actionMode?.menu?.findItem(R.id.action_message_info)?.isVisible = true + } + else -> { + actionMode?.menu?.findItem(R.id.action_user_info)?.isVisible = false + //actionMode?.menu?.findItem(R.id.action_message_info)?.isVisible = false + } } } else if (msg.hasSpoilers) { val value = viewModel.expandedMessages.value @@ -267,8 +283,14 @@ class MessageListFragment : ServiceBoundFragment() { } when (viewModel.selectedMessagesToggle(msg.original.messageId, msg)) { 0 -> actionMode?.finish() - 1 -> actionMode?.menu?.findItem(R.id.action_user_info)?.isVisible = true - else -> actionMode?.menu?.findItem(R.id.action_user_info)?.isVisible = false + 1 -> { + actionMode?.menu?.findItem(R.id.action_user_info)?.isVisible = true + actionMode?.menu?.findItem(R.id.action_message_info)?.isVisible = true + } + else -> { + actionMode?.menu?.findItem(R.id.action_user_info)?.isVisible = false + actionMode?.menu?.findItem(R.id.action_message_info)?.isVisible = false + } } } if (autoCompleteSettings.senderDoubleClick) 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 e1dfd0eb8..1a9edb321 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 @@ -30,6 +30,8 @@ import android.view.Gravity import android.view.View import android.widget.FrameLayout import android.widget.LinearLayout +import com.google.gson.GsonBuilder +import com.google.gson.JsonSyntaxException import de.kuschku.libquassel.protocol.Message.MessageType.* import de.kuschku.libquassel.protocol.Message_Flag import de.kuschku.libquassel.protocol.Message_Type @@ -40,7 +42,9 @@ import de.kuschku.quasseldroid.R import de.kuschku.quasseldroid.persistence.models.MessageData import de.kuschku.quasseldroid.settings.MessageSettings import de.kuschku.quasseldroid.util.ColorContext +import de.kuschku.quasseldroid.util.attachment.AttachmentData import de.kuschku.quasseldroid.util.avatars.AvatarHelper +import de.kuschku.quasseldroid.util.helper.fromJson import de.kuschku.quasseldroid.util.helper.styledAttributes import de.kuschku.quasseldroid.util.helper.visibleIf import de.kuschku.quasseldroid.util.irc.format.ContentFormatter @@ -103,6 +107,8 @@ class QuasselMessageRenderer @Inject constructor( private val zoneId = ZoneId.systemDefault() + private val gson = GsonBuilder().setLenient().create() + override fun layout(type: Message_Type?, hasHighlight: Boolean, isFollowUp: Boolean, @@ -244,6 +250,13 @@ class QuasselMessageRenderer @Inject constructor( val self = message.content.flag.hasFlag(Message_Flag.Self) val highlight = message.content.flag.hasFlag(Message_Flag.Highlight) val monochromeForeground = highlight && monochromeHighlights + + val parsedAttachment: AttachmentData? = try { + gson.fromJson<AttachmentData>(message.content.attachments) + } catch (ignored: JsonSyntaxException) { + null + } + return when (message.content.type.enabledValues().firstOrNull()) { Message_Type.Plain -> { val realName = ircFormatDeserializer.formatString(message.content.realName, @@ -292,7 +305,8 @@ class QuasselMessageRenderer @Inject constructor( isMarkerLine = message.isMarkerLine, isExpanded = message.isExpanded, isSelected = message.isSelected, - hasSpoilers = hasSpoilers + hasSpoilers = hasSpoilers, + attachment = parsedAttachment ) } Message_Type.Action -> { @@ -330,7 +344,8 @@ class QuasselMessageRenderer @Inject constructor( isMarkerLine = message.isMarkerLine, isExpanded = message.isExpanded, isSelected = message.isSelected, - hasSpoilers = hasSpoilers + hasSpoilers = hasSpoilers, + attachment = parsedAttachment ) } Message_Type.Notice -> { @@ -352,7 +367,8 @@ class QuasselMessageRenderer @Inject constructor( isMarkerLine = message.isMarkerLine, isExpanded = message.isExpanded, isSelected = message.isSelected, - hasSpoilers = hasSpoilers + hasSpoilers = hasSpoilers, + attachment = parsedAttachment ) } Message_Type.Nick -> { @@ -395,7 +411,8 @@ class QuasselMessageRenderer @Inject constructor( isMarkerLine = message.isMarkerLine, isExpanded = message.isExpanded, isSelected = message.isSelected, - hasSpoilers = false + hasSpoilers = false, + attachment = parsedAttachment ) } Message_Type.Mode -> FormattedMessage( @@ -412,7 +429,8 @@ class QuasselMessageRenderer @Inject constructor( isMarkerLine = message.isMarkerLine, isExpanded = message.isExpanded, isSelected = message.isSelected, - hasSpoilers = false + hasSpoilers = false, + attachment = parsedAttachment ) Message_Type.Join -> FormattedMessage( original = message.content, @@ -433,7 +451,8 @@ class QuasselMessageRenderer @Inject constructor( isMarkerLine = message.isMarkerLine, isExpanded = message.isExpanded, isSelected = message.isSelected, - hasSpoilers = false + hasSpoilers = false, + attachment = parsedAttachment ) Message_Type.Part -> { val (content, hasSpoilers) = if (message.content.content.isBlank()) { @@ -480,7 +499,8 @@ class QuasselMessageRenderer @Inject constructor( isMarkerLine = message.isMarkerLine, isExpanded = message.isExpanded, isSelected = message.isSelected, - hasSpoilers = hasSpoilers + hasSpoilers = hasSpoilers, + attachment = parsedAttachment ) } Message_Type.Quit -> { @@ -528,7 +548,8 @@ class QuasselMessageRenderer @Inject constructor( isMarkerLine = message.isMarkerLine, isExpanded = message.isExpanded, isSelected = message.isSelected, - hasSpoilers = hasSpoilers + hasSpoilers = hasSpoilers, + attachment = parsedAttachment ) } Message_Type.Kick -> { @@ -575,7 +596,8 @@ class QuasselMessageRenderer @Inject constructor( isMarkerLine = message.isMarkerLine, isExpanded = message.isExpanded, isSelected = message.isSelected, - hasSpoilers = hasSpoilers + hasSpoilers = hasSpoilers, + attachment = parsedAttachment ) } Message_Type.Kill -> { @@ -622,7 +644,8 @@ class QuasselMessageRenderer @Inject constructor( isMarkerLine = message.isMarkerLine, isExpanded = message.isExpanded, isSelected = message.isSelected, - hasSpoilers = hasSpoilers + hasSpoilers = hasSpoilers, + attachment = parsedAttachment ) } Message_Type.NetsplitJoin -> { @@ -649,7 +672,8 @@ class QuasselMessageRenderer @Inject constructor( isMarkerLine = message.isMarkerLine, isExpanded = message.isExpanded, isSelected = message.isSelected, - hasSpoilers = false + hasSpoilers = false, + attachment = parsedAttachment ) } Message_Type.NetsplitQuit -> { @@ -676,7 +700,8 @@ class QuasselMessageRenderer @Inject constructor( isMarkerLine = message.isMarkerLine, isExpanded = message.isExpanded, isSelected = message.isSelected, - hasSpoilers = false + hasSpoilers = false, + attachment = parsedAttachment ) } Message_Type.Server, @@ -695,7 +720,8 @@ class QuasselMessageRenderer @Inject constructor( isMarkerLine = message.isMarkerLine, isExpanded = message.isExpanded, isSelected = message.isSelected, - hasSpoilers = hasSpoilers + hasSpoilers = hasSpoilers, + attachment = parsedAttachment ) } Message_Type.Topic -> { @@ -712,7 +738,8 @@ class QuasselMessageRenderer @Inject constructor( isMarkerLine = message.isMarkerLine, isExpanded = message.isExpanded, isSelected = message.isSelected, - hasSpoilers = hasSpoilers + hasSpoilers = hasSpoilers, + attachment = parsedAttachment ) } Message_Type.DayChange -> FormattedMessage( @@ -724,7 +751,8 @@ class QuasselMessageRenderer @Inject constructor( isMarkerLine = false, isExpanded = false, isSelected = false, - hasSpoilers = false + hasSpoilers = false, + attachment = parsedAttachment ) Message_Type.Invite -> { val (content, hasSpoilers) = contentFormatter.formatContent(message.content.content, @@ -740,7 +768,8 @@ class QuasselMessageRenderer @Inject constructor( isMarkerLine = message.isMarkerLine, isExpanded = message.isExpanded, isSelected = message.isSelected, - hasSpoilers = hasSpoilers + hasSpoilers = hasSpoilers, + attachment = parsedAttachment ) } else -> FormattedMessage( @@ -763,13 +792,15 @@ class QuasselMessageRenderer @Inject constructor( isMarkerLine = message.isMarkerLine, isExpanded = message.isExpanded, isSelected = message.isSelected, - hasSpoilers = false + hasSpoilers = false, + attachment = parsedAttachment ) } } - private fun formatDayChange( - message: DisplayMessage) = - if (message.hasDayChange) dateFormatter.format(message.content.time.atZone(zoneId).truncatedTo( - ChronoUnit.DAYS)) else null + private fun formatDayChange(message: DisplayMessage) = + if (message.hasDayChange) + dateFormatter.format(message.content.time.atZone(zoneId).truncatedTo(ChronoUnit.DAYS)) + else + null } diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/info/message/MessageAttachmentAdapter.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/info/message/MessageAttachmentAdapter.kt new file mode 100644 index 000000000..8551ee4e6 --- /dev/null +++ b/app/src/main/java/de/kuschku/quasseldroid/ui/info/message/MessageAttachmentAdapter.kt @@ -0,0 +1,103 @@ +/* + * Quasseldroid - Quassel client for Android + * + * Copyright (c) 2019 Janne Koschinski + * Copyright (c) 2019 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.ui.info.message + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import butterknife.BindView +import butterknife.ButterKnife +import com.bumptech.glide.load.engine.DiskCacheStrategy +import de.kuschku.quasseldroid.GlideApp +import de.kuschku.quasseldroid.R +import de.kuschku.quasseldroid.util.attachment.AttachmentData +import de.kuschku.quasseldroid.util.ui.view.MessageAttachmentView + +class MessageAttachmentAdapter(private val showLarge: Boolean) : + ListAdapter<AttachmentData, MessageAttachmentAdapter.MessageAttachmentViewHolder>( + object : DiffUtil.ItemCallback<AttachmentData>() { + override fun areItemsTheSame(oldItem: AttachmentData, newItem: AttachmentData) = + oldItem.fromUrl == newItem.fromUrl + + override fun areContentsTheSame(oldItem: AttachmentData, newItem: AttachmentData) = + oldItem == newItem + } + ) { + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MessageAttachmentViewHolder { + return MessageAttachmentViewHolder( + LayoutInflater.from(parent.context) + .inflate(R.layout.widget_message_attachment_item, parent, false), + showLarge + ) + } + + override fun onBindViewHolder(holder: MessageAttachmentViewHolder, position: Int) { + holder.bind(getItem(position)) + } + + + class MessageAttachmentViewHolder(itemView: View, private val showLarge: Boolean) : + RecyclerView.ViewHolder(itemView) { + @BindView(R.id.view) + lateinit var attachmentView: MessageAttachmentView + + init { + ButterKnife.bind(this, itemView) + } + + fun bind(attachment: AttachmentData) { + attachmentView.reinitViews() + + attachmentView.setLink(attachment.fromUrl) + attachmentView.setColor(attachment.color) + GlideApp.with(itemView) + .load(attachment.authorIcon) + .diskCacheStrategy(DiskCacheStrategy.ALL) + .into(attachmentView.authorIconTarget) + attachmentView.setAuthor(attachment.authorName) + //attachmentView.setAuthorLink(attachment.author_link) + attachmentView.setTitle(attachment.title) + attachmentView.setDescription(attachment.text) + if (showLarge) { + GlideApp.with(itemView) + .clear(attachmentView.thumbnailTarget) + GlideApp.with(itemView) + .load(attachment.imageUrl) + .diskCacheStrategy(DiskCacheStrategy.ALL) + .into(attachmentView.previewTarget) + } else { + GlideApp.with(itemView) + .clear(attachmentView.previewTarget) + GlideApp.with(itemView) + .load(attachment.imageUrl) + .diskCacheStrategy(DiskCacheStrategy.ALL) + .into(attachmentView.thumbnailTarget) + } + GlideApp.with(itemView) + .load(attachment.serviceIcon) + .diskCacheStrategy(DiskCacheStrategy.ALL) + .into(attachmentView.serviceIconTarget) + attachmentView.setService(attachment.serviceName) + } + } +} diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/info/message/MessageInfoActivity.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/info/message/MessageInfoActivity.kt new file mode 100644 index 000000000..8e793f0f1 --- /dev/null +++ b/app/src/main/java/de/kuschku/quasseldroid/ui/info/message/MessageInfoActivity.kt @@ -0,0 +1,41 @@ +/* + * Quasseldroid - Quassel client for Android + * + * Copyright (c) 2019 Janne Koschinski + * Copyright (c) 2019 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.ui.info.message + +import android.content.Context +import android.content.Intent +import de.kuschku.libquassel.protocol.MsgId +import de.kuschku.quasseldroid.util.ui.settings.ServiceBoundSettingsActivity + +class MessageInfoActivity : ServiceBoundSettingsActivity(MessageInfoFragment()) { + companion object { + fun launch( + context: Context, + messageId: MsgId + ) = context.startActivity(intent(context, messageId)) + + fun intent( + context: Context, + messageId: MsgId + ) = Intent(context, MessageInfoActivity::class.java).apply { + putExtra("messageId", messageId.id) + } + } +} diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/info/message/MessageInfoFragment.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/info/message/MessageInfoFragment.kt new file mode 100644 index 000000000..cf8df1fb7 --- /dev/null +++ b/app/src/main/java/de/kuschku/quasseldroid/ui/info/message/MessageInfoFragment.kt @@ -0,0 +1,124 @@ +/* + * Quasseldroid - Quassel client for Android + * + * Copyright (c) 2019 Janne Koschinski + * Copyright (c) 2019 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.ui.info.message + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.lifecycle.Observer +import androidx.recyclerview.widget.DefaultItemAnimator +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import butterknife.BindView +import butterknife.ButterKnife +import com.google.gson.Gson +import de.kuschku.libquassel.protocol.MsgId +import de.kuschku.libquassel.util.Optional +import de.kuschku.quasseldroid.R +import de.kuschku.quasseldroid.persistence.dao.find +import de.kuschku.quasseldroid.persistence.db.QuasselDatabase +import de.kuschku.quasseldroid.util.attachment.AttachmentData +import de.kuschku.quasseldroid.util.attachments.AttachmentApi +import de.kuschku.quasseldroid.util.helper.combineLatest +import de.kuschku.quasseldroid.util.helper.toLiveData +import de.kuschku.quasseldroid.util.service.ServiceBoundFragment +import de.kuschku.quasseldroid.util.ui.BetterLinkMovementMethod +import de.kuschku.quasseldroid.util.ui.LinkLongClickMenuHelper +import retrofit2.Retrofit +import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory +import retrofit2.converter.gson.GsonConverterFactory +import javax.inject.Inject + +class MessageInfoFragment : ServiceBoundFragment() { + @BindView(R.id.list) + lateinit var list: RecyclerView + + @Inject + lateinit var database: QuasselDatabase + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle?): View? { + val view = inflater.inflate(R.layout.info_message, container, false) + ButterKnife.bind(this, view) + + val adapter = MessageAttachmentAdapter(true) + list.adapter = adapter + list.layoutManager = LinearLayoutManager(list.context) + list.itemAnimator = DefaultItemAnimator() + + viewModel.session.toLiveData().observe(this, Observer { + runInBackground { + val movementMethod = BetterLinkMovementMethod.newInstance() + movementMethod.setOnLinkLongClickListener(LinkLongClickMenuHelper()) + + val messageId = MsgId(arguments?.getLong("messageId") ?: -1) + val message = database.message().find(messageId) + + val retrofit = Retrofit.Builder() + .baseUrl("http://192.168.178.29:8080/") + .addConverterFactory(GsonConverterFactory.create()) + .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) + .build() + + val api = retrofit.create(AttachmentApi::class.java) + + val gson = Gson() + + val ircCloudEmbeds = listOf( + "{\"ts\":1448400805,\"title_link\":\"https://medium.com/slack-developer-blog/everything-you-ever-wanted-to-know-about-unfurling-but-were-afraid-to-ask-or-how-to-make-your-e64b4bb9254\",\"title\":\"Everything you ever wanted to know about unfurling but were afraid to ask /or/ How to make your…\",\"text\":\"Let’s start with the most obvious question first. This is what an “unfurl†is:\",\"service_name\":\"Medium\",\"service_icon\":\"https://cdn-images-1.medium.com/fit/c/304/304/1*a1O3xhOq8KWSibZF6Ze5xQ.png\",\"image_width\":170,\"image_url\":\"https://cdn-images-1.medium.com/max/1200/1*QOMaDLcO8rExD0ctBV3BWg.png\",\"image_height\":250,\"image_bytes\":695475,\"from_url\":\"https://medium.com/slack-developer-blog/everything-you-ever-wanted-to-know-about-unfurling-but-were-afraid-to-ask-or-how-to-make-your-e64b4bb9254\",\"fields\":[{\"value\":\"10 min read\",\"title\":\"Reading time\",\"short\":true}]}", + "{\"title_link\":\"https://www.youtube.com/watch?v=_Cd9FYO9Rh4\",\"title\":\"Scorpions Berlin Philharmonic Orchestra Rock You Like a Hurricane\",\"service_url\":\"https://www.youtube.com/\",\"service_name\":\"YouTube\",\"service_icon\":\"https://a.slack-edge.com/2089/img/unfurl_icons/youtube.png\",\"from_url\":\"https://www.youtube.com/watch?v=_Cd9FYO9Rh4\",\"author_name\":\"Nathan Allen\",\"author_link\":\"https://www.youtube.com/user/27caboose\"}", + "{\"title_link\":\"http://www.kn-online.de/Nachrichten/Panorama/Lehrerin-fuehrt-Netflix-Experiment-durch-das-Ergebnis-erschreckt\",\"title\":\"Lehrerin führt Netflix-Experiment durch – das Ergebnis erschreckt\",\"text\":\"Rebecca Schiller aus Potsdam unterrichtet am Marie-Curie-Gymnasium im Havelland. Als „Frau Lehrerin“ ist sie auf Twitter eine kleine Berühmtheit – vor allem, seit sie dort eine unkonventionelle Unterrichtsmethode veröffentlicht hat.\",\"service_name\":\"KN - Kieler Nachrichten\",\"service_icon\":\"http://www.kn-online.de/bundles/molasset/images/sites/desktop/kn/apple-touch-icon.png\",\"image_width\":500,\"image_url\":\"http://www.kn-online.de/var/storage/images/rnd/nachrichten/panorama/uebersicht/lehrerin-fuehrt-netflix-experiment-durch-das-ergebnis-erschreckt/712106427-8-ger-DE/Lehrerin-fuehrt-Netflix-Experiment-durch-das-Ergebnis-erschreckt_reference_2_1.jpg\",\"image_height\":250,\"image_bytes\":65450,\"from_url\":\"http://www.kn-online.de/Nachrichten/Panorama/Lehrerin-fuehrt-Netflix-Experiment-durch-das-Ergebnis-erschreckt\"}" + ).map { + gson.fromJson(it, AttachmentData::class.java) + } + + val urls = listOf( + "https://quasseldroid.info/", + "https://www.youtube.com/watch?v=IfXMN3VhikA", + "https://twitter.com/dw_politik/status/1092872739445104640", + "https://soundcloud.com/kevin-manthei/sto-ds9-main-title", + "https://twitter.com/raketenlurch/status/1093991675209416704", + "https://arxius.io/i/25287151", + "https://i.imgur.com/W8DjyWk.jpg", + "https://imgur.com/W8DjyWk", + "https://rp-online.de/panorama/deutschland/buchenbach-transporter-hindert-krankenwagen-mit-kind-an-bord-minutenlang-am-ueberholen_aid-35758071", + "http://m.kn-online.de/Nachrichten/Panorama/Lehrerin-fuehrt-Netflix-Experiment-durch-das-Ergebnis-erschreckt", + "https://media.ccc.de/v/35c3-9904-the_social_credit_system", + "https://medium.com/slack-developer-blog/everything-you-ever-wanted-to-know-about-unfurling-but-were-afraid-to-ask-or-how-to-make-your-e64b4bb9254" + ) + + activity?.runOnUiThread { + combineLatest(urls.map { + api.retrieve(it) + .map { Optional.of(it) } + .onErrorReturnItem(Optional.empty()) + }).map { + it.mapNotNull(Optional<AttachmentData>::orNull) + }.toLiveData().observe(this, Observer { + adapter.submitList(ircCloudEmbeds + it) + }) + } + } + }) + + return view + } +} diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/info/message/MessageInfoFragmentProvider.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/info/message/MessageInfoFragmentProvider.kt new file mode 100644 index 000000000..f9ad726af --- /dev/null +++ b/app/src/main/java/de/kuschku/quasseldroid/ui/info/message/MessageInfoFragmentProvider.kt @@ -0,0 +1,34 @@ +/* + * Quasseldroid - Quassel client for Android + * + * Copyright (c) 2019 Janne Koschinski + * Copyright (c) 2019 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.ui.info.message + +import androidx.fragment.app.FragmentActivity +import dagger.Binds +import dagger.Module +import dagger.android.ContributesAndroidInjector + +@Module +abstract class MessageInfoFragmentProvider { + @Binds + abstract fun bindFragmentActivity(activity: MessageInfoActivity): FragmentActivity + + @ContributesAndroidInjector + abstract fun bindMessageInfoFragment(): MessageInfoFragment +} diff --git a/app/src/main/java/de/kuschku/quasseldroid/util/attachments/AttachmentApi.kt b/app/src/main/java/de/kuschku/quasseldroid/util/attachments/AttachmentApi.kt new file mode 100644 index 000000000..02f921401 --- /dev/null +++ b/app/src/main/java/de/kuschku/quasseldroid/util/attachments/AttachmentApi.kt @@ -0,0 +1,30 @@ +/* + * Quasseldroid - Quassel client for Android + * + * Copyright (c) 2019 Janne Koschinski + * Copyright (c) 2019 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.attachments + +import de.kuschku.quasseldroid.util.attachment.AttachmentData +import io.reactivex.Observable +import retrofit2.http.GET +import retrofit2.http.Query + +interface AttachmentApi { + @GET("/") + fun retrieve(@Query("url") url: String): Observable<AttachmentData> +} diff --git a/app/src/main/java/de/kuschku/quasseldroid/util/ui/view/MessageAttachmentView.kt b/app/src/main/java/de/kuschku/quasseldroid/util/ui/view/MessageAttachmentView.kt new file mode 100644 index 000000000..567b13244 --- /dev/null +++ b/app/src/main/java/de/kuschku/quasseldroid/util/ui/view/MessageAttachmentView.kt @@ -0,0 +1,211 @@ +/* + * Quasseldroid - Quassel client for Android + * + * Copyright (c) 2019 Janne Koschinski + * Copyright (c) 2019 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.ui.view + +import android.content.Context +import android.content.Intent +import android.graphics.Color +import android.graphics.drawable.Drawable +import android.net.Uri +import android.util.AttributeSet +import android.view.LayoutInflater +import android.view.View +import android.widget.FrameLayout +import android.widget.ImageView +import android.widget.TextView +import androidx.annotation.ColorInt +import butterknife.BindView +import butterknife.ButterKnife +import com.bumptech.glide.request.target.CustomViewTarget +import com.bumptech.glide.request.transition.Transition +import de.kuschku.quasseldroid.R +import de.kuschku.quasseldroid.util.helper.styledAttributes +import de.kuschku.quasseldroid.util.helper.visibleIf + +class MessageAttachmentView : FrameLayout { + @BindView(R.id.attachment_color_bar) + lateinit var colorBar: View + + @BindView(R.id.attachment_author_icon) + lateinit var authorIcon: ImageView + + @BindView(R.id.attachment_author) + lateinit var author: TextView + + @BindView(R.id.attachment_title) + lateinit var title: TextView + + @BindView(R.id.attachment_description) + lateinit var description: TextView + + @BindView(R.id.attachment_thumbnail) + lateinit var thumbnail: ImageView + + @BindView(R.id.attachment_preview) + lateinit var preview: ImageView + + @BindView(R.id.attachment_service_icon) + lateinit var serviceIcon: ImageView + + @BindView(R.id.attachment_service) + lateinit var service: TextView + + val authorIconTarget: VisibilitySettingDrawableImageViewTarget + val thumbnailTarget: VisibilitySettingDrawableImageViewTarget + val previewTarget: VisibilitySettingDrawableImageViewTarget + val serviceIconTarget: VisibilitySettingDrawableImageViewTarget + + private var url: String? = null + private var authorUrl: String? = null + + constructor(context: Context) : + this(context, null) + + constructor(context: Context, attrs: AttributeSet?) : + this(context, attrs, 0) + + constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : + super(context, attrs, defStyleAttr) { + + LayoutInflater.from(context).inflate(R.layout.widget_message_attachment, this, true) + ButterKnife.bind(this) + + authorIconTarget = VisibilitySettingDrawableImageViewTarget(authorIcon) + thumbnailTarget = VisibilitySettingDrawableImageViewTarget(thumbnail) + previewTarget = VisibilitySettingDrawableImageViewTarget(preview) + serviceIconTarget = VisibilitySettingDrawableImageViewTarget(serviceIcon) + + reinitViews() + } + + fun reinitViews() { + setColor(0) + setAuthorIcon(null) + authorIcon.visibility = View.VISIBLE + setAuthor("") + setTitle("") + setDescription("") + setThumbnail(null) + thumbnail.visibility = View.VISIBLE + setPreview(null) + preview.visibility = View.VISIBLE + setServiceIcon(null) + serviceIcon.visibility = View.VISIBLE + setService("") + } + + class VisibilitySettingDrawableImageViewTarget(view: ImageView) : + CustomViewTarget<ImageView, Drawable>(view) { + override fun onLoadFailed(errorDrawable: Drawable?) { + view.setImageDrawable(errorDrawable) + view.visibility = View.GONE + } + + override fun onResourceCleared(placeholder: Drawable?) { + view.setImageDrawable(placeholder) + view.visibility = View.GONE + } + + override fun onResourceReady(resource: Drawable, transition: Transition<in Drawable>?) { + view.setImageDrawable(resource) + view.visibility = View.VISIBLE + } + } + + private val colorForegroundSecondary = context.theme.styledAttributes(R.attr.colorForegroundSecondary) { + getColor(0, 0) + } + + fun setColor(color: String?) { + setColor(try { + Color.parseColor(color) + } catch (ignored: Throwable) { + 0 + }) + } + + fun setColor(@ColorInt color: Int) { + if (color != 0) { + colorBar.setBackgroundColor(color) + } else { + colorBar.setBackgroundColor(colorForegroundSecondary) + } + } + + fun setAuthorIcon(drawable: Drawable?) { + authorIcon.setImageDrawable(drawable) + } + + fun setAuthor(text: String?) { + author.text = text + author.visibleIf(!text.isNullOrBlank()) + } + + fun setAuthorLink(url: String) { + if (url.isNotBlank()) { + author.setOnClickListener { + context?.startActivity(Intent(Intent.ACTION_VIEW).apply { + data = Uri.parse(url) + }) + } + } else { + author.setOnClickListener(null) + } + } + + fun setTitle(text: String?) { + title.text = text + title.visibleIf(!text.isNullOrBlank()) + } + + fun setLink(url: String?) { + if (url.isNullOrBlank()) { + this.setOnClickListener(null) + } else { + this.setOnClickListener { + context?.startActivity(Intent(Intent.ACTION_VIEW).apply { + data = Uri.parse(url) + }) + } + } + } + + fun setDescription(text: String?) { + description.text = text + description.visibleIf(!text.isNullOrBlank()) + } + + fun setThumbnail(drawable: Drawable?) { + thumbnail.setImageDrawable(drawable) + } + + fun setPreview(drawable: Drawable?) { + preview.setImageDrawable(drawable) + } + + fun setServiceIcon(drawable: Drawable?) { + serviceIcon.setImageDrawable(drawable) + } + + fun setService(text: String?) { + service.text = text + service.visibleIf(!text.isNullOrBlank()) + } +} diff --git a/app/src/main/res/layout/info_message.xml b/app/src/main/res/layout/info_message.xml new file mode 100644 index 000000000..04882f08e --- /dev/null +++ b/app/src/main/res/layout/info_message.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + Quasseldroid - Quassel client for Android + + Copyright (c) 2019 Janne Koschinski + Copyright (c) 2019 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/>. + --> + +<androidx.recyclerview.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/list" + android:layout_width="match_parent" + android:layout_height="match_parent" /> diff --git a/app/src/main/res/layout/widget_chatmessage_action.xml b/app/src/main/res/layout/widget_chatmessage_action.xml index 6247fb6d2..3706c5fee 100644 --- a/app/src/main/res/layout/widget_chatmessage_action.xml +++ b/app/src/main/res/layout/widget_chatmessage_action.xml @@ -73,40 +73,51 @@ <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_gravity="center_vertical|fill_horizontal" - android:orientation="horizontal"> + android:orientation="vertical"> <LinearLayout - android:layout_width="0dip" + android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_weight="1" - android:orientation="vertical"> + android:layout_gravity="center_vertical|fill_horizontal" + android:orientation="horizontal"> - <de.kuschku.quasseldroid.util.ui.view.RipplePassthroughTextView - android:id="@+id/combined" - style="@style/Widget.RtlConformTextView" - android:layout_width="match_parent" + <LinearLayout + android:layout_width="0dip" android:layout_height="wrap_content" - android:textColor="?attr/colorForegroundAction" + android:layout_weight="1" + android:orientation="vertical"> + + <de.kuschku.quasseldroid.util.ui.view.RipplePassthroughTextView + android:id="@+id/combined" + style="@style/Widget.RtlConformTextView" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:textColor="?attr/colorForegroundAction" + android:textStyle="italic" + tools:text="@sample/messages.json/data/message" /> + + </LinearLayout> + + <TextView + android:id="@+id/time_right" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="top" + android:layout_marginStart="@dimen/message_horizontal" + android:layout_marginLeft="@dimen/message_horizontal" + android:textColor="?attr/colorForegroundSecondary" android:textStyle="italic" - tools:text="@sample/messages.json/data/message" /> - + android:visibility="gone" + tools:ignore="SmallSp" + tools:text="@sample/messages.json/data/time" + tools:textSize="11.9sp" + tools:visibility="visible" /> </LinearLayout> - <TextView - android:id="@+id/time_right" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="top" - android:layout_marginStart="@dimen/message_horizontal" - android:layout_marginLeft="@dimen/message_horizontal" - android:textColor="?attr/colorForegroundSecondary" - android:textStyle="italic" - android:visibility="gone" - tools:ignore="SmallSp" - tools:text="@sample/messages.json/data/time" - tools:textSize="11.9sp" - tools:visibility="visible" /> + <de.kuschku.quasseldroid.util.ui.view.MessageAttachmentView + android:id="@+id/attachment" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> </LinearLayout> </LinearLayout> diff --git a/app/src/main/res/layout/widget_chatmessage_info.xml b/app/src/main/res/layout/widget_chatmessage_info.xml index 343ba2e65..53dabcb69 100644 --- a/app/src/main/res/layout/widget_chatmessage_info.xml +++ b/app/src/main/res/layout/widget_chatmessage_info.xml @@ -59,29 +59,47 @@ android:layout_marginRight="@dimen/message_horizontal" android:visibility="gone" /> - <de.kuschku.quasseldroid.util.ui.view.RipplePassthroughTextView - android:id="@+id/combined" - style="@style/Widget.RtlConformTextView" - android:layout_width="0dip" + <LinearLayout + android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_weight="1" - android:textColor="?attr/colorForegroundSecondary" - android:textStyle="italic" - tools:text="@sample/messages.json/data/message" /> + android:orientation="vertical"> - <TextView - android:id="@+id/time_right" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="top" - android:layout_marginStart="@dimen/message_horizontal" - android:layout_marginLeft="@dimen/message_horizontal" - android:textColor="?attr/colorForegroundSecondary" - android:textStyle="italic" - android:visibility="gone" - tools:ignore="SmallSp" - tools:text="@sample/messages.json/data/time" - tools:textSize="11.9sp" - tools:visibility="visible" /> + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal"> + + <de.kuschku.quasseldroid.util.ui.view.RipplePassthroughTextView + android:id="@+id/combined" + style="@style/Widget.RtlConformTextView" + android:layout_width="0dip" + android:layout_height="wrap_content" + android:layout_weight="1" + android:textColor="?attr/colorForegroundSecondary" + android:textStyle="italic" + tools:text="@sample/messages.json/data/message" /> + + <TextView + android:id="@+id/time_right" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="top" + android:layout_marginStart="@dimen/message_horizontal" + android:layout_marginLeft="@dimen/message_horizontal" + android:textColor="?attr/colorForegroundSecondary" + android:textStyle="italic" + android:visibility="gone" + tools:ignore="SmallSp" + tools:text="@sample/messages.json/data/time" + tools:textSize="11.9sp" + tools:visibility="visible" /> + + </LinearLayout> + + <de.kuschku.quasseldroid.util.ui.view.MessageAttachmentView + android:id="@+id/attachment" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> + </LinearLayout> </LinearLayout> </LinearLayout> diff --git a/app/src/main/res/layout/widget_chatmessage_notice.xml b/app/src/main/res/layout/widget_chatmessage_notice.xml index 86b8dad5e..7b62f418d 100644 --- a/app/src/main/res/layout/widget_chatmessage_notice.xml +++ b/app/src/main/res/layout/widget_chatmessage_notice.xml @@ -59,28 +59,46 @@ android:layout_marginRight="@dimen/message_horizontal" android:visibility="gone" /> - <de.kuschku.quasseldroid.util.ui.view.RipplePassthroughTextView - android:id="@+id/combined" - style="@style/Widget.RtlConformTextView" - android:layout_width="0dip" + <LinearLayout + android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_weight="1" - android:textColor="?attr/colorForegroundNotice" - tools:text="@sample/messages.json/data/message" /> + android:orientation="vertical"> - <TextView - android:id="@+id/time_right" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="top" - android:layout_marginStart="@dimen/message_horizontal" - android:layout_marginLeft="@dimen/message_horizontal" - android:textColor="?attr/colorForegroundSecondary" - android:textStyle="italic" - android:visibility="gone" - tools:ignore="SmallSp" - tools:text="@sample/messages.json/data/time" - tools:textSize="11.9sp" - tools:visibility="visible" /> + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal"> + + <de.kuschku.quasseldroid.util.ui.view.RipplePassthroughTextView + android:id="@+id/combined" + style="@style/Widget.RtlConformTextView" + android:layout_width="0dip" + android:layout_height="wrap_content" + android:layout_weight="1" + android:textColor="?attr/colorForegroundNotice" + tools:text="@sample/messages.json/data/message" /> + + <TextView + android:id="@+id/time_right" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="top" + android:layout_marginStart="@dimen/message_horizontal" + android:layout_marginLeft="@dimen/message_horizontal" + android:textColor="?attr/colorForegroundSecondary" + android:textStyle="italic" + android:visibility="gone" + tools:ignore="SmallSp" + tools:text="@sample/messages.json/data/time" + tools:textSize="11.9sp" + tools:visibility="visible" /> + + </LinearLayout> + + <de.kuschku.quasseldroid.util.ui.view.MessageAttachmentView + android:id="@+id/attachment" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> + </LinearLayout> </LinearLayout> </LinearLayout> diff --git a/app/src/main/res/layout/widget_chatmessage_plain.xml b/app/src/main/res/layout/widget_chatmessage_plain.xml index 3ead811a1..b6e1f9376 100644 --- a/app/src/main/res/layout/widget_chatmessage_plain.xml +++ b/app/src/main/res/layout/widget_chatmessage_plain.xml @@ -138,7 +138,6 @@ android:textColor="?attr/colorForeground" tools:text="@sample/messages.json/data/message" tools:visibility="gone" /> - </LinearLayout> <TextView @@ -156,6 +155,11 @@ tools:textSize="11.9sp" tools:visibility="visible" /> </LinearLayout> + + <de.kuschku.quasseldroid.util.ui.view.MessageAttachmentView + android:id="@+id/attachment" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> </LinearLayout> </LinearLayout> diff --git a/app/src/main/res/layout/widget_message_attachment.xml b/app/src/main/res/layout/widget_message_attachment.xml new file mode 100644 index 000000000..79418540f --- /dev/null +++ b/app/src/main/res/layout/widget_message_attachment.xml @@ -0,0 +1,173 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + Quasseldroid - Quassel client for Android + + Copyright (c) 2019 Janne Koschinski + Copyright (c) 2019 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/>. + --> + +<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_margin="8dp" + app:cardBackgroundColor="?colorBackgroundCard"> + + <androidx.constraintlayout.widget.ConstraintLayout + android:id="@+id/attachment_content" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="?selectableItemBackground" + android:orientation="horizontal"> + + <View + android:id="@+id/attachment_color_bar" + android:layout_width="4dp" + android:layout_height="0dp" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + tools:background="@android:color/holo_red_dark" /> + + <ImageView + android:id="@+id/attachment_author_icon" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginStart="8dp" + android:layout_marginLeft="8dp" + android:adjustViewBounds="true" + android:maxWidth="16dp" + android:maxHeight="16dp" + app:layout_constraintBottom_toBottomOf="@+id/attachment_author" + app:layout_constraintStart_toEndOf="@+id/attachment_color_bar" + app:layout_constraintTop_toTopOf="@+id/attachment_author" + tools:srcCompat="@tools:sample/avatars" /> + + <TextView + android:id="@+id/attachment_author" + android:layout_width="0dip" + android:layout_height="wrap_content" + android:layout_marginStart="8dp" + android:layout_marginLeft="8dp" + android:layout_marginTop="4dp" + android:layout_marginEnd="8dp" + android:layout_marginRight="8dp" + android:textColor="?colorTextSecondary" + app:layout_constraintEnd_toStartOf="@+id/attachment_thumbnail" + app:layout_constraintStart_toEndOf="@id/attachment_author_icon" + app:layout_constraintTop_toTopOf="parent" + tools:text="MrDomigruber" /> + + <TextView + android:id="@+id/attachment_title" + android:layout_width="0dip" + android:layout_height="wrap_content" + android:layout_marginStart="8dp" + android:layout_marginLeft="8dp" + android:layout_marginTop="4dp" + android:layout_marginEnd="8dp" + android:layout_marginRight="8dp" + android:textColor="?colorTextPrimary" + android:textStyle="bold" + app:layout_constraintEnd_toStartOf="@+id/attachment_thumbnail" + app:layout_constraintStart_toEndOf="@id/attachment_color_bar" + app:layout_constraintTop_toBottomOf="@+id/attachment_author" + tools:text="Ein sprechender Elch will meine Kreditkartennummer" /> + + <TextView + android:id="@+id/attachment_description" + android:layout_width="0dip" + android:layout_height="wrap_content" + android:layout_marginStart="8dp" + android:layout_marginLeft="8dp" + android:layout_marginTop="4dp" + android:layout_marginEnd="8dp" + android:layout_marginRight="8dp" + android:textColor="?colorTextPrimary" + app:layout_constraintEnd_toStartOf="@+id/attachment_thumbnail" + app:layout_constraintStart_toEndOf="@id/attachment_color_bar" + app:layout_constraintTop_toBottomOf="@id/attachment_title" + tools:text="Homer Simpson gibt einen Elch seine Kreditkarten nummer" /> + + <ImageView + android:id="@+id/attachment_thumbnail" + android:layout_width="wrap_content" + android:layout_height="0dp" + android:layout_marginTop="8dp" + android:layout_marginEnd="8dp" + android:layout_marginRight="8dp" + android:layout_marginBottom="8dp" + android:adjustViewBounds="true" + android:maxWidth="80dp" + android:scaleType="fitStart" + app:layout_constraintBottom_toTopOf="@+id/attachment_preview" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintVertical_bias="0.0" + tools:background="#ff0000" + tools:minHeight="60dp" + tools:srcCompat="@tools:sample/avatars" /> + + <ImageView + android:id="@+id/attachment_preview" + android:layout_width="0dip" + android:layout_height="wrap_content" + android:layout_marginStart="8dp" + android:layout_marginLeft="8dp" + android:layout_marginTop="8dp" + android:layout_marginEnd="8dp" + android:layout_marginRight="8dp" + android:adjustViewBounds="true" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toEndOf="@id/attachment_color_bar" + app:layout_constraintTop_toBottomOf="@+id/attachment_description" + tools:background="#00ff00" + tools:minHeight="160dp" /> + + <ImageView + android:id="@+id/attachment_service_icon" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginStart="8dp" + android:layout_marginLeft="8dp" + android:adjustViewBounds="true" + android:maxWidth="16dp" + android:maxHeight="16dp" + app:layout_constraintBottom_toBottomOf="@+id/attachment_service" + app:layout_constraintStart_toEndOf="@id/attachment_color_bar" + app:layout_constraintTop_toTopOf="@+id/attachment_service" + tools:srcCompat="@tools:sample/avatars" /> + + <TextView + android:id="@+id/attachment_service" + android:layout_width="0dip" + android:layout_height="wrap_content" + android:layout_marginStart="8dp" + android:layout_marginLeft="8dp" + android:layout_marginTop="4dp" + android:layout_marginEnd="8dp" + android:layout_marginRight="8dp" + android:layout_marginBottom="4dp" + android:textColor="?colorTextSecondary" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toEndOf="@+id/attachment_service_icon" + app:layout_constraintTop_toBottomOf="@+id/attachment_preview" + app:layout_constraintVertical_bias="0.0" + tools:text="YouTube" /> + + </androidx.constraintlayout.widget.ConstraintLayout> + +</com.google.android.material.card.MaterialCardView> diff --git a/app/src/main/res/layout/widget_message_attachment_item.xml b/app/src/main/res/layout/widget_message_attachment_item.xml new file mode 100644 index 000000000..eaf71b668 --- /dev/null +++ b/app/src/main/res/layout/widget_message_attachment_item.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + Quasseldroid - Quassel client for Android + + Copyright (c) 2019 Janne Koschinski + Copyright (c) 2019 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/>. + --> + +<de.kuschku.quasseldroid.util.ui.view.MessageAttachmentView xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/view" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> diff --git a/app/src/main/res/menu/context_messages.xml b/app/src/main/res/menu/context_messages.xml index 0554f668e..aa27fa1e4 100644 --- a/app/src/main/res/menu/context_messages.xml +++ b/app/src/main/res/menu/context_messages.xml @@ -18,6 +18,11 @@ --> <menu xmlns:android="http://schemas.android.com/apk/res/android"> + <item + android:id="@+id/action_message_info" + android:icon="@drawable/ic_info" + android:title="@string/label_info_message" + android:visible="false" /> <item android:id="@+id/action_user_info" android:icon="@drawable/ic_account" diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 714217941..e0dc9ae71 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -65,6 +65,7 @@ <string name="label_info_channel">Channel Details</string> <string name="label_info_channellist">Channel List</string> <string name="label_info_core">Core Details</string> + <string name="label_info_message">Message Details</string> <string name="label_info_user">User Details</string> <string name="label_input_history">Input History</string> <string name="label_join">Join</string> diff --git a/gradle.properties b/gradle.properties index 9450fb27e..9b93d673b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -33,9 +33,9 @@ android.enableD8=true # Enable new Android R8 Optimizer android.enableR8=true # Enable gradle build cache -org.gradle.caching=true +org.gradle.caching=false # Enable android build cache -android.enableBuildCache=true +android.enableBuildCache=false # Enable AndroidX android.useAndroidX=true android.enableJetifier=true diff --git a/lib/src/main/java/de/kuschku/libquassel/protocol/Message.kt b/lib/src/main/java/de/kuschku/libquassel/protocol/Message.kt index e7f2542f7..6fd06dc00 100644 --- a/lib/src/main/java/de/kuschku/libquassel/protocol/Message.kt +++ b/lib/src/main/java/de/kuschku/libquassel/protocol/Message.kt @@ -34,6 +34,7 @@ data class Message( val senderPrefixes: String, val realName: String, val avatarUrl: String, + val attachments: String, val content: String ) { enum class MessageType(override val bit: UInt) : Flag<MessageType> { @@ -84,6 +85,6 @@ data class Message( override fun toString(): String { - return "Message(messageId=$messageId, time=$time, type=$type, flag=$flag, bufferInfo=$bufferInfo, sender='$sender', senderPrefixes='$senderPrefixes', content='$content')" + return "Message(messageId=$messageId, time=$time, type=$type, flag=$flag, bufferInfo=$bufferInfo, sender='$sender', senderPrefixes='$senderPrefixes', attachments='$attachments', content='$content')" } } diff --git a/lib/src/main/java/de/kuschku/libquassel/protocol/primitive/serializer/MessageSerializer.kt b/lib/src/main/java/de/kuschku/libquassel/protocol/primitive/serializer/MessageSerializer.kt index e3d038887..4ae6d4097 100644 --- a/lib/src/main/java/de/kuschku/libquassel/protocol/primitive/serializer/MessageSerializer.kt +++ b/lib/src/main/java/de/kuschku/libquassel/protocol/primitive/serializer/MessageSerializer.kt @@ -43,6 +43,8 @@ object MessageSerializer : Serializer<Message> { StringSerializer.UTF8.serialize(buffer, data.realName, features) if (features.hasFeature(ExtendedFeature.RichMessages)) StringSerializer.UTF8.serialize(buffer, data.avatarUrl, features) + if (features.hasFeature(ExtendedFeature.MessageAttachments)) + StringSerializer.UTF8.serialize(buffer, data.attachments, features) StringSerializer.UTF8.serialize(buffer, data.content, features) } @@ -65,6 +67,8 @@ object MessageSerializer : Serializer<Message> { StringSerializer.UTF8.deserialize(buffer, features) ?: "" else "", avatarUrl = if (features.hasFeature(ExtendedFeature.RichMessages)) StringSerializer.UTF8.deserialize(buffer, features) ?: "" else "", + attachments = if (features.hasFeature(ExtendedFeature.MessageAttachments)) + StringSerializer.UTF8.deserialize(buffer, features) ?: "" else "", content = StringSerializer.UTF8.deserialize(buffer, features) ?: "" ) } diff --git a/lib/src/main/java/de/kuschku/libquassel/quassel/ExtendedFeature.kt b/lib/src/main/java/de/kuschku/libquassel/quassel/ExtendedFeature.kt index 8718c9900..8001e5bed 100644 --- a/lib/src/main/java/de/kuschku/libquassel/quassel/ExtendedFeature.kt +++ b/lib/src/main/java/de/kuschku/libquassel/quassel/ExtendedFeature.kt @@ -58,7 +58,9 @@ enum class ExtendedFeature { /** 64-bit IDs for messages */ LongMessageId, /** CoreInfo dynamically updated using signals */ - SyncedCoreInfo; + SyncedCoreInfo, + /** Rich message attachments */ + MessageAttachments; companion object { private val map = values().associateBy(ExtendedFeature::name) diff --git a/persistence/src/main/java/de/kuschku/quasseldroid/persistence/dao/MessageDao.kt b/persistence/src/main/java/de/kuschku/quasseldroid/persistence/dao/MessageDao.kt index 9880dcf9d..48429ef0b 100644 --- a/persistence/src/main/java/de/kuschku/quasseldroid/persistence/dao/MessageDao.kt +++ b/persistence/src/main/java/de/kuschku/quasseldroid/persistence/dao/MessageDao.kt @@ -42,7 +42,7 @@ interface MessageDao { fun _buffers(): List<BufferId_Type> @Query("SELECT * FROM message WHERE messageId = :messageId") - fun find(messageId: MsgId_Type): MessageData? + fun _find(messageId: MsgId_Type): MessageData? @Query("SELECT * FROM message WHERE bufferId = :bufferId ORDER BY messageId ASC") fun _findByBufferId(bufferId: BufferId_Type): List<MessageData> @@ -93,6 +93,9 @@ interface MessageDao { inline fun MessageDao.buffers() = _buffers().map { BufferId(it) } +inline fun MessageDao.find(messageId: MsgId) = + _find(messageId.id) + inline fun MessageDao.findByBufferId(bufferId: BufferId) = _findByBufferId(bufferId.id) diff --git a/persistence/src/main/java/de/kuschku/quasseldroid/persistence/db/QuasselDatabase.kt b/persistence/src/main/java/de/kuschku/quasseldroid/persistence/db/QuasselDatabase.kt index e4d64c0ae..2ea4bd2fe 100644 --- a/persistence/src/main/java/de/kuschku/quasseldroid/persistence/db/QuasselDatabase.kt +++ b/persistence/src/main/java/de/kuschku/quasseldroid/persistence/db/QuasselDatabase.kt @@ -30,7 +30,7 @@ import de.kuschku.quasseldroid.persistence.models.* import de.kuschku.quasseldroid.persistence.util.MessageTypeConverter @Database(entities = [MessageData::class, Filtered::class, SslValidityWhitelistEntry::class, SslHostnameWhitelistEntry::class, NotificationData::class], - version = 19) + version = 20) @TypeConverters(MessageTypeConverter::class) abstract class QuasselDatabase : RoomDatabase() { abstract fun message(): MessageDao @@ -157,6 +157,11 @@ abstract class QuasselDatabase : RoomDatabase() { override fun migrate(database: SupportSQLiteDatabase) { database.execSQL("ALTER TABLE `notification` ADD `networkName` TEXT DEFAULT '' NOT NULL;") } + }, + object : Migration(19, 20) { + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL("ALTER TABLE `message` ADD `attachments` TEXT DEFAULT '' NOT NULL;") + } } ).build() } diff --git a/persistence/src/main/java/de/kuschku/quasseldroid/persistence/models/MessageData.kt b/persistence/src/main/java/de/kuschku/quasseldroid/persistence/models/MessageData.kt index 3233e80b6..93fbe4b1e 100644 --- a/persistence/src/main/java/de/kuschku/quasseldroid/persistence/models/MessageData.kt +++ b/persistence/src/main/java/de/kuschku/quasseldroid/persistence/models/MessageData.kt @@ -43,6 +43,7 @@ data class MessageData( var senderPrefixes: String, var realName: String, var avatarUrl: String, + var attachments: String, var content: String, var ignored: Boolean ) { @@ -65,6 +66,7 @@ data class MessageData( senderPrefixes: String, realName: String, avatarUrl: String, + attachments: String, content: String, ignored: Boolean ) = MessageData( @@ -78,6 +80,7 @@ data class MessageData( senderPrefixes, realName, avatarUrl, + attachments, content, ignored ) diff --git a/persistence/src/main/java/de/kuschku/quasseldroid/persistence/util/QuasselBacklogStorage.kt b/persistence/src/main/java/de/kuschku/quasseldroid/persistence/util/QuasselBacklogStorage.kt index 60b813a7e..07ebbce4a 100644 --- a/persistence/src/main/java/de/kuschku/quasseldroid/persistence/util/QuasselBacklogStorage.kt +++ b/persistence/src/main/java/de/kuschku/quasseldroid/persistence/util/QuasselBacklogStorage.kt @@ -55,6 +55,7 @@ class QuasselBacklogStorage(private val db: QuasselDatabase) : BacklogStorage { senderPrefixes = it.senderPrefixes, realName = it.realName, avatarUrl = it.avatarUrl, + attachments = it.attachments, content = it.content, ignored = isIgnored( session, diff --git a/viewmodel/build.gradle.kts b/viewmodel/build.gradle.kts index a4d86f44a..5e4e2a749 100644 --- a/viewmodel/build.gradle.kts +++ b/viewmodel/build.gradle.kts @@ -56,6 +56,7 @@ dependencies { implementation("io.reactivex.rxjava2", "rxjava", "2.1.9") implementation("org.threeten", "threetenbp", "1.3.8", classifier = "no-tzdb") implementation("org.jetbrains", "annotations", "16.0.3") + implementation("com.google.code.gson", "gson", "2.8.5") // Quassel implementation(project(":persistence")) diff --git a/viewmodel/src/main/java/de/kuschku/quasseldroid/util/attachment/AttachmentData.kt b/viewmodel/src/main/java/de/kuschku/quasseldroid/util/attachment/AttachmentData.kt new file mode 100644 index 000000000..bf0842c36 --- /dev/null +++ b/viewmodel/src/main/java/de/kuschku/quasseldroid/util/attachment/AttachmentData.kt @@ -0,0 +1,55 @@ +/* + * Quasseldroid - Quassel client for Android + * + * Copyright (c) 2019 Janne Koschinski + * Copyright (c) 2019 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.attachment + +import com.google.gson.annotations.SerializedName + +data class AttachmentData( + @SerializedName("from_url") + val fromUrl: String?, + @SerializedName("color") + val color: String?, + @SerializedName("author_name") + val authorName: String?, + @SerializedName("author_link") + val authorLink: String?, + @SerializedName("author_icon") + val authorIcon: String?, + @SerializedName("title") + val title: String?, + @SerializedName("title_link") + val titleLink: String?, + @SerializedName("text") + val text: String?, + @SerializedName("fields") + val fields: List<AttachmentDataField>?, + @SerializedName("image_url") + val imageUrl: String?, + @SerializedName("type") + val type: String?, + @SerializedName("player") + val player: String?, + @SerializedName("service_name") + val serviceName: String?, + @SerializedName("service_icon") + val serviceIcon: String?, + @SerializedName("ts") + val timestamp: Int? +) diff --git a/viewmodel/src/main/java/de/kuschku/quasseldroid/util/attachment/AttachmentDataField.kt b/viewmodel/src/main/java/de/kuschku/quasseldroid/util/attachment/AttachmentDataField.kt new file mode 100644 index 000000000..e2338e27a --- /dev/null +++ b/viewmodel/src/main/java/de/kuschku/quasseldroid/util/attachment/AttachmentDataField.kt @@ -0,0 +1,26 @@ +/* + * Quasseldroid - Quassel client for Android + * + * Copyright (c) 2019 Janne Koschinski + * Copyright (c) 2019 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.attachment + +data class AttachmentDataField( + val title: String, + val value: String, + val short: Boolean +) diff --git a/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/data/FormattedMessage.kt b/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/data/FormattedMessage.kt index 106fd6e16..92ca0ed3a 100644 --- a/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/data/FormattedMessage.kt +++ b/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/data/FormattedMessage.kt @@ -21,6 +21,7 @@ package de.kuschku.quasseldroid.viewmodel.data import android.graphics.drawable.Drawable import de.kuschku.quasseldroid.persistence.models.MessageData +import de.kuschku.quasseldroid.util.attachment.AttachmentData class FormattedMessage( val original: MessageData, @@ -33,6 +34,7 @@ class FormattedMessage( val realName: CharSequence? = null, val avatarUrls: List<Avatar> = emptyList(), val urls: List<String> = emptyList(), + val attachment: AttachmentData? = null, val hasDayChange: Boolean, val isSelected: Boolean, val isExpanded: Boolean, @@ -54,6 +56,7 @@ class FormattedMessage( if (realName != other.realName) return false if (avatarUrls != other.avatarUrls) return false if (urls != other.urls) return false + if (attachment != other.attachment) return false if (hasDayChange != other.hasDayChange) return false if (isSelected != other.isSelected) return false if (isExpanded != other.isExpanded) return false @@ -72,6 +75,7 @@ class FormattedMessage( result = 31 * result + (realName?.hashCode() ?: 0) result = 31 * result + avatarUrls.hashCode() result = 31 * result + urls.hashCode() + result = 31 * result + attachment.hashCode() result = 31 * result + hasDayChange.hashCode() result = 31 * result + isSelected.hashCode() result = 31 * result + isExpanded.hashCode() -- GitLab