Commit 847036a7 authored by Lucio Maciel's avatar Lucio Maciel

Refactor attachments

parent ea6c8083
{
"formatVersion": 1,
"database": {
"version": 10,
"identityHash": "db46c12dbb8747200288f48d5dc5558b",
"entities": [
{
"tableName": "users",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `username` TEXT, `name` TEXT, `status` TEXT NOT NULL, `utcOffset` REAL, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "username",
"columnName": "username",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "status",
"columnName": "status",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "utcOffset",
"columnName": "utcOffset",
"affinity": "REAL",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": false
},
"indices": [
{
"name": "index_users_username",
"unique": false,
"columnNames": [
"username"
],
"createSql": "CREATE INDEX `index_users_username` ON `${TABLE_NAME}` (`username`)"
}
],
"foreignKeys": []
},
{
"tableName": "chatrooms",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `subscriptionId` TEXT NOT NULL, `type` TEXT NOT NULL, `name` TEXT NOT NULL, `fullname` TEXT, `userId` TEXT, `ownerId` TEXT, `readonly` INTEGER, `isDefault` INTEGER, `favorite` INTEGER, `open` INTEGER NOT NULL, `alert` INTEGER NOT NULL, `unread` INTEGER NOT NULL, `userMentions` INTEGER, `groupMentions` INTEGER, `updatedAt` INTEGER, `timestamp` INTEGER, `lastSeen` INTEGER, `lastMessageText` TEXT, `lastMessageUserId` TEXT, `lastMessageTimestamp` INTEGER, `broadcast` INTEGER, PRIMARY KEY(`id`), FOREIGN KEY(`ownerId`) REFERENCES `users`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION , FOREIGN KEY(`userId`) REFERENCES `users`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION , FOREIGN KEY(`lastMessageUserId`) REFERENCES `users`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION )",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "subscriptionId",
"columnName": "subscriptionId",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "type",
"columnName": "type",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "fullname",
"columnName": "fullname",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "userId",
"columnName": "userId",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "ownerId",
"columnName": "ownerId",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "readonly",
"columnName": "readonly",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "isDefault",
"columnName": "isDefault",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "favorite",
"columnName": "favorite",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "open",
"columnName": "open",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "alert",
"columnName": "alert",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "unread",
"columnName": "unread",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "userMentions",
"columnName": "userMentions",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "groupMentions",
"columnName": "groupMentions",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "updatedAt",
"columnName": "updatedAt",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "timestamp",
"columnName": "timestamp",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "lastSeen",
"columnName": "lastSeen",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "lastMessageText",
"columnName": "lastMessageText",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "lastMessageUserId",
"columnName": "lastMessageUserId",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "lastMessageTimestamp",
"columnName": "lastMessageTimestamp",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "broadcast",
"columnName": "broadcast",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": false
},
"indices": [
{
"name": "index_chatrooms_userId",
"unique": false,
"columnNames": [
"userId"
],
"createSql": "CREATE INDEX `index_chatrooms_userId` ON `${TABLE_NAME}` (`userId`)"
},
{
"name": "index_chatrooms_ownerId",
"unique": false,
"columnNames": [
"ownerId"
],
"createSql": "CREATE INDEX `index_chatrooms_ownerId` ON `${TABLE_NAME}` (`ownerId`)"
},
{
"name": "index_chatrooms_subscriptionId",
"unique": true,
"columnNames": [
"subscriptionId"
],
"createSql": "CREATE UNIQUE INDEX `index_chatrooms_subscriptionId` ON `${TABLE_NAME}` (`subscriptionId`)"
},
{
"name": "index_chatrooms_updatedAt",
"unique": false,
"columnNames": [
"updatedAt"
],
"createSql": "CREATE INDEX `index_chatrooms_updatedAt` ON `${TABLE_NAME}` (`updatedAt`)"
},
{
"name": "index_chatrooms_lastMessageUserId",
"unique": false,
"columnNames": [
"lastMessageUserId"
],
"createSql": "CREATE INDEX `index_chatrooms_lastMessageUserId` ON `${TABLE_NAME}` (`lastMessageUserId`)"
}
],
"foreignKeys": [
{
"table": "users",
"onDelete": "NO ACTION",
"onUpdate": "NO ACTION",
"columns": [
"ownerId"
],
"referencedColumns": [
"id"
]
},
{
"table": "users",
"onDelete": "NO ACTION",
"onUpdate": "NO ACTION",
"columns": [
"userId"
],
"referencedColumns": [
"id"
]
},
{
"table": "users",
"onDelete": "NO ACTION",
"onUpdate": "NO ACTION",
"columns": [
"lastMessageUserId"
],
"referencedColumns": [
"id"
]
}
]
},
{
"tableName": "messages",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `roomId` TEXT NOT NULL, `message` TEXT NOT NULL, `timestamp` INTEGER NOT NULL, `senderId` TEXT, `updatedAt` INTEGER, `editedAt` INTEGER, `editedBy` TEXT, `senderAlias` TEXT, `avatar` TEXT, `type` TEXT, `groupable` INTEGER NOT NULL, `parseUrls` INTEGER NOT NULL, `pinned` INTEGER NOT NULL, `role` TEXT, `synced` INTEGER NOT NULL, `unread` INTEGER, PRIMARY KEY(`id`), FOREIGN KEY(`senderId`) REFERENCES `users`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION , FOREIGN KEY(`editedBy`) REFERENCES `users`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION )",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "roomId",
"columnName": "roomId",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "message",
"columnName": "message",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "timestamp",
"columnName": "timestamp",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "senderId",
"columnName": "senderId",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "updatedAt",
"columnName": "updatedAt",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "editedAt",
"columnName": "editedAt",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "editedBy",
"columnName": "editedBy",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "senderAlias",
"columnName": "senderAlias",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "avatar",
"columnName": "avatar",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "type",
"columnName": "type",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "groupable",
"columnName": "groupable",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "parseUrls",
"columnName": "parseUrls",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "pinned",
"columnName": "pinned",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "role",
"columnName": "role",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "synced",
"columnName": "synced",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "unread",
"columnName": "unread",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": [
{
"table": "users",
"onDelete": "NO ACTION",
"onUpdate": "NO ACTION",
"columns": [
"senderId"
],
"referencedColumns": [
"id"
]
},
{
"table": "users",
"onDelete": "NO ACTION",
"onUpdate": "NO ACTION",
"columns": [
"editedBy"
],
"referencedColumns": [
"id"
]
}
]
},
{
"tableName": "message_favorites",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`messageId` TEXT NOT NULL, `userId` TEXT NOT NULL, PRIMARY KEY(`messageId`, `userId`), FOREIGN KEY(`messageId`) REFERENCES `messages`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`userId`) REFERENCES `users`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION )",
"fields": [
{
"fieldPath": "messageId",
"columnName": "messageId",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "userId",
"columnName": "userId",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"messageId",
"userId"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": [
{
"table": "messages",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"messageId"
],
"referencedColumns": [
"id"
]
},
{
"table": "users",
"onDelete": "NO ACTION",
"onUpdate": "NO ACTION",
"columns": [
"userId"
],
"referencedColumns": [
"id"
]
}
]
},
{
"tableName": "message_mentions",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`messageId` TEXT NOT NULL, `userId` TEXT NOT NULL, PRIMARY KEY(`messageId`, `userId`), FOREIGN KEY(`messageId`) REFERENCES `messages`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`userId`) REFERENCES `users`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION )",
"fields": [
{
"fieldPath": "messageId",
"columnName": "messageId",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "userId",
"columnName": "userId",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"messageId",
"userId"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": [
{
"table": "messages",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"messageId"
],
"referencedColumns": [
"id"
]
},
{
"table": "users",
"onDelete": "NO ACTION",
"onUpdate": "NO ACTION",
"columns": [
"userId"
],
"referencedColumns": [
"id"
]
}
]
},
{
"tableName": "message_channels",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`messageId` TEXT NOT NULL, `roomId` TEXT NOT NULL, `roomName` TEXT, PRIMARY KEY(`messageId`, `roomId`), FOREIGN KEY(`messageId`) REFERENCES `messages`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "messageId",
"columnName": "messageId",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "roomId",
"columnName": "roomId",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "roomName",
"columnName": "roomName",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"messageId",
"roomId"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": [
{
"table": "messages",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"messageId"
],
"referencedColumns": [
"id"
]
}
]
},
{
"tableName": "attachments",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` TEXT NOT NULL, `message_id` TEXT NOT NULL, `title` TEXT, `type` TEXT, `description` TEXT, `text` TEXT, `author_name` TEXT, `author_icon` TEXT, `author_link` TEXT, `thumb_url` TEXT, `color` TEXT, `fallback` TEXT, `title_link` TEXT, `title_link_download` INTEGER NOT NULL, `image_url` TEXT, `image_type` TEXT, `image_size` INTEGER, `video_url` TEXT, `video_type` TEXT, `video_size` INTEGER, `audio_url` TEXT, `audio_type` TEXT, `audio_size` INTEGER, `message_link` TEXT, `timestamp` INTEGER, `has_actions` INTEGER NOT NULL, `has_fields` INTEGER NOT NULL, `button_alignment` TEXT, PRIMARY KEY(`_id`), FOREIGN KEY(`message_id`) REFERENCES `messages`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "_id",
"columnName": "_id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "messageId",
"columnName": "message_id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "title",
"columnName": "title",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "type",
"columnName": "type",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "description",
"columnName": "description",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "text",
"columnName": "text",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "authorName",
"columnName": "author_name",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "authorIcon",
"columnName": "author_icon",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "authorLink",
"columnName": "author_link",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "thumbUrl",
"columnName": "thumb_url",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "color",
"columnName": "color",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "fallback",
"columnName": "fallback",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "titleLink",
"columnName": "title_link",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "titleLinkDownload",
"columnName": "title_link_download",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "imageUrl",
"columnName": "image_url",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "imageType",
"columnName": "image_type",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "imageSize",
"columnName": "image_size",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "videoUrl",
"columnName": "video_url",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "videoType",
"columnName": "video_type",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "videoSize",
"columnName": "video_size",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "audioUrl",
"columnName": "audio_url",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "audioType",
"columnName": "audio_type",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "audioSize",
"columnName": "audio_size",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "messageLink",
"columnName": "message_link",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "timestamp",
"columnName": "timestamp",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "hasActions",
"columnName": "has_actions",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "hasFields",
"columnName": "has_fields",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "buttonAlignment",
"columnName": "button_alignment",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"_id"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": [
{
"table": "messages",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"message_id"
],
"referencedColumns": [
"id"
]
}
]
},
{
"tableName": "attachment_fields",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `attachmentId` TEXT NOT NULL, `title` TEXT NOT NULL, `value` TEXT NOT NULL, FOREIGN KEY(`attachmentId`) REFERENCES `attachments`(`_id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "attachmentId",
"columnName": "attachmentId",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "title",
"columnName": "title",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "value",
"columnName": "value",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": true
},
"indices": [
{
"name": "index_attachment_fields_attachmentId",
"unique": false,
"columnNames": [
"attachmentId"
],
"createSql": "CREATE INDEX `index_attachment_fields_attachmentId` ON `${TABLE_NAME}` (`attachmentId`)"
}
],
"foreignKeys": [
{
"table": "attachments",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"attachmentId"
],
"referencedColumns": [
"_id"
]
}
]
},
{
"tableName": "attachment_action",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `attachmentId` TEXT NOT NULL, `type` TEXT NOT NULL, `text` TEXT, `url` TEXT, `isWebView` INTEGER, `webViewHeightRatio` TEXT, `imageUrl` TEXT, `message` TEXT, `isMessageInChatWindow` INTEGER, FOREIGN KEY(`attachmentId`) REFERENCES `attachments`(`_id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "attachmentId",
"columnName": "attachmentId",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "type",
"columnName": "type",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "text",
"columnName": "text",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "url",
"columnName": "url",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "isWebView",
"columnName": "isWebView",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "webViewHeightRatio",
"columnName": "webViewHeightRatio",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "imageUrl",
"columnName": "imageUrl",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "message",
"columnName": "message",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "isMessageInChatWindow",
"columnName": "isMessageInChatWindow",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": true
},
"indices": [
{
"name": "index_attachment_action_attachmentId",
"unique": false,
"columnNames": [
"attachmentId"
],
"createSql": "CREATE INDEX `index_attachment_action_attachmentId` ON `${TABLE_NAME}` (`attachmentId`)"
}
],
"foreignKeys": [
{
"table": "attachments",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"attachmentId"
],
"referencedColumns": [
"_id"
]
}
]
},
{
"tableName": "urls",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`urlId` INTEGER PRIMARY KEY AUTOINCREMENT, `messageId` TEXT NOT NULL, `url` TEXT NOT NULL, `hostname` TEXT, `title` TEXT, `description` TEXT, `imageUrl` TEXT, FOREIGN KEY(`messageId`) REFERENCES `messages`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "urlId",
"columnName": "urlId",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "messageId",
"columnName": "messageId",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "url",
"columnName": "url",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "hostname",
"columnName": "hostname",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "title",
"columnName": "title",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "description",
"columnName": "description",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "imageUrl",
"columnName": "imageUrl",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"urlId"
],
"autoGenerate": true
},
"indices": [
{
"name": "index_urls_messageId",
"unique": false,
"columnNames": [
"messageId"
],
"createSql": "CREATE INDEX `index_urls_messageId` ON `${TABLE_NAME}` (`messageId`)"
}
],
"foreignKeys": [
{
"table": "messages",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"messageId"
],
"referencedColumns": [
"id"
]
}
]
},
{
"tableName": "reactions",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`reaction` TEXT NOT NULL, `messageId` TEXT NOT NULL, `count` INTEGER NOT NULL, `usernames` TEXT NOT NULL, PRIMARY KEY(`reaction`), FOREIGN KEY(`messageId`) REFERENCES `messages`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "reaction",
"columnName": "reaction",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "messageId",
"columnName": "messageId",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "count",
"columnName": "count",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "usernames",
"columnName": "usernames",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"reaction"
],
"autoGenerate": false
},
"indices": [
{
"name": "index_reactions_messageId",
"unique": false,
"columnNames": [
"messageId"
],
"createSql": "CREATE INDEX `index_reactions_messageId` ON `${TABLE_NAME}` (`messageId`)"
}
],
"foreignKeys": [
{
"table": "messages",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"messageId"
],
"referencedColumns": [
"id"
]
}
]
},
{
"tableName": "messages_sync",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`roomId` TEXT NOT NULL, `timestamp` INTEGER NOT NULL, PRIMARY KEY(`roomId`))",
"fields": [
{
"fieldPath": "roomId",
"columnName": "roomId",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "timestamp",
"columnName": "timestamp",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"roomId"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
}
],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"db46c12dbb8747200288f48d5dc5558b\")"
]
}
}
\ No newline at end of file
package chat.rocket.android.chatroom.adapter package chat.rocket.android.chatroom.adapter
/*
import android.view.View import android.view.View
import chat.rocket.android.chatroom.uimodel.ActionsAttachmentUiModel import chat.rocket.android.chatroom.uimodel.ActionsAttachmentUiModel
import chat.rocket.android.emoji.EmojiReactionListener import chat.rocket.android.emoji.EmojiReactionListener
...@@ -40,4 +41,4 @@ class ActionsAttachmentViewHolder( ...@@ -40,4 +41,4 @@ class ActionsAttachmentViewHolder(
interface ActionAttachmentOnClickListener { interface ActionAttachmentOnClickListener {
fun onActionClicked(view: View, action: Action) fun onActionClicked(view: View, action: Action)
} }*/
\ No newline at end of file
package chat.rocket.android.chatroom.adapter
import android.animation.ValueAnimator
import android.content.Intent
import android.view.View
import android.view.ViewGroup
import android.view.animation.LinearInterpolator
import androidx.core.content.ContextCompat
import androidx.core.net.toUri
import androidx.core.view.isInvisible
import androidx.core.view.isVisible
import androidx.recyclerview.widget.LinearLayoutManager
import chat.rocket.android.R
import chat.rocket.android.chatroom.uimodel.AttachmentUiModel
import chat.rocket.android.emoji.EmojiReactionListener
import chat.rocket.android.helper.ImageHelper
import chat.rocket.android.player.PlayerActivity
import chat.rocket.android.util.extensions.content
import chat.rocket.android.util.extensions.isVisible
import chat.rocket.android.util.extensions.openTabbedUrl
import chat.rocket.android.util.extensions.setOnClickListener
import chat.rocket.core.model.attachment.actions.Action
import com.facebook.drawee.backends.pipeline.Fresco
import kotlinx.android.synthetic.main.item_message_attachment.view.*
import timber.log.Timber
class AttachmentViewHolder(
itemView: View,
listener: ActionsListener,
reactionListener: EmojiReactionListener? = null,
var actionAttachmentOnClickListener: ActionAttachmentOnClickListener
) : BaseViewHolder<AttachmentUiModel>(itemView, listener, reactionListener) {
private val messageViews = listOf<View>(
itemView.text_sender,
itemView.text_message_time,
itemView.text_content,
itemView.text_view_more
)
private val audioVideoViews = listOf<View>(
itemView.audio_video_attachment,
itemView.play_button
)
private val quoteBarColor = ContextCompat.getColor(itemView.context, R.color.quoteBar)
init {
with(itemView) {
setupActionMenu(attachment_container)
}
}
override fun bindViews(data: AttachmentUiModel) {
with(itemView) {
val showQuoteBar = shouldShowQuoteBar(data)
file_name.isVisible = false
image_attachment.isVisible = data.hasImage
audio_video_attachment.isVisible = data.hasAudioOrVideo
text_file_name.isVisible = false
// Media attachments
when {
data.hasImage -> bindImage(data)
data.hasAudioOrVideo -> bindAudioOrVideo(data)
data.hasFile -> bindFile(data)
}
file_description.isVisible = data.hasDescription
file_description.text = data.description
messageViews.isVisible = data.hasMessage
if (data.hasMessage) {
bindMessage(data)
}
author_icon.isInvisible = !(data.hasAuthorIcon && data.hasAuthorLink && data.hasAuthorName)
text_author_name.isVisible = data.hasAuthorLink && data.hasAuthorName
if (data.hasAuthorLink && data.hasAuthorName) {
bindAuthorLink(data)
}
attachment_text.isVisible = !data.hasMedia && !data.hasMessage && data.hasText
attachment_text.text = data.text
file_name_not_file_type.isVisible = !data.hasFile && data.hasTitleLink
if (!data.hasFile && data.hasTitleLink) {
bindTitleLink(data)
}
text_fields.isVisible = data.hasFields
if (data.hasFields) {
bindFields(data)
}
quote_bar.isVisible = showQuoteBar
if (data.color != null) {
quote_bar.setColorFilter(data.color)
} else {
quote_bar.setColorFilter(quoteBarColor)
}
actions_list.isVisible = data.hasActions
if (data.hasActions) {
bindActions(data)
}
}
}
private fun shouldShowQuoteBar(data: AttachmentUiModel): Boolean {
return data.hasFields || (data.hasAuthorLink && data.hasAuthorName) || data.hasMessage || (!data.hasFile && data.hasTitleLink)
}
private fun bindImage(data: AttachmentUiModel) {
with(itemView) {
val controller = Fresco.newDraweeControllerBuilder().apply {
setUri(data.imageUrl)
autoPlayAnimations = true
oldController = image_attachment.controller
}.build()
image_attachment.controller = controller
image_attachment.setOnClickListener {
ImageHelper.openImage(
context,
data.imageUrl!!,
data.title?.toString()
)
}
file_name.isVisible = data.hasTitle
file_name.text = data.title
file_text.isVisible = data.hasText
file_text.text = data.text
}
}
private fun bindAudioOrVideo(data: AttachmentUiModel) {
with(itemView) {
text_file_name.content = data.title
file_text.isVisible = data.hasText
file_text.text = data.text
val url = if (data.hasVideo) data.videoUrl else data.audioUrl
audioVideoViews.setOnClickListener { view ->
url?.let {
PlayerActivity.play(view.context, url)
}
}
}
}
private fun bindFile(data: AttachmentUiModel) {
with(itemView) {
text_file_name.isVisible = true
text_file_name.content = data.title
text_file_name.setOnClickListener {
it.context.startActivity(Intent(Intent.ACTION_VIEW, data.titleLink?.toUri()))
}
}
}
private fun bindMessage(data: AttachmentUiModel) {
with(itemView) {
val collapsedHeight = context.resources.getDimensionPixelSize(R.dimen.quote_collapsed_height)
val viewMore = context.getString(R.string.msg_view_more)
val viewLess = context.getString(R.string.msg_view_less)
text_message_time.text = data.timestamp
text_sender.text = data.authorName
text_content.text = data.text
text_view_more.isVisible = true
text_view_more.text = if (isExpanded()) viewLess else viewMore
val lp = text_content.layoutParams
lp.height = ViewGroup.LayoutParams.WRAP_CONTENT
text_content.layoutParams = lp
text_content.addOnLayoutChangeListener(object : View.OnLayoutChangeListener {
override fun onLayoutChange(v: View, left: Int, top: Int, right: Int, bottom: Int,
oldLeft: Int, oldTop: Int, oldRight: Int, oldBottom: Int) {
val textMeasuredHeight = bottom - top
if (collapsedHeight >= textMeasuredHeight) {
text_view_more.isVisible = false
text_content.removeOnLayoutChangeListener(this)
return
}
val expandAnimation = ValueAnimator
.ofInt(collapsedHeight, textMeasuredHeight)
.setDuration(300)
expandAnimation.interpolator = LinearInterpolator()
val collapseAnimation = ValueAnimator
.ofInt(textMeasuredHeight, collapsedHeight)
.setDuration(300)
collapseAnimation.interpolator = LinearInterpolator()
expandAnimation.addUpdateListener {
val value = it.animatedValue as Int
lp.height = value
text_content.layoutParams = lp
if (value == textMeasuredHeight) {
text_view_more.text = viewLess
lp.height = ViewGroup.LayoutParams.WRAP_CONTENT
} else {
text_view_more.text = viewMore
}
}
collapseAnimation.addUpdateListener {
val value = it.animatedValue as Int
lp.height = value
text_content.layoutParams = lp
if (value == textMeasuredHeight) {
text_view_more.text = viewLess
lp.height = ViewGroup.LayoutParams.WRAP_CONTENT
} else {
text_view_more.text = viewMore
}
}
text_view_more.setOnClickListener {
if (expandAnimation.isRunning) return@setOnClickListener
if (isExpanded()) {
collapseAnimation.start()
} else {
expandAnimation.start()
}
}
text_content.removeOnLayoutChangeListener(this)
}
})
}
}
private fun bindAuthorLink(data: AttachmentUiModel) {
with(itemView) {
author_icon.setImageURI(data.authorIcon)
text_author_name.content = data.authorName
text_author_name.setOnClickListener {
openTabbedUrl(data.authorLink)
}
}
}
private fun bindTitleLink(data: AttachmentUiModel) {
with(itemView) {
val filename = data.title ?: data.titleLink
file_name_not_file_type.text = filename
file_name_not_file_type.setOnClickListener {
openTabbedUrl(data.titleLink)
}
}
}
private fun bindFields(data: AttachmentUiModel) {
with(itemView) {
text_fields.content = data.fields
}
}
private fun bindActions(data: AttachmentUiModel) {
val actions = data.actions
val alignment = data.buttonAlignment
Timber.d("no of actions : ${actions!!.size} : $actions")
with(itemView) {
attachment_text.isVisible = data.hasText
attachment_text.text = data.text
actions_list.layoutManager = LinearLayoutManager(itemView.context,
when (alignment) {
"horizontal" -> LinearLayoutManager.HORIZONTAL
else -> LinearLayoutManager.VERTICAL //Default
}, false)
actions_list.adapter = ActionsListAdapter(actions, actionAttachmentOnClickListener)
}
}
private fun isExpanded(): Boolean {
with(itemView) {
val lp = text_content.layoutParams
return lp.height == ViewGroup.LayoutParams.WRAP_CONTENT
}
}
}
interface ActionAttachmentOnClickListener {
fun onActionClicked(view: View, action: Action)
}
\ No newline at end of file
package chat.rocket.android.chatroom.adapter package chat.rocket.android.chatroom.adapter
/*
import android.view.View import android.view.View
import androidx.core.view.isVisible import androidx.core.view.isVisible
import chat.rocket.android.chatroom.uimodel.AudioAttachmentUiModel import chat.rocket.android.chatroom.uimodel.AudioAttachmentUiModel
...@@ -30,4 +31,4 @@ class AudioAttachmentViewHolder(itemView: View, ...@@ -30,4 +31,4 @@ class AudioAttachmentViewHolder(itemView: View,
} }
} }
} }
} }*/
\ No newline at end of file
package chat.rocket.android.chatroom.adapter package chat.rocket.android.chatroom.adapter
/*
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import android.view.View import android.view.View
...@@ -46,4 +47,4 @@ class AuthorAttachmentViewHolder(itemView: View, ...@@ -46,4 +47,4 @@ class AuthorAttachmentViewHolder(itemView: View,
} }
} }
} }
} }*/
\ No newline at end of file
...@@ -36,7 +36,7 @@ class ChatRoomAdapter( ...@@ -36,7 +36,7 @@ class ChatRoomAdapter(
val view = parent.inflate(R.layout.item_message) val view = parent.inflate(R.layout.item_message)
MessageViewHolder(view, actionsListener, reactionListener) MessageViewHolder(view, actionsListener, reactionListener)
} }
BaseUiModel.ViewType.IMAGE_ATTACHMENT -> { /*BaseUiModel.ViewType.IMAGE_ATTACHMENT -> {
val view = parent.inflate(R.layout.message_attachment) val view = parent.inflate(R.layout.message_attachment)
ImageAttachmentViewHolder(view, actionsListener, reactionListener) ImageAttachmentViewHolder(view, actionsListener, reactionListener)
} }
...@@ -47,12 +47,12 @@ class ChatRoomAdapter( ...@@ -47,12 +47,12 @@ class ChatRoomAdapter(
BaseUiModel.ViewType.VIDEO_ATTACHMENT -> { BaseUiModel.ViewType.VIDEO_ATTACHMENT -> {
val view = parent.inflate(R.layout.message_attachment) val view = parent.inflate(R.layout.message_attachment)
VideoAttachmentViewHolder(view, actionsListener, reactionListener) VideoAttachmentViewHolder(view, actionsListener, reactionListener)
} }*/
BaseUiModel.ViewType.URL_PREVIEW -> { BaseUiModel.ViewType.URL_PREVIEW -> {
val view = parent.inflate(R.layout.message_url_preview) val view = parent.inflate(R.layout.message_url_preview)
UrlPreviewViewHolder(view, actionsListener, reactionListener) UrlPreviewViewHolder(view, actionsListener, reactionListener)
} }
BaseUiModel.ViewType.MESSAGE_ATTACHMENT -> { /*BaseUiModel.ViewType.MESSAGE_ATTACHMENT -> {
val view = parent.inflate(R.layout.item_message_attachment) val view = parent.inflate(R.layout.item_message_attachment)
MessageAttachmentViewHolder(view, actionsListener, reactionListener) MessageAttachmentViewHolder(view, actionsListener, reactionListener)
} }
...@@ -67,6 +67,10 @@ class ChatRoomAdapter( ...@@ -67,6 +67,10 @@ class ChatRoomAdapter(
BaseUiModel.ViewType.GENERIC_FILE_ATTACHMENT -> { BaseUiModel.ViewType.GENERIC_FILE_ATTACHMENT -> {
val view = parent.inflate(R.layout.item_file_attachment) val view = parent.inflate(R.layout.item_file_attachment)
GenericFileAttachmentViewHolder(view, actionsListener, reactionListener) GenericFileAttachmentViewHolder(view, actionsListener, reactionListener)
}*/
BaseUiModel.ViewType.ATTACHMENT -> {
val view = parent.inflate(R.layout.item_message_attachment)
AttachmentViewHolder(view, actionsListener, reactionListener, actionAttachmentOnClickListener)
} }
BaseUiModel.ViewType.MESSAGE_REPLY -> { BaseUiModel.ViewType.MESSAGE_REPLY -> {
val view = parent.inflate(R.layout.item_message_reply) val view = parent.inflate(R.layout.item_message_reply)
...@@ -74,10 +78,10 @@ class ChatRoomAdapter( ...@@ -74,10 +78,10 @@ class ChatRoomAdapter(
actionSelectListener?.openDirectMessage(roomName, permalink) actionSelectListener?.openDirectMessage(roomName, permalink)
} }
} }
BaseUiModel.ViewType.ACTIONS_ATTACHMENT -> { /*BaseUiModel.ViewType.ACTIONS_ATTACHMENT -> {
val view = parent.inflate(R.layout.item_actions_attachment) val view = parent.inflate(R.layout.item_actions_attachment)
ActionsAttachmentViewHolder(view, actionsListener, reactionListener, actionAttachmentOnClickListener) ActionsAttachmentViewHolder(view, actionsListener, reactionListener, actionAttachmentOnClickListener)
} }*/
else -> { else -> {
throw InvalidParameterException("TODO - implement for ${viewType.toViewType()}") throw InvalidParameterException("TODO - implement for ${viewType.toViewType()}")
} }
...@@ -113,26 +117,28 @@ class ChatRoomAdapter( ...@@ -113,26 +117,28 @@ class ChatRoomAdapter(
when (holder) { when (holder) {
is MessageViewHolder -> is MessageViewHolder ->
holder.bind(dataSet[position] as MessageUiModel) holder.bind(dataSet[position] as MessageUiModel)
is ImageAttachmentViewHolder -> /*is ImageAttachmentViewHolder ->
holder.bind(dataSet[position] as ImageAttachmentUiModel) holder.bind(dataSet[position] as ImageAttachmentUiModel)
is AudioAttachmentViewHolder -> is AudioAttachmentViewHolder ->
holder.bind(dataSet[position] as AudioAttachmentUiModel) holder.bind(dataSet[position] as AudioAttachmentUiModel)
is VideoAttachmentViewHolder -> is VideoAttachmentViewHolder ->
holder.bind(dataSet[position] as VideoAttachmentUiModel) holder.bind(dataSet[position] as VideoAttachmentUiModel)*/
is UrlPreviewViewHolder -> is UrlPreviewViewHolder ->
holder.bind(dataSet[position] as UrlPreviewUiModel) holder.bind(dataSet[position] as UrlPreviewUiModel)
is MessageAttachmentViewHolder -> /*is MessageAttachmentViewHolder ->
holder.bind(dataSet[position] as MessageAttachmentUiModel) holder.bind(dataSet[position] as MessageAttachmentUiModel)
is AuthorAttachmentViewHolder -> is AuthorAttachmentViewHolder ->
holder.bind(dataSet[position] as AuthorAttachmentUiModel) holder.bind(dataSet[position] as AuthorAttachmentUiModel)
is ColorAttachmentViewHolder -> is ColorAttachmentViewHolder ->
holder.bind(dataSet[position] as ColorAttachmentUiModel) holder.bind(dataSet[position] as ColorAttachmentUiModel)
is GenericFileAttachmentViewHolder -> is GenericFileAttachmentViewHolder ->
holder.bind(dataSet[position] as GenericFileAttachmentUiModel) holder.bind(dataSet[position] as GenericFileAttachmentUiModel)*/
is MessageReplyViewHolder -> is MessageReplyViewHolder ->
holder.bind(dataSet[position] as MessageReplyUiModel) holder.bind(dataSet[position] as MessageReplyUiModel)
is ActionsAttachmentViewHolder -> /*is ActionsAttachmentViewHolder ->
holder.bind(dataSet[position] as ActionsAttachmentUiModel) holder.bind(dataSet[position] as ActionsAttachmentUiModel)*/
is AttachmentViewHolder ->
holder.bind(dataSet[position] as AttachmentUiModel)
} }
} }
...@@ -140,8 +146,7 @@ class ChatRoomAdapter( ...@@ -140,8 +146,7 @@ class ChatRoomAdapter(
val model = dataSet[position] val model = dataSet[position]
return when (model) { return when (model) {
is MessageUiModel -> model.messageId.hashCode().toLong() is MessageUiModel -> model.messageId.hashCode().toLong()
is BaseFileAttachmentUiModel -> model.id is AttachmentUiModel -> model.id
is AuthorAttachmentUiModel -> model.id
else -> return position.toLong() else -> return position.toLong()
} }
} }
......
package chat.rocket.android.chatroom.adapter package chat.rocket.android.chatroom.adapter
/*
import android.graphics.drawable.ColorDrawable import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
...@@ -30,11 +31,11 @@ class ColorAttachmentViewHolder(itemView: View, ...@@ -30,11 +31,11 @@ class ColorAttachmentViewHolder(itemView: View,
override fun bindViews(data: ColorAttachmentUiModel) { override fun bindViews(data: ColorAttachmentUiModel) {
with(itemView) { with(itemView) {
quote_bar.setColorFilter(data.color) quote_bar.setColorFilter(data.color)
if (data.text.isNotEmpty()) { if (data.text.isNullOrEmpty()) {
attachment_text.isVisible = false
} else {
attachment_text.isVisible = true attachment_text.isVisible = true
attachment_text.text = data.text attachment_text.text = data.text
} else {
attachment_text.isVisible = false
} }
if (data.fields.isNullOrEmpty()) { if (data.fields.isNullOrEmpty()) {
...@@ -46,4 +47,4 @@ class ColorAttachmentViewHolder(itemView: View, ...@@ -46,4 +47,4 @@ class ColorAttachmentViewHolder(itemView: View,
} }
} }
} }*/
\ No newline at end of file
package chat.rocket.android.chatroom.adapter package chat.rocket.android.chatroom.adapter
/*
import android.content.Intent import android.content.Intent
import android.view.View import android.view.View
import androidx.core.net.toUri import androidx.core.net.toUri
...@@ -28,4 +29,4 @@ class GenericFileAttachmentViewHolder(itemView: View, ...@@ -28,4 +29,4 @@ class GenericFileAttachmentViewHolder(itemView: View,
} }
} }
} }
} }*/
\ No newline at end of file
package chat.rocket.android.chatroom.adapter package chat.rocket.android.chatroom.adapter
/*
import android.view.View import android.view.View
import chat.rocket.android.chatroom.uimodel.ImageAttachmentUiModel import chat.rocket.android.chatroom.uimodel.ImageAttachmentUiModel
import chat.rocket.android.helper.ImageHelper import chat.rocket.android.helper.ImageHelper
...@@ -39,4 +40,4 @@ class ImageAttachmentViewHolder( ...@@ -39,4 +40,4 @@ class ImageAttachmentViewHolder(
} }
} }
} }
} }*/
\ No newline at end of file
package chat.rocket.android.chatroom.adapter package chat.rocket.android.chatroom.adapter
import android.animation.ValueAnimator /*import android.animation.ValueAnimator
import android.text.method.LinkMovementMethod import android.text.method.LinkMovementMethod
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
...@@ -9,7 +9,7 @@ import androidx.core.view.isVisible ...@@ -9,7 +9,7 @@ import androidx.core.view.isVisible
import chat.rocket.android.R import chat.rocket.android.R
import chat.rocket.android.chatroom.uimodel.MessageAttachmentUiModel import chat.rocket.android.chatroom.uimodel.MessageAttachmentUiModel
import chat.rocket.android.emoji.EmojiReactionListener import chat.rocket.android.emoji.EmojiReactionListener
import kotlinx.android.synthetic.main.item_message_attachment.view.* import kotlinx.android.synthetic.main.item_message_attachment_old.view.*
class MessageAttachmentViewHolder( class MessageAttachmentViewHolder(
itemView: View, itemView: View,
...@@ -104,4 +104,4 @@ class MessageAttachmentViewHolder( ...@@ -104,4 +104,4 @@ class MessageAttachmentViewHolder(
return lp.height == ViewGroup.LayoutParams.WRAP_CONTENT return lp.height == ViewGroup.LayoutParams.WRAP_CONTENT
} }
} }
} }*/
package chat.rocket.android.chatroom.adapter package chat.rocket.android.chatroom.adapter
/*
import android.view.View import android.view.View
import androidx.core.view.isVisible import androidx.core.view.isVisible
import chat.rocket.android.chatroom.uimodel.VideoAttachmentUiModel import chat.rocket.android.chatroom.uimodel.VideoAttachmentUiModel
...@@ -30,4 +31,4 @@ class VideoAttachmentViewHolder(itemView: View, ...@@ -30,4 +31,4 @@ class VideoAttachmentViewHolder(itemView: View,
} }
} }
} }
} }*/
\ No newline at end of file
package chat.rocket.android.chatroom.uimodel package chat.rocket.android.chatroom.uimodel
import chat.rocket.android.R /*import chat.rocket.android.R
import chat.rocket.core.model.Message import chat.rocket.core.model.Message
import chat.rocket.core.model.attachment.actions.Action import chat.rocket.core.model.attachment.actions.Action
import chat.rocket.core.model.attachment.actions.ActionsAttachment
data class ActionsAttachmentUiModel( data class ActionsAttachmentUiModel(
override val attachmentUrl: String, override val attachmentUrl: String,
...@@ -26,4 +25,4 @@ data class ActionsAttachmentUiModel( ...@@ -26,4 +25,4 @@ data class ActionsAttachmentUiModel(
get() = BaseUiModel.ViewType.ACTIONS_ATTACHMENT.viewType get() = BaseUiModel.ViewType.ACTIONS_ATTACHMENT.viewType
override val layoutId: Int override val layoutId: Int
get() = R.layout.item_actions_attachment get() = R.layout.item_actions_attachment
} }*/
\ No newline at end of file
package chat.rocket.android.chatroom.uimodel
import chat.rocket.android.R
import chat.rocket.core.model.Message
import chat.rocket.core.model.attachment.Attachment
import chat.rocket.core.model.attachment.actions.Action
data class AttachmentUiModel(
override val message: Message,
override val rawData: Attachment,
override val messageId: String,
override var reactions: List<ReactionUiModel>,
override var nextDownStreamMessage: BaseUiModel<*>? = null,
override var preview: Message?,
override var isTemporary: Boolean,
override var unread: Boolean?,
override var currentDayMarkerText: String,
override var showDayMarker: Boolean,
override var menuItemsToHide: MutableList<Int> = mutableListOf(),
val id: Long,
val title: CharSequence?,
val description: CharSequence?,
val authorName: CharSequence?,
val text: CharSequence?,
val color: Int?,
val imageUrl: String?,
val videoUrl: String?,
val audioUrl: String?,
val titleLink: String?,
val messageLink: String?,
val type: String?,
// TODO - attachments
val timestamp: CharSequence?,
val authorIcon: String?,
val authorLink: String?,
val fields: CharSequence?,
val buttonAlignment: String?,
val actions: List<Action>?
) : BaseUiModel<Attachment> {
override val viewType: Int
get() = BaseUiModel.ViewType.ATTACHMENT.viewType
override val layoutId: Int
get() = R.layout.item_message_attachment
val hasTitle: Boolean
get() = !title.isNullOrEmpty()
val hasDescription: Boolean
get() = !description.isNullOrEmpty()
val hasText: Boolean
get() = !text.isNullOrEmpty()
val hasImage: Boolean
get() = imageUrl.orEmpty().isNotEmpty()
val hasVideo: Boolean
get() = videoUrl.orEmpty().isNotEmpty()
val hasAudio: Boolean
get() = audioUrl.orEmpty().isNotEmpty()
val hasAudioOrVideo: Boolean
get() = hasAudio || hasVideo
val hasFile: Boolean
get() = type.orEmpty().contentEquals("file") && titleLink.orEmpty().isNotEmpty()
val hasTitleLink: Boolean
get() = titleLink.orEmpty().isNotEmpty()
val hasMedia: Boolean
get() = hasImage || hasAudioOrVideo || hasFile
val hasMessage: Boolean
get() = messageLink.orEmpty().isNotEmpty()
val hasAuthorName: Boolean
get() = !authorName.isNullOrEmpty()
val hasAuthorLink: Boolean
get() = authorLink.orEmpty().isNotEmpty()
val hasAuthorIcon: Boolean
get() = authorIcon.orEmpty().isNotEmpty()
val hasFields: Boolean
get() = !fields.isNullOrEmpty()
val hasActions: Boolean
get() = actions != null && actions.isNotEmpty()
}
package chat.rocket.android.chatroom.uimodel package chat.rocket.android.chatroom.uimodel
/*
import chat.rocket.android.R import chat.rocket.android.R
import chat.rocket.core.model.Message import chat.rocket.core.model.Message
import chat.rocket.core.model.attachment.AudioAttachment import chat.rocket.core.model.attachment.AudioAttachment
...@@ -24,4 +25,4 @@ data class AudioAttachmentUiModel( ...@@ -24,4 +25,4 @@ data class AudioAttachmentUiModel(
get() = BaseUiModel.ViewType.AUDIO_ATTACHMENT.viewType get() = BaseUiModel.ViewType.AUDIO_ATTACHMENT.viewType
override val layoutId: Int override val layoutId: Int
get() = R.layout.message_attachment get() = R.layout.message_attachment
} }*/
\ No newline at end of file
package chat.rocket.android.chatroom.uimodel package chat.rocket.android.chatroom.uimodel
/*
import chat.rocket.android.R import chat.rocket.android.R
import chat.rocket.core.model.Message import chat.rocket.core.model.Message
import chat.rocket.core.model.attachment.AuthorAttachment import chat.rocket.core.model.attachment.AuthorAttachment
...@@ -26,4 +27,4 @@ data class AuthorAttachmentUiModel( ...@@ -26,4 +27,4 @@ data class AuthorAttachmentUiModel(
get() = BaseUiModel.ViewType.AUTHOR_ATTACHMENT.viewType get() = BaseUiModel.ViewType.AUTHOR_ATTACHMENT.viewType
override val layoutId: Int override val layoutId: Int
get() = R.layout.item_author_attachment get() = R.layout.item_author_attachment
} }*/
\ No newline at end of file
...@@ -22,15 +22,16 @@ interface BaseUiModel<out T> { ...@@ -22,15 +22,16 @@ interface BaseUiModel<out T> {
MESSAGE(0), MESSAGE(0),
SYSTEM_MESSAGE(1), SYSTEM_MESSAGE(1),
URL_PREVIEW(2), URL_PREVIEW(2),
ATTACHMENT(3),/*
IMAGE_ATTACHMENT(3), IMAGE_ATTACHMENT(3),
VIDEO_ATTACHMENT(4), VIDEO_ATTACHMENT(4),
AUDIO_ATTACHMENT(5), AUDIO_ATTACHMENT(5),
MESSAGE_ATTACHMENT(6), MESSAGE_ATTACHMENT(6),
AUTHOR_ATTACHMENT(7), AUTHOR_ATTACHMENT(7),
COLOR_ATTACHMENT(8), COLOR_ATTACHMENT(8),
GENERIC_FILE_ATTACHMENT(9), GENERIC_FILE_ATTACHMENT(9),*/
MESSAGE_REPLY(10), MESSAGE_REPLY(10)
ACTIONS_ATTACHMENT(11) //ACTIONS_ATTACHMENT(11)
} }
} }
......
package chat.rocket.android.chatroom.uimodel package chat.rocket.android.chatroom.uimodel
/*
import chat.rocket.android.R import chat.rocket.android.R
import chat.rocket.core.model.Message import chat.rocket.core.model.Message
import chat.rocket.core.model.attachment.ColorAttachment import chat.rocket.core.model.attachment.ColorAttachment
...@@ -8,7 +9,7 @@ data class ColorAttachmentUiModel( ...@@ -8,7 +9,7 @@ data class ColorAttachmentUiModel(
override val attachmentUrl: String, override val attachmentUrl: String,
val id: Long, val id: Long,
val color: Int, val color: Int,
val text: CharSequence, val text: CharSequence?,
val fields: CharSequence? = null, val fields: CharSequence? = null,
override val message: Message, override val message: Message,
override val rawData: ColorAttachment, override val rawData: ColorAttachment,
...@@ -26,4 +27,4 @@ data class ColorAttachmentUiModel( ...@@ -26,4 +27,4 @@ data class ColorAttachmentUiModel(
get() = BaseUiModel.ViewType.COLOR_ATTACHMENT.viewType get() = BaseUiModel.ViewType.COLOR_ATTACHMENT.viewType
override val layoutId: Int override val layoutId: Int
get() = R.layout.item_color_attachment get() = R.layout.item_color_attachment
} }*/
\ No newline at end of file
package chat.rocket.android.chatroom.uimodel package chat.rocket.android.chatroom.uimodel
/*
import chat.rocket.android.R import chat.rocket.android.R
import chat.rocket.core.model.Message import chat.rocket.core.model.Message
import chat.rocket.core.model.attachment.GenericFileAttachment import chat.rocket.core.model.attachment.GenericFileAttachment
...@@ -24,4 +25,4 @@ data class GenericFileAttachmentUiModel( ...@@ -24,4 +25,4 @@ data class GenericFileAttachmentUiModel(
get() = BaseUiModel.ViewType.GENERIC_FILE_ATTACHMENT.viewType get() = BaseUiModel.ViewType.GENERIC_FILE_ATTACHMENT.viewType
override val layoutId: Int override val layoutId: Int
get() = R.layout.item_file_attachment get() = R.layout.item_file_attachment
} }*/
\ No newline at end of file
package chat.rocket.android.chatroom.uimodel package chat.rocket.android.chatroom.uimodel
/*
import chat.rocket.android.R import chat.rocket.android.R
import chat.rocket.core.model.Message import chat.rocket.core.model.Message
import chat.rocket.core.model.attachment.ImageAttachment import chat.rocket.core.model.attachment.ImageAttachment
...@@ -26,4 +27,4 @@ data class ImageAttachmentUiModel( ...@@ -26,4 +27,4 @@ data class ImageAttachmentUiModel(
get() = BaseUiModel.ViewType.IMAGE_ATTACHMENT.viewType get() = BaseUiModel.ViewType.IMAGE_ATTACHMENT.viewType
override val layoutId: Int override val layoutId: Int
get() = R.layout.message_attachment get() = R.layout.message_attachment
} }*/
\ No newline at end of file
package chat.rocket.android.chatroom.uimodel package chat.rocket.android.chatroom.uimodel
/*
import chat.rocket.android.R import chat.rocket.android.R
import chat.rocket.core.model.Message import chat.rocket.core.model.Message
...@@ -26,4 +27,4 @@ data class MessageAttachmentUiModel( ...@@ -26,4 +27,4 @@ data class MessageAttachmentUiModel(
override val layoutId: Int override val layoutId: Int
get() = R.layout.item_message_attachment get() = R.layout.item_message_attachment
} }*/
\ No newline at end of file
...@@ -39,16 +39,7 @@ import chat.rocket.core.model.Message ...@@ -39,16 +39,7 @@ import chat.rocket.core.model.Message
import chat.rocket.core.model.MessageType import chat.rocket.core.model.MessageType
import chat.rocket.core.model.ReadReceipt import chat.rocket.core.model.ReadReceipt
import chat.rocket.core.model.attachment.Attachment import chat.rocket.core.model.attachment.Attachment
import chat.rocket.core.model.attachment.AudioAttachment
import chat.rocket.core.model.attachment.AuthorAttachment
import chat.rocket.core.model.attachment.ColorAttachment
import chat.rocket.core.model.attachment.Field import chat.rocket.core.model.attachment.Field
import chat.rocket.core.model.attachment.FileAttachment
import chat.rocket.core.model.attachment.GenericFileAttachment
import chat.rocket.core.model.attachment.ImageAttachment
import chat.rocket.core.model.attachment.MessageAttachment
import chat.rocket.core.model.attachment.VideoAttachment
import chat.rocket.core.model.attachment.actions.ActionsAttachment
import chat.rocket.core.model.isSystemMessage import chat.rocket.core.model.isSystemMessage
import chat.rocket.core.model.url.Url import chat.rocket.core.model.url.Url
import kotlinx.coroutines.experimental.CommonPool import kotlinx.coroutines.experimental.CommonPool
...@@ -304,17 +295,60 @@ class UiModelMapper @Inject constructor( ...@@ -304,17 +295,60 @@ class UiModelMapper @Inject constructor(
} }
private fun mapAttachment(message: Message, attachment: Attachment): BaseUiModel<*>? { private fun mapAttachment(message: Message, attachment: Attachment): BaseUiModel<*>? {
return when (attachment) { return with(attachment) {
is FileAttachment -> mapFileAttachment(message, attachment) val content = stripMessageQuotes(message)
is MessageAttachment -> mapMessageAttachment(message, attachment) val id = attachmentId(message, attachment)
is AuthorAttachment -> mapAuthorAttachment(message, attachment)
is ColorAttachment -> mapColorAttachment(message, attachment) val localDateTime = DateTimeHelper.getLocalDateTime(message.timestamp)
is ActionsAttachment -> mapActionsAttachment(message, attachment) val dayMarkerText = DateTimeHelper.getFormattedDateForMessages(localDateTime, context)
else -> null val fieldsText = mapFields(fields)
val attachmentAuthor = attachment.authorName
val time = attachment.timestamp?.let { getTime(it) }
val imageUrl = attachmentUrl(attachment.imageUrl)
val videoUrl = attachmentUrl(attachment.videoUrl)
val audioUrl = attachmentUrl(attachment.audioUrl)
val titleLink = attachmentUrl(attachment.titleLink)
val attachmentTitle = attachmentTitle(attachment.title, imageUrl, videoUrl, audioUrl, titleLink)
val attachmentText = attachmentText(attachment)
val attachmentDescription = attachmentDescription(attachment)
AttachmentUiModel(
message = message,
rawData = this,
messageId = message.id,
reactions = getReactions(message),
preview = message.copy(message = content.message),
isTemporary = !message.synced,
unread = message.unread,
currentDayMarkerText = dayMarkerText,
showDayMarker = false,
id = id,
title = attachmentTitle,
description = attachmentDescription,
authorName = attachmentAuthor,
text = attachmentText,
color = color?.color,
imageUrl = imageUrl,
videoUrl = videoUrl,
audioUrl = audioUrl,
titleLink = titleLink,
type = type,
messageLink = messageLink,
timestamp = time,
authorIcon = authorIcon,
authorLink = authorLink,
fields = fieldsText,
buttonAlignment = buttonAlignment,
actions = actions
)
} }
} }
private fun mapActionsAttachment(message: Message, attachment: ActionsAttachment): BaseUiModel<*>? { /*private fun mapActionsAttachment(message: Message, attachment: ActionsAttachment): BaseUiModel<*>? {
return with(attachment) { return with(attachment) {
val content = stripMessageQuotes(message) val content = stripMessageQuotes(message)
...@@ -344,7 +378,7 @@ class UiModelMapper @Inject constructor( ...@@ -344,7 +378,7 @@ class UiModelMapper @Inject constructor(
preview = message.copy(message = content.message), unread = message.unread, preview = message.copy(message = content.message), unread = message.unread,
showDayMarker = false, currentDayMarkerText = dayMarkerText) showDayMarker = false, currentDayMarkerText = dayMarkerText)
} }
} }*/
private fun mapFields(fields: List<Field>?): CharSequence? { private fun mapFields(fields: List<Field>?): CharSequence? {
return fields?.let { return fields?.let {
...@@ -364,7 +398,7 @@ class UiModelMapper @Inject constructor( ...@@ -364,7 +398,7 @@ class UiModelMapper @Inject constructor(
} }
} }
private fun mapAuthorAttachment(message: Message, attachment: AuthorAttachment): AuthorAttachmentUiModel { /*private fun mapAuthorAttachment(message: Message, attachment: AuthorAttachment): AuthorAttachmentUiModel {
return with(attachment) { return with(attachment) {
val content = stripMessageQuotes(message) val content = stripMessageQuotes(message)
...@@ -434,48 +468,47 @@ class UiModelMapper @Inject constructor( ...@@ -434,48 +468,47 @@ class UiModelMapper @Inject constructor(
showDayMarker = false, currentDayMarkerText = dayMarkerText) showDayMarker = false, currentDayMarkerText = dayMarkerText)
else -> null else -> null
} }
} }*/
private fun attachmentId(message: Message, attachment: Attachment): Long { private fun attachmentId(message: Message, attachment: Attachment): Long {
return "${message.id}_${attachment.url}".hashCode().toLong() return "${message.id}_${attachment.hashCode()}".hashCode().toLong()
} }
private fun attachmentTitle(attachment: FileAttachment): CharSequence { private fun attachmentTitle(title: String?, vararg url: String?): CharSequence {
return with(attachment) { title?.let { return it }
title?.let { return@with it }
val fileUrl = HttpUrl.parse(url) url.filterNotNull().forEach {
fileUrl?.let { val fileUrl = HttpUrl.parse(it)
return@with it.pathSegments().last() fileUrl?.let { httpUrl ->
return httpUrl.pathSegments().last()
} }
return@with ""
} }
return ""
} }
private fun attachmentUrl(attachment: FileAttachment): String { private fun attachmentUrl(url: String?): String? {
return with(attachment) { if (url.isNullOrEmpty()) return null
if (url.startsWith("http")) return@with url if (url!!.startsWith("http")) return url
val fullUrl = "$baseUrl$url" val fullUrl = "$baseUrl$url"
val httpUrl = HttpUrl.parse(fullUrl) val httpUrl = HttpUrl.parse(fullUrl)
httpUrl?.let { httpUrl?.let {
return@with it.newBuilder().apply { return it.newBuilder().apply {
addQueryParameter("rc_uid", token?.userId) addQueryParameter("rc_uid", token?.userId)
addQueryParameter("rc_token", token?.authToken) addQueryParameter("rc_token", token?.authToken)
}.build().toString() }.build().toString()
} }
// Fallback to baseUrl + url // Fallback to baseUrl + url
return@with fullUrl return fullUrl
}
} }
private fun attachmentText(attachment: FileAttachment): String? { private fun attachmentText(attachment: Attachment): String? {
return attachment.text return attachment.text
} }
private fun attachmentDescription(attachment: FileAttachment): String? { private fun attachmentDescription(attachment: Attachment): String? {
return attachment.description return attachment.description
} }
......
package chat.rocket.android.chatroom.uimodel package chat.rocket.android.chatroom.uimodel
/*
import chat.rocket.android.R import chat.rocket.android.R
import chat.rocket.core.model.Message import chat.rocket.core.model.Message
import chat.rocket.core.model.attachment.VideoAttachment import chat.rocket.core.model.attachment.VideoAttachment
...@@ -24,4 +25,4 @@ data class VideoAttachmentUiModel( ...@@ -24,4 +25,4 @@ data class VideoAttachmentUiModel(
get() = BaseUiModel.ViewType.VIDEO_ATTACHMENT.viewType get() = BaseUiModel.ViewType.VIDEO_ATTACHMENT.viewType
override val layoutId: Int override val layoutId: Int
get() = R.layout.message_attachment get() = R.layout.message_attachment
} }*/
\ No newline at end of file
...@@ -23,7 +23,7 @@ import chat.rocket.android.db.model.UserEntity ...@@ -23,7 +23,7 @@ import chat.rocket.android.db.model.UserEntity
AttachmentFieldEntity::class, AttachmentActionEntity::class, UrlEntity::class, AttachmentFieldEntity::class, AttachmentActionEntity::class, UrlEntity::class,
ReactionEntity::class, MessagesSync::class ReactionEntity::class, MessagesSync::class
], ],
version = 9, version = 10,
exportSchema = true exportSchema = true
) )
abstract class RCDatabase : RoomDatabase() { abstract class RCDatabase : RoomDatabase() {
......
...@@ -7,16 +7,7 @@ import androidx.room.Index ...@@ -7,16 +7,7 @@ import androidx.room.Index
import androidx.room.PrimaryKey import androidx.room.PrimaryKey
import chat.rocket.android.util.extension.orFalse import chat.rocket.android.util.extension.orFalse
import chat.rocket.core.model.attachment.Attachment import chat.rocket.core.model.attachment.Attachment
import chat.rocket.core.model.attachment.AudioAttachment
import chat.rocket.core.model.attachment.AuthorAttachment
import chat.rocket.core.model.attachment.ColorAttachment
import chat.rocket.core.model.attachment.GenericFileAttachment
import chat.rocket.core.model.attachment.ImageAttachment
import chat.rocket.core.model.attachment.MessageAttachment
import chat.rocket.core.model.attachment.VideoAttachment
import chat.rocket.core.model.attachment.actions.ActionsAttachment
import chat.rocket.core.model.attachment.actions.ButtonAction import chat.rocket.core.model.attachment.actions.ButtonAction
import timber.log.Timber
@Entity(tableName = "attachments", @Entity(tableName = "attachments",
foreignKeys = [ foreignKeys = [
...@@ -116,149 +107,51 @@ data class AttachmentActionEntity( ...@@ -116,149 +107,51 @@ data class AttachmentActionEntity(
} }
fun Attachment.asEntity(msgId: String): List<BaseMessageEntity> { fun Attachment.asEntity(msgId: String): List<BaseMessageEntity> {
return when(this) { val attachmentId = "${msgId}_${hashCode()}"
is ImageAttachment -> listOf(asEntity(msgId)) val list = mutableListOf<BaseMessageEntity>()
is VideoAttachment -> listOf(asEntity(msgId))
is AudioAttachment -> listOf(asEntity(msgId))
is AuthorAttachment -> asEntity(msgId)
is ColorAttachment -> asEntity(msgId)
is MessageAttachment -> listOf(asEntity(msgId))
is GenericFileAttachment -> listOf(asEntity(msgId))
is ActionsAttachment -> asEntity(msgId)
else -> {
Timber.d("Missing conversion for: ${javaClass.canonicalName}")
emptyList()
}
}
}
fun ImageAttachment.asEntity(msgId: String): AttachmentEntity =
AttachmentEntity(
_id = "${msgId}_${hashCode()}",
messageId = msgId,
title = title,
description = description,
text = text,
titleLink = titleLink,
titleLinkDownload = titleLinkDownload.orFalse(),
imageUrl = url,
imageType = type,
imageSize = size
)
fun VideoAttachment.asEntity(msgId: String): AttachmentEntity =
AttachmentEntity(
_id = "${msgId}_${hashCode()}",
messageId = msgId,
title = title,
description = description,
text = text,
titleLink = titleLink,
titleLinkDownload = titleLinkDownload.orFalse(),
videoUrl = url,
videoType = type,
videoSize = size
)
fun AudioAttachment.asEntity(msgId: String): AttachmentEntity = val entity = AttachmentEntity(
AttachmentEntity( _id = attachmentId,
_id = "${msgId}_${hashCode()}",
messageId = msgId, messageId = msgId,
title = title, title = title,
type = type,
description = description, description = description,
text = text, text = text,
titleLink = titleLink, titleLink = titleLink,
titleLinkDownload = titleLinkDownload.orFalse(), titleLinkDownload = titleLinkDownload.orFalse(),
audioUrl = url, imageUrl = imageUrl,
audioType = type, imageType = imageType,
audioSize = size imageSize = imageSize,
) videoUrl = videoUrl,
videoType = videoType,
fun AuthorAttachment.asEntity(msgId: String): List<BaseMessageEntity> { videoSize = videoSize,
val list = mutableListOf<BaseMessageEntity>() audioUrl = audioUrl,
val attachment = AttachmentEntity( audioType = audioType,
_id = "${msgId}_${hashCode()}", audioSize = audioSize,
messageId = msgId, authorLink = authorLink,
authorLink = url,
authorIcon = authorIcon, authorIcon = authorIcon,
authorName = authorName, authorName = authorName,
hasFields = fields?.isNotEmpty() == true color = color?.rawColor,
)
list.add(attachment)
fields?.forEach { field ->
val entity = AttachmentFieldEntity(
attachmentId = attachment._id,
title = field.title,
value = field.value
)
list.add(entity)
}
return list
}
fun ColorAttachment.asEntity(msgId: String): List<BaseMessageEntity> {
val list = mutableListOf<BaseMessageEntity>()
val attachment = AttachmentEntity(
_id = "${msgId}_${hashCode()}",
messageId = msgId,
color = color.rawColor,
fallback = fallback, fallback = fallback,
thumbUrl = thumbUrl,
messageLink = messageLink,
timestamp = timestamp,
buttonAlignment = buttonAlignment,
hasActions = actions?.isNotEmpty() == true,
hasFields = fields?.isNotEmpty() == true hasFields = fields?.isNotEmpty() == true
) )
list.add(attachment) list.add(entity)
fields?.forEach { field -> fields?.forEach { field ->
val entity = AttachmentFieldEntity( val entity = AttachmentFieldEntity(
attachmentId = attachment._id, attachmentId = attachmentId,
title = field.title, title = field.title,
value = field.value value = field.value
) )
list.add(entity) list.add(entity)
} }
return list actions?.forEach { action ->
}
// TODO - how to model An message attachment with attachments???
fun MessageAttachment.asEntity(msgId: String): AttachmentEntity =
AttachmentEntity(
_id = "${msgId}_${hashCode()}",
messageId = msgId,
authorName = author,
authorIcon = icon,
text = text,
thumbUrl = thumbUrl,
color = color?.rawColor,
messageLink = url,
timestamp = timestamp
)
fun GenericFileAttachment.asEntity(msgId: String): AttachmentEntity =
AttachmentEntity(
_id = "${msgId}_${hashCode()}",
messageId = msgId,
title = title,
description = description,
text = text,
titleLink = titleLink,
titleLinkDownload = titleLinkDownload ?: false
)
fun ActionsAttachment.asEntity(msgId: String): List<BaseMessageEntity> {
val list = mutableListOf<BaseMessageEntity>()
val attachmentId = "${msgId}_${hashCode()}"
val attachment = AttachmentEntity(
_id = attachmentId,
messageId = msgId,
title = title,
hasActions = true,
buttonAlignment = buttonAlignment
)
list.add(attachment)
actions.forEach { action ->
when (action) { when (action) {
is ButtonAction -> AttachmentActionEntity( is ButtonAction -> AttachmentActionEntity(
attachmentId = attachmentId, attachmentId = attachmentId,
......
...@@ -35,7 +35,7 @@ object ImageHelper { ...@@ -35,7 +35,7 @@ object ImageHelper {
// TODO - implement a proper image viewer with a proper Transition // TODO - implement a proper image viewer with a proper Transition
// TODO - We should definitely write our own ImageViewer // TODO - We should definitely write our own ImageViewer
fun openImage(context: Context, imageUrl: String, imageName: String) { fun openImage(context: Context, imageUrl: String, imageName: String? = "") {
var imageViewer: ImageViewer? = null var imageViewer: ImageViewer? = null
val request = val request =
ImageRequestBuilder.newBuilderWithSource(imageUrl.toUri()) ImageRequestBuilder.newBuilderWithSource(imageUrl.toUri())
......
...@@ -12,18 +12,10 @@ import chat.rocket.common.model.SimpleUser ...@@ -12,18 +12,10 @@ import chat.rocket.common.model.SimpleUser
import chat.rocket.core.model.Message import chat.rocket.core.model.Message
import chat.rocket.core.model.Reactions import chat.rocket.core.model.Reactions
import chat.rocket.core.model.attachment.Attachment import chat.rocket.core.model.attachment.Attachment
import chat.rocket.core.model.attachment.AudioAttachment
import chat.rocket.core.model.attachment.AuthorAttachment
import chat.rocket.core.model.attachment.Color import chat.rocket.core.model.attachment.Color
import chat.rocket.core.model.attachment.ColorAttachment
import chat.rocket.core.model.attachment.DEFAULT_COLOR_STR import chat.rocket.core.model.attachment.DEFAULT_COLOR_STR
import chat.rocket.core.model.attachment.Field import chat.rocket.core.model.attachment.Field
import chat.rocket.core.model.attachment.GenericFileAttachment
import chat.rocket.core.model.attachment.ImageAttachment
import chat.rocket.core.model.attachment.MessageAttachment
import chat.rocket.core.model.attachment.VideoAttachment
import chat.rocket.core.model.attachment.actions.Action import chat.rocket.core.model.attachment.actions.Action
import chat.rocket.core.model.attachment.actions.ActionsAttachment
import chat.rocket.core.model.attachment.actions.ButtonAction import chat.rocket.core.model.attachment.actions.ButtonAction
import chat.rocket.core.model.messageTypeOf import chat.rocket.core.model.messageTypeOf
import chat.rocket.core.model.url.Meta import chat.rocket.core.model.url.Meta
...@@ -141,44 +133,57 @@ class DatabaseMessageMapper(private val dbManager: DatabaseManager) { ...@@ -141,44 +133,57 @@ class DatabaseMessageMapper(private val dbManager: DatabaseManager) {
val list = mutableListOf<Attachment>() val list = mutableListOf<Attachment>()
attachments.forEach { attachment -> attachments.forEach { attachment ->
with(attachment) { with(attachment) {
when { val fields = if (hasFields) {
imageUrl != null -> { withContext(CommonPool) {
ImageAttachment(title, description, text, titleLink, titleLinkDownload, imageUrl, type, imageSize) dbManager.messageDao().getAttachmentFields(attachment._id)
} }.map { Field(it.title, it.value) }
videoUrl != null -> { } else {
VideoAttachment(title, description, text, titleLink, titleLinkDownload, videoUrl, type, videoSize) null
}
audioUrl != null -> {
AudioAttachment(title, description, text, titleLink, titleLinkDownload, audioUrl, type, audioSize)
}
titleLink != null -> {
GenericFileAttachment(title, description, text, titleLink, titleLink, titleLinkDownload)
}
text != null && color != null && fallback != null -> {
ColorAttachment(Color.Custom(color), text, fallback)
}
text != null -> {
// TODO how to model message with attachments
MessageAttachment(authorName, authorIcon, text, thumbUrl,
color?.let { Color.Custom(it) }, messageLink, null, timestamp)
}
authorLink != null -> {
mapAuthorAttachment(this)
}
hasFields -> {
mapColorAttachmentWithFields(this)
}
hasActions -> {
mapActionAttachment(this)
} }
else -> null val actions = if (hasActions) {
}?.let { list.add(it) } withContext(CommonPool) {
dbManager.messageDao().getAttachmentActions(attachment._id)
}.mapNotNull { mapAction(it) }
} else {
null
}
val attachment = Attachment(
title = title,
type = type,
description = description,
authorName = authorName,
text = text,
thumbUrl = thumbUrl,
color = color?.let { Color.Custom(color) },
titleLink = titleLink,
titleLinkDownload = titleLinkDownload,
imageUrl = imageUrl,
imageType = imageType,
imageSize = imageSize,
videoUrl = videoUrl,
videoType = videoType,
videoSize = videoSize,
audioUrl = audioUrl,
audioType = audioType,
audioSize = audioSize,
messageLink = messageLink,
attachments = null, // HOW TO MAP THIS
timestamp = timestamp,
authorIcon = authorIcon,
authorLink = authorLink,
fields = fields,
fallback = fallback,
buttonAlignment = if (actions != null && actions.isNotEmpty()) buttonAlignment ?: "vertical" else null,
actions = actions
)
list.add(attachment)
} }
} }
return list return list
} }
private suspend fun mapColorAttachmentWithFields(entity: AttachmentEntity): ColorAttachment { /*private suspend fun mapColorAttachmentWithFields(entity: AttachmentEntity): ColorAttachment {
val fields = withContext(CommonPool) { val fields = withContext(CommonPool) {
dbManager.messageDao().getAttachmentFields(entity._id) dbManager.messageDao().getAttachmentFields(entity._id)
}.map { Field(it.title, it.value) } }.map { Field(it.title, it.value) }
...@@ -199,7 +204,7 @@ class DatabaseMessageMapper(private val dbManager: DatabaseManager) { ...@@ -199,7 +204,7 @@ class DatabaseMessageMapper(private val dbManager: DatabaseManager) {
// TODO - remove the default "vertical" value from here... // TODO - remove the default "vertical" value from here...
ActionsAttachment(title, actions, buttonAlignment ?: "vertical") ActionsAttachment(title, actions, buttonAlignment ?: "vertical")
} }
} }*/
private fun mapAction(action: AttachmentActionEntity): Action? { private fun mapAction(action: AttachmentActionEntity): Action? {
return when (action.type) { return when (action.type) {
...@@ -210,12 +215,12 @@ class DatabaseMessageMapper(private val dbManager: DatabaseManager) { ...@@ -210,12 +215,12 @@ class DatabaseMessageMapper(private val dbManager: DatabaseManager) {
} }
} }
private suspend fun mapAuthorAttachment(attachment: AttachmentEntity): AuthorAttachment { /*private suspend fun mapAuthorAttachment(attachment: AttachmentEntity): AuthorAttachment {
val fields = withContext(CommonPool) { val fields = withContext(CommonPool) {
dbManager.messageDao().getAttachmentFields(attachment._id) dbManager.messageDao().getAttachmentFields(attachment._id)
}.map { Field(it.title, it.value) } }.map { Field(it.title, it.value) }
return with(attachment) { return with(attachment) {
AuthorAttachment(authorLink!!, authorIcon, authorName, fields) AuthorAttachment(authorLink!!, authorIcon, authorName, fields)
} }
} }*/
} }
\ No newline at end of file
...@@ -4,10 +4,12 @@ import android.net.Uri ...@@ -4,10 +4,12 @@ import android.net.Uri
import androidx.browser.customtabs.CustomTabsIntent import androidx.browser.customtabs.CustomTabsIntent
import androidx.core.content.res.ResourcesCompat import androidx.core.content.res.ResourcesCompat
import android.view.View import android.view.View
import androidx.core.view.isVisible
import chat.rocket.android.R import chat.rocket.android.R
import timber.log.Timber import timber.log.Timber
fun View.openTabbedUrl(url: String) { fun View.openTabbedUrl(url: String?) {
if (url == null) return
with(this) { with(this) {
val uri = url.ensureScheme() val uri = url.ensureScheme()
val tabsbuilder = CustomTabsIntent.Builder() val tabsbuilder = CustomTabsIntent.Builder()
...@@ -31,3 +33,26 @@ private fun String.ensureScheme(): Uri? { ...@@ -31,3 +33,26 @@ private fun String.ensureScheme(): Uri? {
return Uri.parse(url.lowercaseUrl()) return Uri.parse(url.lowercaseUrl())
} }
inline var List<View>.isVisible: Boolean
get() {
var visible = true
forEach { view ->
if (!view.isVisible) {
visible = false
}
}
return visible
}
set(value) {
forEach { view ->
view.isVisible = value
}
}
fun List<View>.setOnClickListener(listener: (v: View) -> Unit) {
forEach { view ->
view.setOnClickListener(listener)
}
}
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:fresco="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/attachment_container" android:id="@+id/attachment_container"
android:layout_width="match_parent" android:layout_width="match_parent"
...@@ -10,20 +11,112 @@ ...@@ -10,20 +11,112 @@
android:clickable="true" android:clickable="true"
android:focusable="true" android:focusable="true"
android:paddingStart="@dimen/screen_edge_left_and_right_padding" android:paddingStart="@dimen/screen_edge_left_and_right_padding"
android:paddingTop="@dimen/message_item_top_and_bottom_padding"
android:paddingEnd="@dimen/screen_edge_left_and_right_padding" android:paddingEnd="@dimen/screen_edge_left_and_right_padding"
android:paddingBottom="@dimen/message_item_top_and_bottom_padding"> android:paddingBottom="@dimen/message_item_top_and_bottom_padding">
<View <androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_begin="56dp" />
<!-- File attachments (image, video, audio) -->
<TextView
android:id="@+id/file_name"
style="@style/Message.TextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
app:layout_constraintStart_toStartOf="@id/guideline"
tools:text="Filename.png"
tools:visibility="visible"/>
<TextView
android:id="@+id/file_description"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
app:layout_constraintStart_toStartOf="@id/guideline"
app:layout_constraintTop_toBottomOf="@id/file_name"
tools:text="Some description"
tools:visibility="visible"/>
<com.facebook.drawee.view.SimpleDraweeView
android:id="@+id/image_attachment"
android:layout_width="0dp"
android:layout_height="150dp"
android:visibility="gone"
app:layout_constraintStart_toStartOf="@id/guideline"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/file_description"
fresco:actualImageScaleType="centerCrop"
fresco:placeholderImage="@drawable/image_dummy"
tools:background="@drawable/image_dummy"
tools:visibility="visible"/>
<FrameLayout
android:id="@+id/audio_video_attachment"
android:layout_width="0dp"
android:layout_height="150dp"
android:background="@color/colorBlack"
android:visibility="gone"
android:clickable="true"
android:focusable="true"
app:layout_constraintStart_toStartOf="@id/guideline"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/image_attachment"
tools:visibility="gone">
<ImageView
android:id="@+id/play_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:src="@drawable/exo_controls_play" />
</FrameLayout>
<TextView
android:id="@+id/file_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@id/guideline"
app:layout_constraintTop_toBottomOf="@id/audio_video_attachment"
tools:text="Some text"
tools:visibility="visible"/>
<TextView
android:id="@+id/text_file_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:textColor="@color/colorAccent"
android:textAppearance="@style/TextAppearance.AppCompat.Body2"
android:drawableStart="@drawable/ic_files_24dp"
android:drawablePadding="6dp"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@id/guideline"
app:layout_constraintTop_toBottomOf="@id/file_text"
android:textDirection="locale"
tools:text="This is a very, very, very long filename, to test how the layout will work on very very very long filenames.pdf"
tools:visibility="visible" />
<!-- END File attachments -->
<ImageView
android:id="@+id/quote_bar" android:id="@+id/quote_bar"
android:layout_width="4dp" android:layout_width="4dp"
android:layout_height="0dp" android:layout_height="0dp"
android:layout_marginStart="56dp" android:src="@drawable/quote_vertical_gray_bar"
android:background="@drawable/quote_vertical_gray_bar" android:scaleType="fitXY"
app:layout_constraintBottom_toTopOf="@+id/text_view_more" android:visibility="gone"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintBottom_toBottomOf="@+id/actions_list"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintStart_toStartOf="@id/guideline"
app:layout_constraintTop_toBottomOf="@id/text_file_name"
tools:visibility="visible"/>
<!-- Message attachment -->
<TextView <TextView
android:id="@+id/text_sender" android:id="@+id/text_sender"
style="@style/Sender.Name.TextView" style="@style/Sender.Name.TextView"
...@@ -31,8 +124,9 @@ ...@@ -31,8 +124,9 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="8dp" android:layout_marginStart="8dp"
android:textColor="@color/colorPrimary" android:textColor="@color/colorPrimary"
android:visibility="gone"
app:layout_constraintStart_toEndOf="@+id/quote_bar" app:layout_constraintStart_toEndOf="@+id/quote_bar"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toBottomOf="@id/text_file_name"
tools:text="Ronald Perkins" /> tools:text="Ronald Perkins" />
<TextView <TextView
...@@ -41,6 +135,7 @@ ...@@ -41,6 +135,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="10dp" android:layout_marginStart="10dp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="@+id/text_sender" app:layout_constraintBottom_toBottomOf="@+id/text_sender"
app:layout_constraintStart_toEndOf="@+id/text_sender" app:layout_constraintStart_toEndOf="@+id/text_sender"
app:layout_constraintTop_toTopOf="@+id/text_sender" app:layout_constraintTop_toTopOf="@+id/text_sender"
...@@ -51,6 +146,7 @@ ...@@ -51,6 +146,7 @@
style="@style/Message.Quote.TextView" style="@style/Message.Quote.TextView"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:visibility="gone"
app:layout_constraintBottom_toTopOf="@id/text_view_more" app:layout_constraintBottom_toTopOf="@id/text_view_more"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@+id/text_sender" app:layout_constraintStart_toStartOf="@+id/text_sender"
...@@ -64,16 +160,105 @@ ...@@ -64,16 +160,105 @@
android:gravity="end" android:gravity="end"
android:textColor="@color/darkGray" android:textColor="@color/darkGray"
android:textSize="14sp" android:textSize="14sp"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="@+id/text_content" app:layout_constraintEnd_toEndOf="@+id/text_content"
app:layout_constraintStart_toStartOf="@+id/quote_bar" app:layout_constraintStart_toEndOf="@id/guideline"
app:layout_constraintTop_toBottomOf="@+id/text_content" app:layout_constraintTop_toBottomOf="@+id/text_content"
tools:text="Visualizar mais" /> tools:text="Visualizar mais" />
<!-- END Message attachment -->
<!-- author -->
<com.facebook.drawee.view.SimpleDraweeView
android:id="@+id/author_icon"
android:layout_width="8dp"
android:layout_height="8dp"
android:layout_marginTop="6dp"
android:layout_marginStart="8dp"
android:visibility="gone"
app:layout_constraintTop_toBottomOf="@id/text_view_more"
app:layout_constraintStart_toStartOf="@id/guideline"
tools:src="@tools:sample/avatars"
tools:visibility="visible"/>
<TextView
android:id="@+id/text_author_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:textColor="@color/colorAccent"
android:textAppearance="@style/TextAppearance.AppCompat.Body2"
android:visibility="gone"
app:layout_constraintTop_toBottomOf="@id/text_view_more"
app:layout_constraintStart_toEndOf="@id/author_icon"
app:layout_constraintEnd_toEndOf="parent"
tools:text="#5571 - User profile from SSO must not have password change option"
tools:visibility="visible"/>
<!-- END author -->
<!-- TEXT -->
<TextView
android:id="@+id/file_name_not_file_type"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:textColor="@color/colorAccent"
android:textAppearance="@style/TextAppearance.AppCompat.Body2"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@id/guideline"
app:layout_constraintTop_toBottomOf="@id/text_author_name"
android:textDirection="locale"
tools:text="This is a very, very, very long filename, to test how the layout will work on very very very long filenames.pdf"
tools:visibility="visible" />
<TextView
android:id="@+id/attachment_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:textAppearance="@style/TextAppearance.AppCompat.Body1"
android:visibility="gone"
app:layout_constraintTop_toBottomOf="@id/file_name_not_file_type"
app:layout_constraintStart_toStartOf="@id/guideline"
app:layout_constraintEnd_toEndOf="parent"
tools:text="#5571 - User profile from SSO must not have password change option"
tools:visibility="visible"/>
<!-- END TEXT -->
<!-- Fields -->
<TextView
android:id="@+id/text_fields"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:visibility="gone"
android:layout_marginStart="8dp"
android:layout_marginTop="4dp"
app:layout_constraintTop_toBottomOf="@id/attachment_text"
app:layout_constraintStart_toEndOf="@id/guideline"
app:layout_constraintEnd_toEndOf="parent"
tools:text="line1\nline2\n\nline3\nline4"
tools:visibility="visible"/>
<!-- END Fields -->
<!-- Actions -->
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/actions_list"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:textAppearance="@style/TextAppearance.AppCompat.Body2"
android:textColor="@color/colorAccent"
android:textDirection="locale"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="@id/title"
app:layout_constraintStart_toEndOf="@id/guideline"
app:layout_constraintTop_toBottomOf="@id/text_fields" />
<include <include
layout="@layout/layout_reactions" layout="@layout/layout_reactions"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="@+id/quote_bar" app:layout_constraintStart_toStartOf="@id/guideline"
app:layout_constraintTop_toBottomOf="@+id/text_view_more" /> app:layout_constraintTop_toBottomOf="@+id/actions_list" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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:id="@+id/attachment_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:animateLayoutChanges="true"
android:background="?android:attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:paddingStart="@dimen/screen_edge_left_and_right_padding"
android:paddingTop="@dimen/message_item_top_and_bottom_padding"
android:paddingEnd="@dimen/screen_edge_left_and_right_padding"
android:paddingBottom="@dimen/message_item_top_and_bottom_padding">
<View
android:id="@+id/quote_bar"
android:layout_width="4dp"
android:layout_height="0dp"
android:layout_marginStart="56dp"
android:background="@drawable/quote_vertical_gray_bar"
app:layout_constraintBottom_toTopOf="@+id/text_view_more"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/text_sender"
style="@style/Sender.Name.TextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:textColor="@color/colorPrimary"
app:layout_constraintStart_toEndOf="@+id/quote_bar"
app:layout_constraintTop_toTopOf="parent"
tools:text="Ronald Perkins" />
<TextView
android:id="@+id/text_message_time"
style="@style/Timestamp.TextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
app:layout_constraintBottom_toBottomOf="@+id/text_sender"
app:layout_constraintStart_toEndOf="@+id/text_sender"
app:layout_constraintTop_toTopOf="@+id/text_sender"
tools:text="11:45 PM" />
<TextView
android:id="@+id/text_content"
style="@style/Message.Quote.TextView"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintBottom_toTopOf="@id/text_view_more"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@+id/text_sender"
app:layout_constraintTop_toBottomOf="@+id/text_sender"
tools:text="This is a multiline chat message from Bertie that will take more than just one line of text. I have sure that everything is amazing!" />
<TextView
android:id="@+id/text_view_more"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:gravity="end"
android:textColor="@color/darkGray"
android:textSize="14sp"
app:layout_constraintEnd_toEndOf="@+id/text_content"
app:layout_constraintStart_toStartOf="@+id/quote_bar"
app:layout_constraintTop_toBottomOf="@+id/text_content"
tools:text="Visualizar mais" />
<include
layout="@layout/layout_reactions"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="@+id/quote_bar"
app:layout_constraintTop_toBottomOf="@+id/text_view_more" />
</androidx.constraintlayout.widget.ConstraintLayout>
...@@ -10,7 +10,7 @@ buildscript { ...@@ -10,7 +10,7 @@ buildscript {
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:3.3.0-alpha12' classpath 'com.android.tools.build:gradle:3.2.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${versions.kotlin}" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${versions.kotlin}"
classpath "org.jetbrains.dokka:dokka-gradle-plugin:${versions.dokka}" classpath "org.jetbrains.dokka:dokka-gradle-plugin:${versions.dokka}"
classpath 'com.google.gms:google-services:4.0.2' classpath 'com.google.gms:google-services:4.0.2'
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment