Commit ee16b416 authored by Filipe de Lima Brito's avatar Filipe de Lima Brito

Merge branch 'develop' of github.com:RocketChat/Rocket.Chat.Android into new/licence-contactus

parents 1ba86e52 4c9ae11a
{
"formatVersion": 1,
"database": {
"version": 12,
"identityHash": "c533a4d17707cba4f91d35a310b8439d",
"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, `muted` TEXT, 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
},
{
"fieldPath": "muted",
"columnName": "muted",
"affinity": "TEXT",
"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, \"c533a4d17707cba4f91d35a310b8439d\")"
]
}
}
\ No newline at end of file
...@@ -61,7 +61,14 @@ interface Analytics { ...@@ -61,7 +61,14 @@ interface Analytics {
fun logServerSwitch(serverUrl: String, serverCount: Int) {} fun logServerSwitch(serverUrl: String, serverCount: Int) {}
/** /**
* Logs the admin opening. * Logs the admin opening event.
*/ */
fun logOpenAdmin() {} fun logOpenAdmin() {}
/**
* Logs the reset password event.
*
* @param resetPasswordSucceeded True if successful reset password, false otherwise.
*/
fun logResetPassword(resetPasswordSucceeded: Boolean) {}
} }
...@@ -70,4 +70,10 @@ class AnalyticsManager @Inject constructor( ...@@ -70,4 +70,10 @@ class AnalyticsManager @Inject constructor(
analytics.forEach { it.logOpenAdmin() } analytics.forEach { it.logOpenAdmin() }
} }
} }
fun logResetPassword(resetPasswordSucceeded: Boolean) {
if (analyticsTrackingInteractor.get()) {
analytics.forEach { it.logResetPassword(resetPasswordSucceeded) }
}
}
} }
...@@ -61,6 +61,10 @@ abstract class BaseViewHolder<T : BaseUiModel<*>>( ...@@ -61,6 +61,10 @@ abstract class BaseViewHolder<T : BaseUiModel<*>>(
reactionListener?.onReactionAdded(messageId, emoji) reactionListener?.onReactionAdded(messageId, emoji)
} }
} }
override fun onReactionLongClicked(shortname: String, isCustom: Boolean, url: String?, usernames: List<String>) {
reactionListener?.onReactionLongClicked(shortname, isCustom,url, usernames)
}
} }
val context = itemView.context val context = itemView.context
......
...@@ -75,7 +75,7 @@ class MessageReactionsAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() ...@@ -75,7 +75,7 @@ class MessageReactionsAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>()
class ReactionViewHolder( class ReactionViewHolder(
view: View, view: View,
private val listener: EmojiReactionListener? private val listener: EmojiReactionListener?
) : RecyclerView.ViewHolder(view), View.OnClickListener { ) : RecyclerView.ViewHolder(view), View.OnClickListener, View.OnLongClickListener {
@Inject @Inject
lateinit var localRepository: LocalRepository lateinit var localRepository: LocalRepository
...@@ -121,6 +121,8 @@ class MessageReactionsAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() ...@@ -121,6 +121,8 @@ class MessageReactionsAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>()
view_flipper_reaction.setOnClickListener(this@ReactionViewHolder) view_flipper_reaction.setOnClickListener(this@ReactionViewHolder)
text_count.setOnClickListener(this@ReactionViewHolder) text_count.setOnClickListener(this@ReactionViewHolder)
view_flipper_reaction.setOnLongClickListener(this@ReactionViewHolder)
text_count.setOnLongClickListener(this@ReactionViewHolder)
} }
} }
...@@ -132,6 +134,11 @@ class MessageReactionsAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() ...@@ -132,6 +134,11 @@ class MessageReactionsAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>()
} }
} }
} }
override fun onLongClick(v: View?): Boolean {
listener?.onReactionLongClicked(reaction.shortname, reaction.isCustom, reaction.url, reaction.usernames)
return true
}
} }
class AddReactionViewHolder( class AddReactionViewHolder(
......
package chat.rocket.android.chatroom.di package chat.rocket.android.chatroom.di
import android.app.Application
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import chat.rocket.android.chatroom.presentation.ChatRoomView import chat.rocket.android.chatroom.presentation.ChatRoomView
import chat.rocket.android.chatroom.ui.ChatRoomFragment import chat.rocket.android.chatroom.ui.ChatRoomFragment
import chat.rocket.android.chatrooms.adapter.RoomUiModelMapper
import chat.rocket.android.core.lifecycle.CancelStrategy import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.dagger.scope.PerFragment import chat.rocket.android.dagger.scope.PerFragment
import chat.rocket.android.db.ChatRoomDao import chat.rocket.android.db.ChatRoomDao
import chat.rocket.android.db.DatabaseManager import chat.rocket.android.db.DatabaseManager
import chat.rocket.android.db.DatabaseManagerFactory import chat.rocket.android.db.DatabaseManagerFactory
import chat.rocket.android.db.UserDao
import chat.rocket.android.server.domain.GetCurrentServerInteractor import chat.rocket.android.server.domain.GetCurrentServerInteractor
import chat.rocket.android.server.domain.GetCurrentUserInteractor
import chat.rocket.android.server.domain.PermissionsInteractor
import chat.rocket.android.server.domain.SettingsRepository
import chat.rocket.android.server.domain.TokenRepository
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
import kotlinx.coroutines.experimental.Job import kotlinx.coroutines.experimental.Job
...@@ -42,4 +49,30 @@ class ChatRoomFragmentModule { ...@@ -42,4 +49,30 @@ class ChatRoomFragmentModule {
@Provides @Provides
@PerFragment @PerFragment
fun provideChatRoomDao(manager: DatabaseManager): ChatRoomDao = manager.chatRoomDao() fun provideChatRoomDao(manager: DatabaseManager): ChatRoomDao = manager.chatRoomDao()
@Provides
@PerFragment
fun provideUserDao(manager: DatabaseManager): UserDao = manager.userDao()
@Provides
@PerFragment
fun provideGetCurrentUserInteractor(
tokenRepository: TokenRepository,
@Named("currentServer") serverUrl: String,
userDao: UserDao
): GetCurrentUserInteractor {
return GetCurrentUserInteractor(tokenRepository, serverUrl, userDao)
}
@Provides
@PerFragment
fun provideRoomMapper(
context: Application,
repository: SettingsRepository,
userInteractor: GetCurrentUserInteractor,
@Named("currentServer") serverUrl: String,
permissionsInteractor: PermissionsInteractor
): RoomUiModelMapper {
return RoomUiModelMapper(context, repository.get(serverUrl), userInteractor, serverUrl, permissionsInteractor)
}
} }
...@@ -15,6 +15,7 @@ import chat.rocket.android.chatroom.uimodel.suggestion.ChatRoomSuggestionUiModel ...@@ -15,6 +15,7 @@ import chat.rocket.android.chatroom.uimodel.suggestion.ChatRoomSuggestionUiModel
import chat.rocket.android.chatroom.uimodel.suggestion.CommandSuggestionUiModel import chat.rocket.android.chatroom.uimodel.suggestion.CommandSuggestionUiModel
import chat.rocket.android.chatroom.uimodel.suggestion.EmojiSuggestionUiModel import chat.rocket.android.chatroom.uimodel.suggestion.EmojiSuggestionUiModel
import chat.rocket.android.chatroom.uimodel.suggestion.PeopleSuggestionUiModel import chat.rocket.android.chatroom.uimodel.suggestion.PeopleSuggestionUiModel
import chat.rocket.android.chatrooms.adapter.RoomUiModelMapper
import chat.rocket.android.core.behaviours.showMessage import chat.rocket.android.core.behaviours.showMessage
import chat.rocket.android.core.lifecycle.CancelStrategy import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.db.DatabaseManager import chat.rocket.android.db.DatabaseManager
...@@ -34,11 +35,11 @@ import chat.rocket.android.server.domain.uploadMimeTypeFilter ...@@ -34,11 +35,11 @@ import chat.rocket.android.server.domain.uploadMimeTypeFilter
import chat.rocket.android.server.domain.useRealName import chat.rocket.android.server.domain.useRealName
import chat.rocket.android.server.infraestructure.ConnectionManagerFactory import chat.rocket.android.server.infraestructure.ConnectionManagerFactory
import chat.rocket.android.server.infraestructure.state import chat.rocket.android.server.infraestructure.state
import chat.rocket.android.util.extension.compressImageAndGetByteArray
import chat.rocket.android.util.extension.getByteArray import chat.rocket.android.util.extension.getByteArray
import chat.rocket.android.util.extension.launchUI import chat.rocket.android.util.extension.launchUI
import chat.rocket.android.util.extensions.avatarUrl import chat.rocket.android.util.extensions.avatarUrl
import chat.rocket.android.util.extensions.getBitmpap import chat.rocket.android.util.extensions.exhaustive
import chat.rocket.android.util.retryDB
import chat.rocket.android.util.retryIO import chat.rocket.android.util.retryIO
import chat.rocket.common.RocketChatException import chat.rocket.common.RocketChatException
import chat.rocket.common.model.RoomType import chat.rocket.common.model.RoomType
...@@ -74,6 +75,7 @@ import chat.rocket.core.model.ChatRoom ...@@ -74,6 +75,7 @@ import chat.rocket.core.model.ChatRoom
import chat.rocket.core.model.ChatRoomRole import chat.rocket.core.model.ChatRoomRole
import chat.rocket.core.model.Command import chat.rocket.core.model.Command
import chat.rocket.core.model.Message import chat.rocket.core.model.Message
import chat.rocket.core.model.Room
import kotlinx.coroutines.experimental.CommonPool import kotlinx.coroutines.experimental.CommonPool
import kotlinx.coroutines.experimental.DefaultDispatcher import kotlinx.coroutines.experimental.DefaultDispatcher
import kotlinx.coroutines.experimental.android.UI import kotlinx.coroutines.experimental.android.UI
...@@ -82,7 +84,6 @@ import kotlinx.coroutines.experimental.launch ...@@ -82,7 +84,6 @@ import kotlinx.coroutines.experimental.launch
import kotlinx.coroutines.experimental.withContext import kotlinx.coroutines.experimental.withContext
import org.threeten.bp.Instant import org.threeten.bp.Instant
import timber.log.Timber import timber.log.Timber
import java.io.InputStream
import java.util.* import java.util.*
import javax.inject.Inject import javax.inject.Inject
...@@ -98,6 +99,7 @@ class ChatRoomPresenter @Inject constructor( ...@@ -98,6 +99,7 @@ class ChatRoomPresenter @Inject constructor(
private val analyticsManager: AnalyticsManager, private val analyticsManager: AnalyticsManager,
private val userHelper: UserHelper, private val userHelper: UserHelper,
private val mapper: UiModelMapper, private val mapper: UiModelMapper,
private val roomMapper: RoomUiModelMapper,
private val jobSchedulerInteractor: JobSchedulerInteractor, private val jobSchedulerInteractor: JobSchedulerInteractor,
private val messageHelper: MessageHelper, private val messageHelper: MessageHelper,
private val dbManager: DatabaseManager, private val dbManager: DatabaseManager,
...@@ -120,6 +122,7 @@ class ChatRoomPresenter @Inject constructor( ...@@ -120,6 +122,7 @@ class ChatRoomPresenter @Inject constructor(
private var typingStatusSubscriptionId: String? = null private var typingStatusSubscriptionId: String? = null
private var lastState = manager.state private var lastState = manager.state
private var typingStatusList = arrayListOf<String>() private var typingStatusList = arrayListOf<String>()
private val roomChangesChannel = Channel<Room>(Channel.CONFLATED)
fun setupChatRoom( fun setupChatRoom(
roomId: String, roomId: String,
...@@ -127,7 +130,7 @@ class ChatRoomPresenter @Inject constructor( ...@@ -127,7 +130,7 @@ class ChatRoomPresenter @Inject constructor(
roomType: String, roomType: String,
chatRoomMessage: String? = null chatRoomMessage: String? = null
) { ) {
launchUI(strategy) { launch(CommonPool + strategy.jobs) {
try { try {
chatRoles = if (roomTypeOf(roomType) !is RoomType.DirectMessage) { chatRoles = if (roomTypeOf(roomType) !is RoomType.DirectMessage) {
client.chatRoomRoles(roomType = roomTypeOf(roomType), roomName = roomName) client.chatRoomRoles(roomType = roomTypeOf(roomType), roomName = roomName)
...@@ -137,13 +140,21 @@ class ChatRoomPresenter @Inject constructor( ...@@ -137,13 +140,21 @@ class ChatRoomPresenter @Inject constructor(
chatRoles = emptyList() chatRoles = emptyList()
} finally { } finally {
// User has at least an 'owner' or 'moderator' role. // User has at least an 'owner' or 'moderator' role.
val userCanMod = isOwnerOrMod() val canModerate = isOwnerOrMod()
// Can post anyway if has the 'post-readonly' permission on server. // Can post anyway if has the 'post-readonly' permission on server.
val userCanPost = userCanMod || permissions.canPostToReadOnlyChannels() val room = dbManager.getRoom(roomId)
chatIsBroadcast = dbManager.getRoom(roomId)?.chatRoom?.run { room?.let {
broadcast chatIsBroadcast = it.chatRoom.broadcast ?: false
} ?: false val roomUiModel = roomMapper.map(it, true)
view.onRoomUpdated(userCanPost, chatIsBroadcast, userCanMod) launchUI(strategy) {
view.onRoomUpdated(roomUiModel = roomUiModel.copy(
broadcast = chatIsBroadcast,
canModerate = canModerate,
writable = roomUiModel.writable || canModerate
))
}
}
loadMessages(roomId, roomType, clearDataSet = true) loadMessages(roomId, roomType, clearDataSet = true)
chatRoomMessage?.let { messageHelper.messageIdFromPermalink(it) } chatRoomMessage?.let { messageHelper.messageIdFromPermalink(it) }
?.let { messageId -> ?.let { messageId ->
...@@ -155,10 +166,26 @@ class ChatRoomPresenter @Inject constructor( ...@@ -155,10 +166,26 @@ class ChatRoomPresenter @Inject constructor(
true true
) )
} }
subscribeRoomChanges()
} }
} }
} }
private suspend fun subscribeRoomChanges() {
chatRoomId?.let {
manager.addRoomChannel(it, roomChangesChannel)
for (room in roomChangesChannel) {
dbManager.getRoom(room.id)?.let {
view.onRoomUpdated(roomMapper.map(chatRoom = it, showLastMessage = true))
}
}
}
}
private fun unsubscribeRoomChanges() {
chatRoomId?.let { manager.removeRoomChannel(it) }
}
private fun isOwnerOrMod(): Boolean { private fun isOwnerOrMod(): Boolean {
return chatRoles.firstOrNull { it.user.username == currentLoggedUsername }?.roles?.any { return chatRoles.firstOrNull { it.user.username == currentLoggedUsername }?.roles?.any {
it == "owner" || it == "moderator" it == "owner" || it == "moderator"
...@@ -901,77 +928,81 @@ class ChatRoomPresenter @Inject constructor( ...@@ -901,77 +928,81 @@ class ChatRoomPresenter @Inject constructor(
// TODO: move this to new interactor or FetchChatRoomsInteractor? // TODO: move this to new interactor or FetchChatRoomsInteractor?
private suspend fun getChatRoomAsync(roomId: String): ChatRoom? = withContext(CommonPool) { private suspend fun getChatRoomAsync(roomId: String): ChatRoom? = withContext(CommonPool) {
return@withContext dbManager.chatRoomDao().get(roomId)?.let { retryDB("getRoom($roomId)") {
with(it.chatRoom) { dbManager.chatRoomDao().get(roomId)?.let {
ChatRoom( with(it.chatRoom) {
id = id, ChatRoom(
subscriptionId = subscriptionId, id = id,
type = roomTypeOf(type), subscriptionId = subscriptionId,
unread = unread, type = roomTypeOf(type),
broadcast = broadcast ?: false, unread = unread,
alert = alert, broadcast = broadcast ?: false,
fullName = fullname, alert = alert,
name = name, fullName = fullname,
favorite = favorite ?: false, name = name,
default = isDefault ?: false, favorite = favorite ?: false,
readonly = readonly, default = isDefault ?: false,
open = open, readonly = readonly,
lastMessage = null, open = open,
archived = false, lastMessage = null,
status = null, archived = false,
user = null, status = null,
userMentions = userMentions, user = null,
client = client, userMentions = userMentions,
announcement = null, client = client,
description = null, announcement = null,
groupMentions = groupMentions, description = null,
roles = null, groupMentions = groupMentions,
topic = null, roles = null,
lastSeen = this.lastSeen, topic = null,
timestamp = timestamp, lastSeen = this.lastSeen,
updatedAt = updatedAt timestamp = timestamp,
) updatedAt = updatedAt
)
}
} }
} }
} }
// TODO: move this to new interactor or FetchChatRoomsInteractor? // TODO: move this to new interactor or FetchChatRoomsInteractor?
private suspend fun getChatRoomsAsync(name: String? = null): List<ChatRoom> = withContext(CommonPool) { private suspend fun getChatRoomsAsync(name: String? = null): List<ChatRoom> = withContext(CommonPool) {
return@withContext dbManager.chatRoomDao().getAllSync().filter { retryDB("getAllSync()") {
if (name == null) { dbManager.chatRoomDao().getAllSync().filter {
return@filter true if (name == null) {
} return@filter true
it.chatRoom.name == name || it.chatRoom.fullname == name }
}.map { it.chatRoom.name == name || it.chatRoom.fullname == name
with(it.chatRoom) { }.map {
ChatRoom( with(it.chatRoom) {
id = id, ChatRoom(
subscriptionId = subscriptionId, id = id,
type = roomTypeOf(type), subscriptionId = subscriptionId,
unread = unread, type = roomTypeOf(type),
broadcast = broadcast ?: false, unread = unread,
alert = alert, broadcast = broadcast ?: false,
fullName = fullname, alert = alert,
name = name ?: "", fullName = fullname,
favorite = favorite ?: false, name = name ?: "",
default = isDefault ?: false, favorite = favorite ?: false,
readonly = readonly, default = isDefault ?: false,
open = open, readonly = readonly,
lastMessage = null, open = open,
archived = false, lastMessage = null,
status = null, archived = false,
user = null, status = null,
userMentions = userMentions, user = null,
client = client, userMentions = userMentions,
announcement = null, client = client,
description = null, announcement = null,
groupMentions = groupMentions, description = null,
roles = null, groupMentions = groupMentions,
topic = null, roles = null,
lastSeen = this.lastSeen, topic = null,
timestamp = timestamp, lastSeen = this.lastSeen,
updatedAt = updatedAt timestamp = timestamp,
) updatedAt = updatedAt
)
}
} }
} }
} }
...@@ -981,7 +1012,12 @@ class ChatRoomPresenter @Inject constructor( ...@@ -981,7 +1012,12 @@ class ChatRoomPresenter @Inject constructor(
try { try {
retryIO("joinChat($chatRoomId)") { client.joinChat(chatRoomId) } retryIO("joinChat($chatRoomId)") { client.joinChat(chatRoomId) }
val canPost = permissions.canPostToReadOnlyChannels() val canPost = permissions.canPostToReadOnlyChannels()
view.onJoined(canPost) dbManager.getRoom(chatRoomId)?.let {
val roomUiModel = roomMapper.map(it, true).copy(
writable = canPost)
view.onJoined(roomUiModel = roomUiModel)
view.onRoomUpdated(roomUiModel = roomUiModel)
}
} catch (ex: RocketChatException) { } catch (ex: RocketChatException) {
Timber.e(ex) Timber.e(ex)
} }
...@@ -1151,6 +1187,7 @@ class ChatRoomPresenter @Inject constructor( ...@@ -1151,6 +1187,7 @@ class ChatRoomPresenter @Inject constructor(
} }
fun disconnect() { fun disconnect() {
unsubscribeRoomChanges()
unsubscribeTypingStatus() unsubscribeTypingStatus()
if (chatRoomId != null) { if (chatRoomId != null) {
unsubscribeMessages(chatRoomId.toString()) unsubscribeMessages(chatRoomId.toString())
......
...@@ -5,6 +5,7 @@ import chat.rocket.android.chatroom.uimodel.suggestion.ChatRoomSuggestionUiModel ...@@ -5,6 +5,7 @@ import chat.rocket.android.chatroom.uimodel.suggestion.ChatRoomSuggestionUiModel
import chat.rocket.android.chatroom.uimodel.suggestion.CommandSuggestionUiModel import chat.rocket.android.chatroom.uimodel.suggestion.CommandSuggestionUiModel
import chat.rocket.android.chatroom.uimodel.suggestion.EmojiSuggestionUiModel import chat.rocket.android.chatroom.uimodel.suggestion.EmojiSuggestionUiModel
import chat.rocket.android.chatroom.uimodel.suggestion.PeopleSuggestionUiModel import chat.rocket.android.chatroom.uimodel.suggestion.PeopleSuggestionUiModel
import chat.rocket.android.chatrooms.adapter.model.RoomUiModel
import chat.rocket.android.core.behaviours.LoadingView import chat.rocket.android.core.behaviours.LoadingView
import chat.rocket.android.core.behaviours.MessageView import chat.rocket.android.core.behaviours.MessageView
import chat.rocket.core.internal.realtime.socket.model.State import chat.rocket.core.internal.realtime.socket.model.State
...@@ -131,12 +132,7 @@ interface ChatRoomView : LoadingView, MessageView { ...@@ -131,12 +132,7 @@ interface ChatRoomView : LoadingView, MessageView {
fun populateEmojiSuggestions(emojis: List<EmojiSuggestionUiModel>) fun populateEmojiSuggestions(emojis: List<EmojiSuggestionUiModel>)
/** fun onJoined(roomUiModel: RoomUiModel)
* This user has joined the chat callback.
*
* @param userCanPost Whether the user can post a message or not.
*/
fun onJoined(userCanPost: Boolean)
fun showReactionsPopup(messageId: String) fun showReactionsPopup(messageId: String)
...@@ -147,9 +143,6 @@ interface ChatRoomView : LoadingView, MessageView { ...@@ -147,9 +143,6 @@ interface ChatRoomView : LoadingView, MessageView {
*/ */
fun populateCommandSuggestions(commands: List<CommandSuggestionUiModel>) fun populateCommandSuggestions(commands: List<CommandSuggestionUiModel>)
/** fun onRoomUpdated(roomUiModel: RoomUiModel)
* Communicate whether it's a broadcast channel and if current user can post to it.
*/
fun onRoomUpdated(userCanPost: Boolean, channelIsBroadcast: Boolean, userCanMod: Boolean)
} }
...@@ -51,6 +51,7 @@ import chat.rocket.android.chatroom.uimodel.suggestion.ChatRoomSuggestionUiModel ...@@ -51,6 +51,7 @@ import chat.rocket.android.chatroom.uimodel.suggestion.ChatRoomSuggestionUiModel
import chat.rocket.android.chatroom.uimodel.suggestion.CommandSuggestionUiModel import chat.rocket.android.chatroom.uimodel.suggestion.CommandSuggestionUiModel
import chat.rocket.android.chatroom.uimodel.suggestion.EmojiSuggestionUiModel import chat.rocket.android.chatroom.uimodel.suggestion.EmojiSuggestionUiModel
import chat.rocket.android.chatroom.uimodel.suggestion.PeopleSuggestionUiModel import chat.rocket.android.chatroom.uimodel.suggestion.PeopleSuggestionUiModel
import chat.rocket.android.chatrooms.adapter.model.RoomUiModel
import chat.rocket.android.draw.main.ui.DRAWING_BYTE_ARRAY_EXTRA_DATA import chat.rocket.android.draw.main.ui.DRAWING_BYTE_ARRAY_EXTRA_DATA
import chat.rocket.android.draw.main.ui.DrawingActivity import chat.rocket.android.draw.main.ui.DrawingActivity
import chat.rocket.android.emoji.ComposerEditText import chat.rocket.android.emoji.ComposerEditText
...@@ -60,6 +61,7 @@ import chat.rocket.android.emoji.EmojiKeyboardPopup ...@@ -60,6 +61,7 @@ import chat.rocket.android.emoji.EmojiKeyboardPopup
import chat.rocket.android.emoji.EmojiParser import chat.rocket.android.emoji.EmojiParser
import chat.rocket.android.emoji.EmojiPickerPopup import chat.rocket.android.emoji.EmojiPickerPopup
import chat.rocket.android.emoji.EmojiReactionListener import chat.rocket.android.emoji.EmojiReactionListener
import chat.rocket.android.emoji.internal.GlideApp
import chat.rocket.android.emoji.internal.isCustom import chat.rocket.android.emoji.internal.isCustom
import chat.rocket.android.helper.EndlessRecyclerViewScrollListener import chat.rocket.android.helper.EndlessRecyclerViewScrollListener
import chat.rocket.android.helper.ImageHelper import chat.rocket.android.helper.ImageHelper
...@@ -84,6 +86,8 @@ import dagger.android.support.AndroidSupportInjection ...@@ -84,6 +86,8 @@ import dagger.android.support.AndroidSupportInjection
import io.reactivex.Observable import io.reactivex.Observable
import io.reactivex.disposables.CompositeDisposable import io.reactivex.disposables.CompositeDisposable
import io.reactivex.disposables.Disposable import io.reactivex.disposables.Disposable
import kotlinx.android.synthetic.main.emoji_image_row_item.*
import kotlinx.android.synthetic.main.emoji_row_item.*
import kotlinx.android.synthetic.main.fragment_chat_room.* import kotlinx.android.synthetic.main.fragment_chat_room.*
import kotlinx.android.synthetic.main.message_attachment_options.* import kotlinx.android.synthetic.main.message_attachment_options.*
import kotlinx.android.synthetic.main.message_composer.* import kotlinx.android.synthetic.main.message_composer.*
...@@ -91,6 +95,7 @@ import kotlinx.android.synthetic.main.message_list.* ...@@ -91,6 +95,7 @@ import kotlinx.android.synthetic.main.message_list.*
import timber.log.Timber import timber.log.Timber
import java.io.File import java.io.File
import java.io.IOException import java.io.IOException
import kotlinx.android.synthetic.main.reaction_praises_list_item.*
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.atomic.AtomicInteger
import javax.inject.Inject import javax.inject.Inject
...@@ -397,17 +402,14 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR ...@@ -397,17 +402,14 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
empty_chat_view.isVisible = adapter.itemCount == 0 empty_chat_view.isVisible = adapter.itemCount == 0
} }
override fun onRoomUpdated( override fun onRoomUpdated(roomUiModel: RoomUiModel) {
userCanPost: Boolean,
channelIsBroadcast: Boolean,
userCanMod: Boolean
) {
// TODO: We should rely solely on the user being able to post, but we cannot guarantee // TODO: We should rely solely on the user being able to post, but we cannot guarantee
// that the "(channels|groups).roles" endpoint is supported by the server in use. // that the "(channels|groups).roles" endpoint is supported by the server in use.
ui { ui {
setupMessageComposer(userCanPost) setupToolbar(roomUiModel.name.toString())
isBroadcastChannel = channelIsBroadcast setupMessageComposer(roomUiModel)
if (isBroadcastChannel && !userCanMod) { isBroadcastChannel = roomUiModel.broadcast
if (isBroadcastChannel && !roomUiModel.canModerate) {
disableMenu = true disableMenu = true
activity?.invalidateOptionsMenu() activity?.invalidateOptionsMenu()
} }
...@@ -544,7 +546,6 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR ...@@ -544,7 +546,6 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
ui { ui {
button_send.isEnabled = true button_send.isEnabled = true
text_message.isEnabled = true text_message.isEnabled = true
clearMessageComposition(true)
} }
} }
...@@ -611,7 +612,7 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR ...@@ -611,7 +612,7 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
} }
} }
override fun showGenericErrorMessage(){ override fun showGenericErrorMessage() {
ui { ui {
showMessage(getString(R.string.msg_generic_error)) showMessage(getString(R.string.msg_generic_error))
} }
...@@ -688,6 +689,44 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR ...@@ -688,6 +689,44 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
presenter.react(messageId, emoji.shortname) presenter.react(messageId, emoji.shortname)
} }
override fun onReactionLongClicked(shortname: String, isCustom: Boolean, url: String?, usernames: List<String>) {
val layout = LayoutInflater.from(requireContext()).inflate(R.layout.reaction_praises_list_item, null)
val dialog = AlertDialog.Builder(requireContext())
.setView(layout)
.setCancelable(true)
with(layout) {
view_flipper.displayedChild = if (isCustom) 1 else 0
if (isCustom && url != null) {
val glideRequest = if (url.endsWith("gif", true)) {
GlideApp.with(requireContext()).asGif()
} else {
GlideApp.with(requireContext()).asBitmap()
}
glideRequest.load(url).into(emoji_image_view)
} else {
emoji_view.text = EmojiParser.parse(requireContext(), shortname)
}
var listing = ""
if (usernames.size == 1) {
listing = usernames.first()
} else {
usernames.forEachIndexed { index, username ->
listing += if (index == usernames.size - 1) "|$username" else "$username, "
}
listing = listing.replace(", |", " ${requireContext().getString(R.string.msg_and)} ")
}
text_view_usernames.text = requireContext().resources.getQuantityString(
R.plurals.msg_reacted_with_, usernames.size, listing, shortname)
dialog.show()
}
}
override fun showReactionsPopup(messageId: String) { override fun showReactionsPopup(messageId: String) {
ui { ui {
val emojiPickerPopup = EmojiPickerPopup(it) val emojiPickerPopup = EmojiPickerPopup(it)
...@@ -748,12 +787,11 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR ...@@ -748,12 +787,11 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
} }
} }
override fun onJoined(userCanPost: Boolean) { override fun onJoined(roomUiModel: RoomUiModel) {
ui { ui {
input_container.isVisible = true input_container.isVisible = true
button_join_chat.isVisible = false button_join_chat.isVisible = false
isSubscribed = true isSubscribed = true
setupMessageComposer(userCanPost)
} }
} }
...@@ -786,15 +824,25 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR ...@@ -786,15 +824,25 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
} }
} }
private fun setupMessageComposer(canPost: Boolean) { private fun setupMessageComposer(roomUiModel: RoomUiModel) {
if (isReadOnly && !canPost) { if (isReadOnly || !roomUiModel.writable) {
text_room_is_read_only.isVisible = true text_room_is_read_only.isVisible = true
input_container.isVisible = false input_container.isVisible = false
text_room_is_read_only.setText(
if (isReadOnly) {
R.string.msg_this_room_is_read_only
} else {
// Not a read-only channel but user has been muted.
R.string.msg_muted_on_this_channel
}
)
} else if (!isSubscribed && roomTypeOf(chatRoomType) !is RoomType.DirectMessage) { } else if (!isSubscribed && roomTypeOf(chatRoomType) !is RoomType.DirectMessage) {
input_container.isVisible = false input_container.isVisible = false
button_join_chat.isVisible = true button_join_chat.isVisible = true
button_join_chat.setOnClickListener { presenter.joinChat(chatRoomId) } button_join_chat.setOnClickListener { presenter.joinChat(chatRoomId) }
} else { } else {
input_container.isVisible = true
text_room_is_read_only.isVisible = false
button_send.isVisible = false button_send.isVisible = false
button_show_attachment_options.alpha = 1f button_show_attachment_options.alpha = 1f
button_show_attachment_options.isVisible = true button_show_attachment_options.isVisible = true
......
...@@ -6,5 +6,6 @@ data class ReactionUiModel( ...@@ -6,5 +6,6 @@ data class ReactionUiModel(
val unicode: CharSequence, val unicode: CharSequence,
val count: Int, val count: Int,
val usernames: List<String> = emptyList(), val usernames: List<String> = emptyList(),
var url: String? = null var url: String? = null,
) val isCustom: Boolean = false
\ No newline at end of file )
...@@ -19,6 +19,7 @@ import chat.rocket.android.dagger.scope.PerFragment ...@@ -19,6 +19,7 @@ import chat.rocket.android.dagger.scope.PerFragment
import chat.rocket.android.db.DatabaseManager import chat.rocket.android.db.DatabaseManager
import chat.rocket.android.emoji.EmojiParser import chat.rocket.android.emoji.EmojiParser
import chat.rocket.android.emoji.EmojiRepository import chat.rocket.android.emoji.EmojiRepository
import chat.rocket.android.emoji.internal.isCustom
import chat.rocket.android.helper.MessageHelper import chat.rocket.android.helper.MessageHelper
import chat.rocket.android.helper.MessageParser import chat.rocket.android.helper.MessageParser
import chat.rocket.android.helper.UserHelper import chat.rocket.android.helper.UserHelper
...@@ -31,6 +32,7 @@ import chat.rocket.android.server.domain.messageReadReceiptEnabled ...@@ -31,6 +32,7 @@ import chat.rocket.android.server.domain.messageReadReceiptEnabled
import chat.rocket.android.server.domain.messageReadReceiptStoreUsers import chat.rocket.android.server.domain.messageReadReceiptStoreUsers
import chat.rocket.android.server.domain.useRealName import chat.rocket.android.server.domain.useRealName
import chat.rocket.android.server.infraestructure.ConnectionManagerFactory import chat.rocket.android.server.infraestructure.ConnectionManagerFactory
import chat.rocket.android.util.extension.orFalse
import chat.rocket.android.util.extensions.avatarUrl import chat.rocket.android.util.extensions.avatarUrl
import chat.rocket.android.util.extensions.ifNotNullNorEmpty import chat.rocket.android.util.extensions.ifNotNullNorEmpty
import chat.rocket.android.util.extensions.isNotNullNorEmpty import chat.rocket.android.util.extensions.isNotNullNorEmpty
...@@ -469,7 +471,7 @@ class UiModelMapper @Inject constructor( ...@@ -469,7 +471,7 @@ class UiModelMapper @Inject constructor(
val list = mutableListOf<ReactionUiModel>() val list = mutableListOf<ReactionUiModel>()
val customEmojis = EmojiRepository.getCustomEmojis() val customEmojis = EmojiRepository.getCustomEmojis()
it.getShortNames().forEach { shortname -> it.getShortNames().forEach { shortname ->
val usernames = it.getUsernames(shortname) ?: emptyList() val usernames = it.getUsernames(shortname).orEmpty()
val count = usernames.size val count = usernames.size
val custom = customEmojis.firstOrNull { emoji -> emoji.shortname == shortname } val custom = customEmojis.firstOrNull { emoji -> emoji.shortname == shortname }
list.add( list.add(
...@@ -478,7 +480,8 @@ class UiModelMapper @Inject constructor( ...@@ -478,7 +480,8 @@ class UiModelMapper @Inject constructor(
unicode = EmojiParser.parse(context, shortname), unicode = EmojiParser.parse(context, shortname),
count = count, count = count,
usernames = usernames, usernames = usernames,
url = custom?.url) url = custom?.url,
isCustom = custom != null)
) )
} }
list list
......
...@@ -8,9 +8,8 @@ import androidx.core.text.color ...@@ -8,9 +8,8 @@ import androidx.core.text.color
import chat.rocket.android.R import chat.rocket.android.R
import chat.rocket.android.chatrooms.adapter.model.RoomUiModel import chat.rocket.android.chatrooms.adapter.model.RoomUiModel
import chat.rocket.android.db.model.ChatRoom import chat.rocket.android.db.model.ChatRoom
import chat.rocket.android.infrastructure.LocalRepository
import chat.rocket.android.infrastructure.checkIfMyself
import chat.rocket.android.server.domain.GetCurrentUserInteractor import chat.rocket.android.server.domain.GetCurrentUserInteractor
import chat.rocket.android.server.domain.PermissionsInteractor
import chat.rocket.android.server.domain.PublicSettings import chat.rocket.android.server.domain.PublicSettings
import chat.rocket.android.server.domain.showLastMessage import chat.rocket.android.server.domain.showLastMessage
import chat.rocket.android.server.domain.useRealName import chat.rocket.android.server.domain.useRealName
...@@ -30,7 +29,8 @@ class RoomUiModelMapper( ...@@ -30,7 +29,8 @@ class RoomUiModelMapper(
private val context: Application, private val context: Application,
private val settings: PublicSettings, private val settings: PublicSettings,
private val userInteractor: GetCurrentUserInteractor, private val userInteractor: GetCurrentUserInteractor,
private val serverUrl: String private val serverUrl: String,
private val permissions: PermissionsInteractor
) { ) {
private val nameUnreadColor = ContextCompat.getColor(context, R.color.colorPrimaryText) private val nameUnreadColor = ContextCompat.getColor(context, R.color.colorPrimaryText)
private val nameColor = ContextCompat.getColor(context, R.color.colorSecondaryText) private val nameColor = ContextCompat.getColor(context, R.color.colorSecondaryText)
...@@ -97,7 +97,9 @@ class RoomUiModelMapper( ...@@ -97,7 +97,9 @@ class RoomUiModelMapper(
avatar = serverUrl.avatarUrl(name!!, isGroupOrChannel = true), avatar = serverUrl.avatarUrl(name!!, isGroupOrChannel = true),
lastMessage = if(showLastMessage) { mapLastMessage(lastMessage?.sender?.id, lastMessage?.sender?.username, lastMessage = if(showLastMessage) { mapLastMessage(lastMessage?.sender?.id, lastMessage?.sender?.username,
lastMessage?.sender?.name, lastMessage?.message, lastMessage?.sender?.name, lastMessage?.message,
isDirectMessage = type is RoomType.DirectMessage)} else { null } isDirectMessage = type is RoomType.DirectMessage)} else { null },
muted = muted.orEmpty(),
writable = isChannelWritable(muted)
) )
} }
} }
...@@ -133,11 +135,18 @@ class RoomUiModelMapper( ...@@ -133,11 +135,18 @@ class RoomUiModelMapper(
alert = isUnread, alert = isUnread,
lastMessage = lastMessageMarkdown, lastMessage = lastMessageMarkdown,
status = status, status = status,
username = if (type is RoomType.DirectMessage) name else null username = if (type is RoomType.DirectMessage) name else null,
muted = muted.orEmpty(),
writable = isChannelWritable(muted)
) )
} }
} }
private fun isChannelWritable(muted: List<String>?): Boolean {
val canWriteToReadOnlyChannels = permissions.canPostToReadOnlyChannels()
return canWriteToReadOnlyChannels || !muted.orEmpty().contains(currentUser?.username)
}
private fun roomType(type: String): String { private fun roomType(type: String): String {
val resources = context.resources val resources = context.resources
return when (type) { return when (type) {
...@@ -205,4 +214,4 @@ class RoomUiModelMapper( ...@@ -205,4 +214,4 @@ class RoomUiModelMapper(
} }
} }
} }
} }
\ No newline at end of file
...@@ -14,5 +14,9 @@ data class RoomUiModel( ...@@ -14,5 +14,9 @@ data class RoomUiModel(
val alert: Boolean = false, val alert: Boolean = false,
val lastMessage: CharSequence? = null, val lastMessage: CharSequence? = null,
val status: UserStatus? = null, val status: UserStatus? = null,
val username: String? = null val username: String? = null,
) val broadcast: Boolean = false,
\ No newline at end of file val canModerate: Boolean = false,
val writable: Boolean = true,
val muted: List<String> = emptyList()
)
...@@ -12,6 +12,7 @@ import chat.rocket.android.db.DatabaseManager ...@@ -12,6 +12,7 @@ import chat.rocket.android.db.DatabaseManager
import chat.rocket.android.db.UserDao import chat.rocket.android.db.UserDao
import chat.rocket.android.infrastructure.LocalRepository import chat.rocket.android.infrastructure.LocalRepository
import chat.rocket.android.server.domain.GetCurrentUserInteractor import chat.rocket.android.server.domain.GetCurrentUserInteractor
import chat.rocket.android.server.domain.PermissionsInteractor
import chat.rocket.android.server.domain.PublicSettings import chat.rocket.android.server.domain.PublicSettings
import chat.rocket.android.server.domain.SettingsRepository import chat.rocket.android.server.domain.SettingsRepository
import chat.rocket.android.server.domain.TokenRepository import chat.rocket.android.server.domain.TokenRepository
...@@ -88,9 +89,10 @@ class ChatRoomsFragmentModule { ...@@ -88,9 +89,10 @@ class ChatRoomsFragmentModule {
context: Application, context: Application,
repository: SettingsRepository, repository: SettingsRepository,
userInteractor: GetCurrentUserInteractor, userInteractor: GetCurrentUserInteractor,
@Named("currentServer") serverUrl: String @Named("currentServer") serverUrl: String,
permissionsInteractor: PermissionsInteractor
): RoomUiModelMapper { ): RoomUiModelMapper {
return RoomUiModelMapper(context, repository.get(serverUrl), userInteractor, serverUrl) return RoomUiModelMapper(context, repository.get(serverUrl), userInteractor, serverUrl, permissionsInteractor)
} }
@Provides @Provides
......
...@@ -3,9 +3,12 @@ package chat.rocket.android.chatrooms.infrastructure ...@@ -3,9 +3,12 @@ package chat.rocket.android.chatrooms.infrastructure
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import chat.rocket.android.db.ChatRoomDao import chat.rocket.android.db.ChatRoomDao
import chat.rocket.android.db.model.ChatRoom import chat.rocket.android.db.model.ChatRoom
import chat.rocket.android.util.retryDB
import javax.inject.Inject import javax.inject.Inject
class ChatRoomsRepository @Inject constructor(private val dao: ChatRoomDao){ class ChatRoomsRepository @Inject constructor(private val dao: ChatRoomDao) {
// TODO - check how to use retryDB here - suspend
fun getChatRooms(order: Order): LiveData<List<ChatRoom>> { fun getChatRooms(order: Order): LiveData<List<ChatRoom>> {
return when(order) { return when(order) {
Order.ACTIVITY -> dao.getAll() Order.ACTIVITY -> dao.getAll()
...@@ -15,9 +18,10 @@ class ChatRoomsRepository @Inject constructor(private val dao: ChatRoomDao){ ...@@ -15,9 +18,10 @@ class ChatRoomsRepository @Inject constructor(private val dao: ChatRoomDao){
} }
} }
fun search(query: String) = dao.searchSync(query) suspend fun search(query: String) =
retryDB("roomSearch($query)") { dao.searchSync(query) }
fun count() = dao.count() suspend fun count() = retryDB("roomsCount") { dao.count() }
enum class Order { enum class Order {
ACTIVITY, ACTIVITY,
......
...@@ -13,6 +13,7 @@ import chat.rocket.android.server.domain.useRealName ...@@ -13,6 +13,7 @@ import chat.rocket.android.server.domain.useRealName
import chat.rocket.android.server.domain.useSpecialCharsOnRoom import chat.rocket.android.server.domain.useSpecialCharsOnRoom
import chat.rocket.android.server.infraestructure.ConnectionManager import chat.rocket.android.server.infraestructure.ConnectionManager
import chat.rocket.android.util.extension.launchUI import chat.rocket.android.util.extension.launchUI
import chat.rocket.android.util.retryDB
import chat.rocket.android.util.retryIO import chat.rocket.android.util.retryIO
import chat.rocket.common.RocketChatException import chat.rocket.common.RocketChatException
import chat.rocket.common.model.RoomType import chat.rocket.common.model.RoomType
...@@ -41,11 +42,31 @@ class ChatRoomsPresenter @Inject constructor( ...@@ -41,11 +42,31 @@ class ChatRoomsPresenter @Inject constructor(
private val client = manager.client private val client = manager.client
private val settings = settingsRepository.get(currentServer) private val settings = settingsRepository.get(currentServer)
fun loadChatRoom(roomId: String) {
launchUI(strategy) {
view.showLoadingRoom("")
try {
val room = dbManager.getRoom(roomId)
if (room != null) {
loadChatRoom(room.chatRoom, true)
} else {
Timber.d("Error loading channel")
view.showGenericErrorMessage()
}
} catch (ex: Exception) {
Timber.d(ex, "Error loading channel")
view.showGenericErrorMessage()
} finally {
view.hideLoadingRoom()
}
}
}
fun loadChatRoom(chatRoom: RoomUiModel) { fun loadChatRoom(chatRoom: RoomUiModel) {
launchUI(strategy) { launchUI(strategy) {
view.showLoadingRoom(chatRoom.name) view.showLoadingRoom(chatRoom.name)
try { try {
val room = dbManager.getRoom(chatRoom.id) val room = retryDB("getRoom(${chatRoom.id}") { dbManager.getRoom(chatRoom.id) }
if (room != null) { if (room != null) {
loadChatRoom(room.chatRoom, true) loadChatRoom(room.chatRoom, true)
} else { } else {
...@@ -56,7 +77,8 @@ class ChatRoomsPresenter @Inject constructor( ...@@ -56,7 +77,8 @@ class ChatRoomsPresenter @Inject constructor(
type = type.toString(), type = type.toString(),
name = username ?: name.toString(), name = username ?: name.toString(),
fullname = name.toString(), fullname = name.toString(),
open = open open = open,
muted = muted
) )
loadChatRoom(entity, false) loadChatRoom(entity, false)
} }
......
...@@ -84,8 +84,7 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView { ...@@ -84,8 +84,7 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView {
if (bundle != null) { if (bundle != null) {
chatRoomId = bundle.getString(BUNDLE_CHAT_ROOM_ID) chatRoomId = bundle.getString(BUNDLE_CHAT_ROOM_ID)
chatRoomId?.let { chatRoomId?.let {
// TODO - bring back support to load a room from id. presenter.loadChatRoom(it)
//presenter.goToChatRoomWithId(it)
chatRoomId = null chatRoomId = null
} }
} }
......
...@@ -27,7 +27,7 @@ import kotlinx.coroutines.experimental.launch ...@@ -27,7 +27,7 @@ import kotlinx.coroutines.experimental.launch
import kotlinx.coroutines.experimental.newSingleThreadContext import kotlinx.coroutines.experimental.newSingleThreadContext
import kotlinx.coroutines.experimental.withContext import kotlinx.coroutines.experimental.withContext
import timber.log.Timber import timber.log.Timber
import java.security.InvalidParameterException import java.lang.IllegalArgumentException
import kotlin.coroutines.experimental.coroutineContext import kotlin.coroutines.experimental.coroutineContext
...@@ -171,6 +171,6 @@ fun Query.asSortingOrder(): ChatRoomsRepository.Order { ...@@ -171,6 +171,6 @@ fun Query.asSortingOrder(): ChatRoomsRepository.Order {
ChatRoomsRepository.Order.ACTIVITY ChatRoomsRepository.Order.ACTIVITY
} }
} }
else -> throw InvalidParameterException("Should be ByName or ByActivity") else -> throw IllegalArgumentException("Should be ByName or ByActivity")
} }
} }
...@@ -19,6 +19,7 @@ import chat.rocket.android.util.extensions.exhaustive ...@@ -19,6 +19,7 @@ import chat.rocket.android.util.extensions.exhaustive
import chat.rocket.android.util.extensions.removeTrailingSlash import chat.rocket.android.util.extensions.removeTrailingSlash
import chat.rocket.android.util.extensions.toEntity import chat.rocket.android.util.extensions.toEntity
import chat.rocket.android.util.extensions.userId import chat.rocket.android.util.extensions.userId
import chat.rocket.android.util.retryDB
import chat.rocket.common.model.BaseRoom import chat.rocket.common.model.BaseRoom
import chat.rocket.common.model.RoomType import chat.rocket.common.model.RoomType
import chat.rocket.common.model.SimpleUser import chat.rocket.common.model.SimpleUser
...@@ -41,13 +42,12 @@ import timber.log.Timber ...@@ -41,13 +42,12 @@ import timber.log.Timber
import java.util.HashSet import java.util.HashSet
import kotlin.system.measureTimeMillis import kotlin.system.measureTimeMillis
class DatabaseManager(val context: Application, class DatabaseManager(val context: Application, val serverUrl: String) {
val serverUrl: String) {
private val database: RCDatabase = androidx.room.Room.databaseBuilder(context, private val database: RCDatabase = androidx.room.Room.databaseBuilder(context,
RCDatabase::class.java, serverUrl.databaseName()) RCDatabase::class.java, serverUrl.databaseName())
.fallbackToDestructiveMigration() .fallbackToDestructiveMigration()
.build() .build()
private val dbContext = newSingleThreadContext("$serverUrl-db-context") private val dbContext = newSingleThreadContext("$serverUrl-db-context")
private val dbManagerContext = newSingleThreadContext("$serverUrl-db-manager-context") private val dbManagerContext = newSingleThreadContext("$serverUrl-db-manager-context")
...@@ -92,12 +92,14 @@ class DatabaseManager(val context: Application, ...@@ -92,12 +92,14 @@ class DatabaseManager(val context: Application,
} }
} }
fun logout() { suspend fun logout() {
database.clearAllTables() retryDB("clearAllTables") { database.clearAllTables() }
} }
suspend fun getRoom(id: String) = withContext(dbManagerContext) { suspend fun getRoom(id: String) = withContext(dbManagerContext) {
chatRoomDao().get(id) retryDB("getRoom($id)") {
chatRoomDao().get(id)
}
} }
fun processUsersBatch(users: List<User>) { fun processUsersBatch(users: List<User>) {
...@@ -124,11 +126,11 @@ class DatabaseManager(val context: Application, ...@@ -124,11 +126,11 @@ class DatabaseManager(val context: Application,
val toInsert = ArrayList<ChatRoomEntity>(batch.size / 2) val toInsert = ArrayList<ChatRoomEntity>(batch.size / 2)
val toUpdate = ArrayList<ChatRoomEntity>(batch.size) val toUpdate = ArrayList<ChatRoomEntity>(batch.size)
batch.forEach { batch.forEach {
when(it.type) { when (it.type) {
is Type.Removed -> toRemove.add(removeChatRoom(it.data)) is Type.Removed -> toRemove.add(removeChatRoom(it.data))
is Type.Inserted -> insertChatRoom(it.data)?.let { toInsert.add(it) } is Type.Inserted -> insertChatRoom(it.data)?.let { toInsert.add(it) }
is Type.Updated -> { is Type.Updated -> {
when(it.data) { when (it.data) {
is Subscription -> updateSubs[(it.data as Subscription).roomId] = it.data as Subscription is Subscription -> updateSubs[(it.data as Subscription).roomId] = it.data as Subscription
is Room -> updateRooms[(it.data as Room).id] = it.data as Room is Room -> updateRooms[(it.data as Room).id] = it.data as Room
} }
...@@ -152,12 +154,12 @@ class DatabaseManager(val context: Application, ...@@ -152,12 +154,12 @@ class DatabaseManager(val context: Application,
fun updateSelfUser(myself: Myself) { fun updateSelfUser(myself: Myself) {
launch(dbManagerContext) { launch(dbManagerContext) {
val user = userDao().getUser(myself.id) val user = retryDB("getUser(${myself.id})") { userDao().getUser(myself.id) }
val entity = user?.copy( val entity = user?.copy(
name = myself.name ?: user.name, name = myself.name ?: user.name,
username = myself.username ?: user.username, username = myself.username ?: user.username,
utcOffset = myself.utcOffset ?: user.utcOffset, utcOffset = myself.utcOffset ?: user.utcOffset,
status = myself.status?.toString() ?: user.status status = myself.status?.toString() ?: user.status
) ?: myself.asUser().toEntity() ) ?: myself.asUser().toEntity()
Timber.d("UPDATING SELF: $entity") Timber.d("UPDATING SELF: $entity")
...@@ -187,7 +189,7 @@ class DatabaseManager(val context: Application, ...@@ -187,7 +189,7 @@ class DatabaseManager(val context: Application,
private suspend fun createMessageEntities(message: Message): Pair<MessageEntity, List<BaseMessageEntity>> { private suspend fun createMessageEntities(message: Message): Pair<MessageEntity, List<BaseMessageEntity>> {
val messageEntity = message.toEntity() val messageEntity = message.toEntity()
val list = mutableListOf<BaseMessageEntity>() val list = mutableListOf<BaseMessageEntity>()
createAttachments(message)?.let { list.addAll(it) } createAttachments(message)?.let { list.addAll(it) }
createFavoriteRelations(message)?.let { list.addAll(it) } createFavoriteRelations(message)?.let { list.addAll(it) }
createMentionRelations(message)?.let { list.addAll(it) } createMentionRelations(message)?.let { list.addAll(it) }
createChannelRelations(message)?.let { list.addAll(it) } createChannelRelations(message)?.let { list.addAll(it) }
...@@ -225,7 +227,7 @@ class DatabaseManager(val context: Application, ...@@ -225,7 +227,7 @@ class DatabaseManager(val context: Application,
val list = mutableListOf<UrlEntity>() val list = mutableListOf<UrlEntity>()
message.urls!!.forEach { url -> message.urls!!.forEach { url ->
list.add(UrlEntity(message.id, url.url, url.parsedUrl?.host, url.meta?.title, list.add(UrlEntity(message.id, url.url, url.parsedUrl?.host, url.meta?.title,
url.meta?.description, url.meta?.imageUrl)) url.meta?.description, url.meta?.imageUrl))
} }
return list return list
...@@ -329,14 +331,14 @@ class DatabaseManager(val context: Application, ...@@ -329,14 +331,14 @@ class DatabaseManager(val context: Application,
} }
private fun removeChatRoom(data: BaseRoom): String { private fun removeChatRoom(data: BaseRoom): String {
return when(data) { return when (data) {
is Subscription -> data.roomId is Subscription -> data.roomId
else -> data.id else -> data.id
} }
} }
private suspend fun updateRoom(data: Room): ChatRoomEntity? { private suspend fun updateRoom(data: Room): ChatRoomEntity? {
return chatRoomDao().get(data.id)?.let { current -> return retryDB("getChatRoom(${data.id})") { chatRoomDao().get(data.id) }?.let { current ->
with(data) { with(data) {
val chatRoom = current.chatRoom val chatRoom = current.chatRoom
...@@ -344,14 +346,15 @@ class DatabaseManager(val context: Application, ...@@ -344,14 +346,15 @@ class DatabaseManager(val context: Application,
insertUserIfMissing(user) insertUserIfMissing(user)
chatRoom.copy( chatRoom.copy(
name = name ?: chatRoom.name, name = name ?: chatRoom.name,
fullname = fullName ?: chatRoom.fullname, fullname = fullName ?: chatRoom.fullname,
ownerId = user?.id ?: chatRoom.ownerId, ownerId = user?.id ?: chatRoom.ownerId,
readonly = readonly, readonly = readonly,
updatedAt = updatedAt ?: chatRoom.updatedAt, updatedAt = updatedAt ?: chatRoom.updatedAt,
lastMessageText = mapLastMessageText(lastMessage), lastMessageText = mapLastMessageText(lastMessage),
lastMessageUserId = lastMessage?.sender?.id, lastMessageUserId = lastMessage?.sender?.id,
lastMessageTimestamp = lastMessage?.timestamp lastMessageTimestamp = lastMessage?.timestamp,
muted = muted
) )
} }
} }
...@@ -373,7 +376,7 @@ class DatabaseManager(val context: Application, ...@@ -373,7 +376,7 @@ class DatabaseManager(val context: Application,
context.getString(R.string.msg_sent_attachment) context.getString(R.string.msg_sent_attachment)
private suspend fun updateSubscription(data: Subscription): ChatRoomEntity? { private suspend fun updateSubscription(data: Subscription): ChatRoomEntity? {
return chatRoomDao().get(data.roomId)?.let { current -> return retryDB("getRoom(${data.roomId}") { chatRoomDao().get(data.roomId) }?.let { current ->
with(data) { with(data) {
val userId = if (type is RoomType.DirectMessage) { val userId = if (type is RoomType.DirectMessage) {
...@@ -386,30 +389,32 @@ class DatabaseManager(val context: Application, ...@@ -386,30 +389,32 @@ class DatabaseManager(val context: Application,
val chatRoom = current.chatRoom val chatRoom = current.chatRoom
chatRoom.copy( chatRoom.copy(
id = roomId, id = roomId,
subscriptionId = id, subscriptionId = id,
type = type.toString(), type = type.toString(),
name = name ?: throw NullPointerException(), // this should be filtered on the SDK name = name
fullname = fullName ?: chatRoom.fullname, ?: throw NullPointerException(), // this should be filtered on the SDK
userId = userId ?: chatRoom.userId, fullname = fullName ?: chatRoom.fullname,
readonly = readonly ?: chatRoom.readonly, userId = userId ?: chatRoom.userId,
isDefault = isDefault, readonly = readonly ?: chatRoom.readonly,
favorite = isFavorite, isDefault = isDefault,
open = open, favorite = isFavorite,
alert = alert, open = open,
unread = unread, alert = alert,
userMentions = userMentions ?: chatRoom.userMentions, unread = unread,
groupMentions = groupMentions ?: chatRoom.groupMentions, userMentions = userMentions ?: chatRoom.userMentions,
updatedAt = updatedAt ?: chatRoom.updatedAt, groupMentions = groupMentions ?: chatRoom.groupMentions,
timestamp = timestamp ?: chatRoom.timestamp, updatedAt = updatedAt ?: chatRoom.updatedAt,
lastSeen = lastSeen ?: chatRoom.lastSeen timestamp = timestamp ?: chatRoom.timestamp,
lastSeen = lastSeen ?: chatRoom.lastSeen,
muted = chatRoom.muted
) )
} }
} }
} }
private suspend fun insertChatRoom(data: BaseRoom): ChatRoomEntity? { private suspend fun insertChatRoom(data: BaseRoom): ChatRoomEntity? {
return when(data) { return when (data) {
is Room -> insertRoom(data) is Room -> insertRoom(data)
is Subscription -> insertSubscription(data) is Subscription -> insertSubscription(data)
else -> null else -> null
...@@ -451,7 +456,8 @@ class DatabaseManager(val context: Application, ...@@ -451,7 +456,8 @@ class DatabaseManager(val context: Application,
id = room.id, id = room.id,
subscriptionId = subscription.id, subscriptionId = subscription.id,
type = room.type.toString(), type = room.type.toString(),
name = room.name ?: subscription.name ?: throw NullPointerException(), // this should be filtered on the SDK name = room.name ?: subscription.name
?: throw NullPointerException(), // this should be filtered on the SDK
fullname = subscription.fullName ?: room.fullName, fullname = subscription.fullName ?: room.fullName,
userId = userId, userId = userId,
ownerId = room.user?.id, ownerId = room.user?.id,
...@@ -506,7 +512,8 @@ class DatabaseManager(val context: Application, ...@@ -506,7 +512,8 @@ class DatabaseManager(val context: Application,
lastMessageText = mapLastMessageText(lastMessage), lastMessageText = mapLastMessageText(lastMessage),
lastMessageUserId = lastMessage?.sender?.id, lastMessageUserId = lastMessage?.sender?.id,
lastMessageTimestamp = lastMessage?.timestamp, lastMessageTimestamp = lastMessage?.timestamp,
broadcast = broadcast broadcast = broadcast,
muted = room.muted
) )
} }
} }
...@@ -535,39 +542,42 @@ class DatabaseManager(val context: Application, ...@@ -535,39 +542,42 @@ class DatabaseManager(val context: Application,
} }
} }
private fun findUser(userId: String): String? = userDao().findUser(userId) private suspend fun findUser(userId: String): String? =
retryDB("findUser($userId)") { userDao().findUser(userId) }
private fun doOperation(operation: Operation) { private suspend fun doOperation(operation: Operation) {
when (operation) { retryDB(description = "doOperation($operation)") {
is Operation.ClearStatus -> userDao().clearStatus() when (operation) {
is Operation.UpdateRooms -> { is Operation.ClearStatus -> userDao().clearStatus()
Timber.d("Running ChatRooms transaction: remove: ${operation.toRemove} - insert: ${operation.toInsert} - update: ${operation.toUpdate}") is Operation.UpdateRooms -> {
Timber.d("Running ChatRooms transaction: remove: ${operation.toRemove} - insert: ${operation.toInsert} - update: ${operation.toUpdate}")
chatRoomDao().update(operation.toInsert, operation.toUpdate, operation.toRemove) chatRoomDao().update(operation.toInsert, operation.toUpdate, operation.toRemove)
} }
is Operation.InsertRooms -> { is Operation.InsertRooms -> {
chatRoomDao().insertOrReplace(operation.chatRooms) chatRoomDao().insertOrReplace(operation.chatRooms)
} }
is Operation.CleanInsertRooms -> { is Operation.CleanInsertRooms -> {
chatRoomDao().cleanInsert(operation.chatRooms) chatRoomDao().cleanInsert(operation.chatRooms)
} }
is Operation.InsertUsers -> { is Operation.InsertUsers -> {
val time = measureTimeMillis { userDao().upsert(operation.users) } val time = measureTimeMillis { userDao().upsert(operation.users) }
Timber.d("Upserted users batch(${operation.users.size}) in $time MS") Timber.d("Upserted users batch(${operation.users.size}) in $time MS")
} }
is Operation.InsertUser -> { is Operation.InsertUser -> {
userDao().insert(operation.user) userDao().insert(operation.user)
} }
is Operation.UpsertUser -> { is Operation.UpsertUser -> {
userDao().upsert(operation.user) userDao().upsert(operation.user)
} }
is Operation.InsertMessages -> { is Operation.InsertMessages -> {
messageDao().insert(operation.list) messageDao().insert(operation.list)
} }
is Operation.SaveLastSync -> { is Operation.SaveLastSync -> {
messageDao().saveLastSync(operation.sync) messageDao().saveLastSync(operation.sync)
} }
}.exhaustive }.exhaustive
}
} }
} }
...@@ -579,6 +589,7 @@ sealed class Operation { ...@@ -579,6 +589,7 @@ sealed class Operation {
val toUpdate: List<ChatRoomEntity>, val toUpdate: List<ChatRoomEntity>,
val toRemove: List<String> val toRemove: List<String>
) : Operation() ) : Operation()
data class InsertRooms(val chatRooms: List<ChatRoomEntity>) : Operation() data class InsertRooms(val chatRooms: List<ChatRoomEntity>) : Operation()
data class CleanInsertRooms(val chatRooms: List<ChatRoomEntity>) : Operation() data class CleanInsertRooms(val chatRooms: List<ChatRoomEntity>) : Operation()
...@@ -607,10 +618,10 @@ private fun Myself.asUser(): User { ...@@ -607,10 +618,10 @@ private fun Myself.asUser(): User {
private fun String.databaseName(): String { private fun String.databaseName(): String {
val tmp = this.removePrefix("https://") val tmp = this.removePrefix("https://")
.removePrefix("http://") .removePrefix("http://")
.removeTrailingSlash() .removeTrailingSlash()
.replace("/","-") .replace("/", "-")
.replace(".", "_") .replace(".", "_")
return "$tmp.db" return "$tmp.db"
} }
\ No newline at end of file
...@@ -2,6 +2,7 @@ package chat.rocket.android.db ...@@ -2,6 +2,7 @@ package chat.rocket.android.db
import androidx.room.Database import androidx.room.Database
import androidx.room.RoomDatabase import androidx.room.RoomDatabase
import androidx.room.TypeConverters
import chat.rocket.android.db.model.AttachmentActionEntity import chat.rocket.android.db.model.AttachmentActionEntity
import chat.rocket.android.db.model.AttachmentEntity import chat.rocket.android.db.model.AttachmentEntity
import chat.rocket.android.db.model.AttachmentFieldEntity import chat.rocket.android.db.model.AttachmentFieldEntity
...@@ -14,6 +15,7 @@ import chat.rocket.android.db.model.MessagesSync ...@@ -14,6 +15,7 @@ import chat.rocket.android.db.model.MessagesSync
import chat.rocket.android.db.model.ReactionEntity import chat.rocket.android.db.model.ReactionEntity
import chat.rocket.android.db.model.UrlEntity import chat.rocket.android.db.model.UrlEntity
import chat.rocket.android.db.model.UserEntity import chat.rocket.android.db.model.UserEntity
import chat.rocket.android.emoji.internal.db.StringListConverter
@Database( @Database(
entities = [ entities = [
...@@ -23,11 +25,12 @@ import chat.rocket.android.db.model.UserEntity ...@@ -23,11 +25,12 @@ 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 = 10, version = 12,
exportSchema = true exportSchema = true
) )
@TypeConverters(StringListConverter::class)
abstract class RCDatabase : RoomDatabase() { abstract class RCDatabase : RoomDatabase() {
abstract fun userDao(): UserDao abstract fun userDao(): UserDao
abstract fun chatRoomDao(): ChatRoomDao abstract fun chatRoomDao(): ChatRoomDao
abstract fun messageDao(): MessageDao abstract fun messageDao(): MessageDao
} }
\ No newline at end of file
...@@ -5,6 +5,8 @@ import androidx.room.Entity ...@@ -5,6 +5,8 @@ import androidx.room.Entity
import androidx.room.ForeignKey import androidx.room.ForeignKey
import androidx.room.Index import androidx.room.Index
import androidx.room.PrimaryKey import androidx.room.PrimaryKey
import androidx.room.TypeConverters
import chat.rocket.android.emoji.internal.db.StringListConverter
@Entity(tableName = "chatrooms", @Entity(tableName = "chatrooms",
indices = [ indices = [
...@@ -20,6 +22,7 @@ import androidx.room.PrimaryKey ...@@ -20,6 +22,7 @@ import androidx.room.PrimaryKey
ForeignKey(entity = UserEntity::class, parentColumns = ["id"], childColumns = ["lastMessageUserId"]) ForeignKey(entity = UserEntity::class, parentColumns = ["id"], childColumns = ["lastMessageUserId"])
] ]
) )
@TypeConverters(StringListConverter::class)
data class ChatRoomEntity( data class ChatRoomEntity(
@PrimaryKey var id: String, @PrimaryKey var id: String,
var subscriptionId: String, var subscriptionId: String,
...@@ -42,7 +45,8 @@ data class ChatRoomEntity( ...@@ -42,7 +45,8 @@ data class ChatRoomEntity(
var lastMessageText: String? = null, var lastMessageText: String? = null,
var lastMessageUserId: String? = null, var lastMessageUserId: String? = null,
var lastMessageTimestamp: Long? = null, var lastMessageTimestamp: Long? = null,
var broadcast: Boolean? = false var broadcast: Boolean? = false,
var muted: List<String>? = null
) )
data class ChatRoom( data class ChatRoom(
...@@ -52,4 +56,4 @@ data class ChatRoom( ...@@ -52,4 +56,4 @@ data class ChatRoom(
var status: String?, var status: String?,
var lastMessageUserName: String?, var lastMessageUserName: String?,
var lastMessageUserFullName: String? var lastMessageUserFullName: String?
) )
\ No newline at end of file
...@@ -5,6 +5,7 @@ import chat.rocket.android.core.lifecycle.CancelStrategy ...@@ -5,6 +5,7 @@ import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.db.DatabaseManager import chat.rocket.android.db.DatabaseManager
import chat.rocket.android.server.infraestructure.RocketChatClientFactory import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.util.extension.launchUI import chat.rocket.android.util.extension.launchUI
import chat.rocket.android.util.retryDB
import chat.rocket.common.RocketChatException import chat.rocket.common.RocketChatException
import chat.rocket.common.model.roomTypeOf import chat.rocket.common.model.roomTypeOf
import chat.rocket.common.util.ifNull import chat.rocket.common.util.ifNull
...@@ -36,7 +37,7 @@ class FavoriteMessagesPresenter @Inject constructor( ...@@ -36,7 +37,7 @@ class FavoriteMessagesPresenter @Inject constructor(
view.showLoading() view.showLoading()
dbManager.getRoom(roomId)?.let { dbManager.getRoom(roomId)?.let {
val favoriteMessages = val favoriteMessages =
client.getFavoriteMessages(roomId, roomTypeOf(it.chatRoom.type), offset) client.getFavoriteMessages(roomId, roomTypeOf(it.chatRoom.type), offset)
val messageList = mapper.map(favoriteMessages.result, asNotReversed = true) val messageList = mapper.map(favoriteMessages.result, asNotReversed = true)
view.showFavoriteMessages(messageList) view.showFavoriteMessages(messageList)
offset += 1 * 30 offset += 1 * 30
......
...@@ -7,6 +7,7 @@ import chat.rocket.android.files.uimodel.FileUiModel ...@@ -7,6 +7,7 @@ import chat.rocket.android.files.uimodel.FileUiModel
import chat.rocket.android.files.uimodel.FileUiModelMapper import chat.rocket.android.files.uimodel.FileUiModelMapper
import chat.rocket.android.server.infraestructure.RocketChatClientFactory import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.util.extension.launchUI import chat.rocket.android.util.extension.launchUI
import chat.rocket.android.util.retryDB
import chat.rocket.common.RocketChatException import chat.rocket.common.RocketChatException
import chat.rocket.common.model.roomTypeOf import chat.rocket.common.model.roomTypeOf
import chat.rocket.common.util.ifNull import chat.rocket.common.util.ifNull
......
...@@ -6,6 +6,7 @@ import chat.rocket.android.members.uimodel.MemberUiModel ...@@ -6,6 +6,7 @@ import chat.rocket.android.members.uimodel.MemberUiModel
import chat.rocket.android.members.uimodel.MemberUiModelMapper import chat.rocket.android.members.uimodel.MemberUiModelMapper
import chat.rocket.android.server.infraestructure.RocketChatClientFactory import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.util.extension.launchUI import chat.rocket.android.util.extension.launchUI
import chat.rocket.android.util.retryDB
import chat.rocket.common.RocketChatException import chat.rocket.common.RocketChatException
import chat.rocket.common.model.roomTypeOf import chat.rocket.common.model.roomTypeOf
import chat.rocket.common.util.ifNull import chat.rocket.common.util.ifNull
...@@ -38,7 +39,7 @@ class MembersPresenter @Inject constructor( ...@@ -38,7 +39,7 @@ class MembersPresenter @Inject constructor(
view.showLoading() view.showLoading()
dbManager.getRoom(roomId)?.let { dbManager.getRoom(roomId)?.let {
val members = val members =
client.getMembers(roomId, roomTypeOf(it.chatRoom.type), offset, 60) client.getMembers(roomId, roomTypeOf(it.chatRoom.type), offset, 60)
val memberUiModels = mapper.mapToUiModelList(members.result) val memberUiModels = mapper.mapToUiModelList(members.result)
view.showMembers(memberUiModels, members.total) view.showMembers(memberUiModels, members.total)
offset += 1 * 60L offset += 1 * 60L
......
...@@ -5,6 +5,7 @@ import chat.rocket.android.core.lifecycle.CancelStrategy ...@@ -5,6 +5,7 @@ import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.db.DatabaseManager import chat.rocket.android.db.DatabaseManager
import chat.rocket.android.server.infraestructure.RocketChatClientFactory import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.util.extension.launchUI import chat.rocket.android.util.extension.launchUI
import chat.rocket.android.util.retryDB
import chat.rocket.common.RocketChatException import chat.rocket.common.RocketChatException
import chat.rocket.common.model.roomTypeOf import chat.rocket.common.model.roomTypeOf
import chat.rocket.common.util.ifNull import chat.rocket.common.util.ifNull
...@@ -36,7 +37,7 @@ class PinnedMessagesPresenter @Inject constructor( ...@@ -36,7 +37,7 @@ class PinnedMessagesPresenter @Inject constructor(
view.showLoading() view.showLoading()
dbManager.getRoom(roomId)?.let { dbManager.getRoom(roomId)?.let {
val pinnedMessages = val pinnedMessages =
client.getPinnedMessages(roomId, roomTypeOf(it.chatRoom.type), offset) client.getPinnedMessages(roomId, roomTypeOf(it.chatRoom.type), offset)
val messageList = mapper.map(pinnedMessages.result, asNotReversed = true) val messageList = mapper.map(pinnedMessages.result, asNotReversed = true)
view.showPinnedMessages(messageList) view.showPinnedMessages(messageList)
offset += 1 * 30 offset += 1 * 30
......
...@@ -296,11 +296,8 @@ class PushManager @Inject constructor( ...@@ -296,11 +296,8 @@ class PushManager @Inject constructor(
} }
private fun getContentIntent(context: Context, notificationId: Int, pushMessage: PushMessage, grouped: Boolean = false): PendingIntent { private fun getContentIntent(context: Context, notificationId: Int, pushMessage: PushMessage, grouped: Boolean = false): PendingIntent {
val notificationIntent = context.changeServerIntent(pushMessage.info.host, chatRoomId = pushMessage.info.roomId) val roomId = if (!grouped) pushMessage.info.roomId else null
// TODO - add support to go directly to the chatroom val notificationIntent = context.changeServerIntent(pushMessage.info.host, chatRoomId = roomId)
/*if (!isGrouped) {
notificationIntent.putExtra(EXTRA_ROOM_ID, pushMessage.info.roomId)
}*/
return PendingIntent.getActivity(context, random.nextInt(), notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT) return PendingIntent.getActivity(context, random.nextInt(), notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT)
} }
......
...@@ -22,6 +22,7 @@ import chat.rocket.core.internal.realtime.unsubscribe ...@@ -22,6 +22,7 @@ import chat.rocket.core.internal.realtime.unsubscribe
import chat.rocket.core.internal.rest.chatRooms import chat.rocket.core.internal.rest.chatRooms
import chat.rocket.core.model.Message import chat.rocket.core.model.Message
import chat.rocket.core.model.Myself import chat.rocket.core.model.Myself
import chat.rocket.core.model.Room
import kotlinx.coroutines.experimental.CommonPool import kotlinx.coroutines.experimental.CommonPool
import kotlinx.coroutines.experimental.Job import kotlinx.coroutines.experimental.Job
import kotlinx.coroutines.experimental.channels.Channel import kotlinx.coroutines.experimental.channels.Channel
...@@ -45,6 +46,7 @@ class ConnectionManager( ...@@ -45,6 +46,7 @@ class ConnectionManager(
private val roomMessagesChannels = LinkedHashMap<String, Channel<Message>>() private val roomMessagesChannels = LinkedHashMap<String, Channel<Message>>()
private val userDataChannels = ArrayList<Channel<Myself>>() private val userDataChannels = ArrayList<Channel<Myself>>()
private val roomsChannels = LinkedHashMap<String, Channel<Room>>()
private val subscriptionIdMap = HashMap<String, String>() private val subscriptionIdMap = HashMap<String, String>()
private var subscriptionId: String? = null private var subscriptionId: String? = null
...@@ -127,6 +129,18 @@ class ConnectionManager( ...@@ -127,6 +129,18 @@ class ConnectionManager(
maxSize = 10) { batch -> maxSize = 10) { batch ->
Timber.d("processing Stream batch: ${batch.size} - $batch") Timber.d("processing Stream batch: ${batch.size} - $batch")
dbManager.processChatRoomsBatch(batch) dbManager.processChatRoomsBatch(batch)
batch.forEach {
//TODO - Do we need to handle Type.Removed and Type.Inserted here?
if (it.type == Type.Updated) {
if (it.data is Room) {
val room = it.data as Room
roomsChannels[it.data.id]?.let { channel ->
channel.offer(room)
}
}
}
}
} }
val messagesActor = createBatchActor<Message>(messagesContext, parent = connectJob, val messagesActor = createBatchActor<Message>(messagesContext, parent = connectJob,
...@@ -241,6 +255,14 @@ class ConnectionManager( ...@@ -241,6 +255,14 @@ class ConnectionManager(
fun removeUserDataChannel(channel: Channel<Myself>) = userDataChannels.remove(channel) fun removeUserDataChannel(channel: Channel<Myself>) = userDataChannels.remove(channel)
fun addRoomChannel(roomId: String, channel: Channel<Room>) {
roomsChannels[roomId] = channel
}
fun removeRoomChannel(roomId: String) {
roomsChannels.remove(roomId)
}
fun subscribeRoomMessages(roomId: String, channel: Channel<Message>) { fun subscribeRoomMessages(roomId: String, channel: Channel<Message>) {
val oldSub = roomMessagesChannels.put(roomId, channel) val oldSub = roomMessagesChannels.put(roomId, channel)
if (oldSub != null) { if (oldSub != null) {
......
...@@ -7,6 +7,7 @@ import chat.rocket.android.db.model.FullMessage ...@@ -7,6 +7,7 @@ import chat.rocket.android.db.model.FullMessage
import chat.rocket.android.db.model.ReactionEntity import chat.rocket.android.db.model.ReactionEntity
import chat.rocket.android.db.model.UrlEntity import chat.rocket.android.db.model.UrlEntity
import chat.rocket.android.db.model.UserEntity import chat.rocket.android.db.model.UserEntity
import chat.rocket.android.util.retryDB
import chat.rocket.common.model.SimpleRoom import chat.rocket.common.model.SimpleRoom
import chat.rocket.common.model.SimpleUser import chat.rocket.common.model.SimpleUser
import chat.rocket.core.model.Message import chat.rocket.core.model.Message
...@@ -135,14 +136,18 @@ class DatabaseMessageMapper(private val dbManager: DatabaseManager) { ...@@ -135,14 +136,18 @@ class DatabaseMessageMapper(private val dbManager: DatabaseManager) {
with(attachment) { with(attachment) {
val fields = if (hasFields) { val fields = if (hasFields) {
withContext(CommonPool) { withContext(CommonPool) {
dbManager.messageDao().getAttachmentFields(attachment._id) retryDB("getAttachmentFields(${attachment._id})") {
dbManager.messageDao().getAttachmentFields(attachment._id)
}
}.map { Field(it.title, it.value) } }.map { Field(it.title, it.value) }
} else { } else {
null null
} }
val actions = if (hasActions) { val actions = if (hasActions) {
withContext(CommonPool) { withContext(CommonPool) {
dbManager.messageDao().getAttachmentActions(attachment._id) retryDB("getAttachmentActions(${attachment._id})") {
dbManager.messageDao().getAttachmentActions(attachment._id)
}
}.mapNotNull { mapAction(it) } }.mapNotNull { mapAction(it) }
} else { } else {
null null
...@@ -183,29 +188,6 @@ class DatabaseMessageMapper(private val dbManager: DatabaseManager) { ...@@ -183,29 +188,6 @@ class DatabaseMessageMapper(private val dbManager: DatabaseManager) {
return list return list
} }
/*private suspend fun mapColorAttachmentWithFields(entity: AttachmentEntity): ColorAttachment {
val fields = withContext(CommonPool) {
dbManager.messageDao().getAttachmentFields(entity._id)
}.map { Field(it.title, it.value) }
return with(entity) {
ColorAttachment(
color = Color.Custom(color ?: DEFAULT_COLOR_STR),
text = text ?: "",
fallback = fallback,
fields = fields)
}
}
private suspend fun mapActionAttachment(attachment: AttachmentEntity): ActionsAttachment {
val actions = withContext(CommonPool) {
dbManager.messageDao().getAttachmentActions(attachment._id)
}.mapNotNull { mapAction(it) }
return with(attachment) {
// TODO - remove the default "vertical" value from here...
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) {
"button" -> ButtonAction(action.type, action.text, action.url, action.isWebView, "button" -> ButtonAction(action.type, action.text, action.url, action.isWebView,
...@@ -214,13 +196,4 @@ class DatabaseMessageMapper(private val dbManager: DatabaseManager) { ...@@ -214,13 +196,4 @@ class DatabaseMessageMapper(private val dbManager: DatabaseManager) {
else -> null else -> null
} }
} }
/*private suspend fun mapAuthorAttachment(attachment: AttachmentEntity): AuthorAttachment {
val fields = withContext(CommonPool) {
dbManager.messageDao().getAttachmentFields(attachment._id)
}.map { Field(it.title, it.value) }
return with(attachment) {
AuthorAttachment(authorLink!!, authorIcon, authorName, fields)
}
}*/
} }
\ No newline at end of file
...@@ -4,6 +4,7 @@ import chat.rocket.android.db.DatabaseManager ...@@ -4,6 +4,7 @@ import chat.rocket.android.db.DatabaseManager
import chat.rocket.android.db.Operation import chat.rocket.android.db.Operation
import chat.rocket.android.db.model.MessagesSync import chat.rocket.android.db.model.MessagesSync
import chat.rocket.android.server.domain.MessagesRepository import chat.rocket.android.server.domain.MessagesRepository
import chat.rocket.android.util.retryDB
import chat.rocket.core.model.Message import chat.rocket.core.model.Message
import kotlinx.coroutines.experimental.CommonPool import kotlinx.coroutines.experimental.CommonPool
import kotlinx.coroutines.experimental.withContext import kotlinx.coroutines.experimental.withContext
...@@ -14,25 +15,31 @@ class DatabaseMessagesRepository( ...@@ -14,25 +15,31 @@ class DatabaseMessagesRepository(
) : MessagesRepository { ) : MessagesRepository {
override suspend fun getById(id: String): Message? = withContext(CommonPool) { override suspend fun getById(id: String): Message? = withContext(CommonPool) {
dbManager.messageDao().getMessageById(id)?.let { message -> mapper.map(message) } retryDB("getMessageById($id)") {
dbManager.messageDao().getMessageById(id)?.let { message -> mapper.map(message) }
}
} }
override suspend fun getByRoomId(roomId: String): List<Message> = withContext(CommonPool) { override suspend fun getByRoomId(roomId: String): List<Message> = withContext(CommonPool) {
// FIXME - investigate how to avoid this distinctBy here, since DAO is returning a lot of // FIXME - investigate how to avoid this distinctBy here, since DAO is returning a lot of
// duplicate rows (something related to our JOINS and relations on Room) // duplicate rows (something related to our JOINS and relations on Room)
dbManager.messageDao().getMessagesByRoomId(roomId) retryDB("getMessagesByRoomId($roomId)") {
.distinctBy { it.message.message.id } dbManager.messageDao().getMessagesByRoomId(roomId)
.let { messages -> .distinctBy { it.message.message.id }
mapper.map(messages) .let { messages ->
} mapper.map(messages)
}
}
} }
override suspend fun getRecentMessages(roomId: String, count: Long): List<Message> = withContext(CommonPool) { override suspend fun getRecentMessages(roomId: String, count: Long): List<Message> = withContext(CommonPool) {
dbManager.messageDao().getRecentMessagesByRoomId(roomId, count) retryDB("getRecentMessagesByRoomId($roomId, $count)") {
.distinctBy { it.message.message.id } dbManager.messageDao().getRecentMessagesByRoomId(roomId, count)
.let { messages -> .distinctBy { it.message.message.id }
mapper.map(messages) .let { messages ->
} mapper.map(messages)
}
}
} }
override suspend fun save(message: Message) { override suspend fun save(message: Message) {
...@@ -45,20 +52,24 @@ class DatabaseMessagesRepository( ...@@ -45,20 +52,24 @@ class DatabaseMessagesRepository(
override suspend fun removeById(id: String) { override suspend fun removeById(id: String) {
withContext(CommonPool) { withContext(CommonPool) {
dbManager.messageDao().delete(id) retryDB("delete($id)") { dbManager.messageDao().delete(id) }
} }
} }
override suspend fun removeByRoomId(roomId: String) { override suspend fun removeByRoomId(roomId: String) {
withContext(CommonPool) { withContext(CommonPool) {
dbManager.messageDao().deleteByRoomId(roomId) retryDB("deleteByRoomId($roomId)") {
dbManager.messageDao().deleteByRoomId(roomId)
}
} }
} }
override suspend fun getAllUnsent(): List<Message> = withContext(CommonPool) { override suspend fun getAllUnsent(): List<Message> = withContext(CommonPool) {
dbManager.messageDao().getUnsentMessages() retryDB("getUnsentMessages") {
.distinctBy { it.message.message.id } dbManager.messageDao().getUnsentMessages()
.let { mapper.map(it) } .distinctBy { it.message.message.id }
.let { mapper.map(it) }
}
} }
override suspend fun saveLastSyncDate(roomId: String, timeMillis: Long) { override suspend fun saveLastSyncDate(roomId: String, timeMillis: Long) {
...@@ -66,6 +77,8 @@ class DatabaseMessagesRepository( ...@@ -66,6 +77,8 @@ class DatabaseMessagesRepository(
} }
override suspend fun getLastSyncDate(roomId: String): Long? = withContext(CommonPool) { override suspend fun getLastSyncDate(roomId: String): Long? = withContext(CommonPool) {
dbManager.messageDao().getLastSync(roomId)?.let { it.timestamp } retryDB("getLastSync($roomId)") {
dbManager.messageDao().getLastSync(roomId)?.let { it.timestamp }
}
} }
} }
\ No newline at end of file
package chat.rocket.android.settings.password.presentation package chat.rocket.android.settings.password.presentation
import chat.rocket.android.analytics.AnalyticsManager
import chat.rocket.android.core.lifecycle.CancelStrategy import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.server.domain.GetCurrentServerInteractor import chat.rocket.android.server.domain.GetCurrentServerInteractor
import chat.rocket.android.server.infraestructure.RocketChatClientFactory import chat.rocket.android.server.infraestructure.RocketChatClientFactory
...@@ -14,6 +15,7 @@ import javax.inject.Inject ...@@ -14,6 +15,7 @@ import javax.inject.Inject
class PasswordPresenter @Inject constructor( class PasswordPresenter @Inject constructor(
private val view: PasswordView, private val view: PasswordView,
private val strategy: CancelStrategy, private val strategy: CancelStrategy,
private val analyticsManager: AnalyticsManager,
serverInteractor: GetCurrentServerInteractor, serverInteractor: GetCurrentServerInteractor,
factory: RocketChatClientFactory factory: RocketChatClientFactory
) { ) {
...@@ -30,10 +32,12 @@ class PasswordPresenter @Inject constructor( ...@@ -30,10 +32,12 @@ class PasswordPresenter @Inject constructor(
client.updateProfile(me.id, null, null, password, null) client.updateProfile(me.id, null, null, password, null)
} }
analyticsManager.logResetPassword(true)
view.showPasswordSuccessfullyUpdatedMessage() view.showPasswordSuccessfullyUpdatedMessage()
view.hideLoading()
} catch (exception: RocketChatException) { } catch (exception: RocketChatException) {
analyticsManager.logResetPassword(false)
view.showPasswordFailsUpdateMessage(exception.message) view.showPasswordFailsUpdateMessage(exception.message)
} finally {
view.hideLoading() view.hideLoading()
} }
} }
......
...@@ -8,6 +8,7 @@ import android.view.View ...@@ -8,6 +8,7 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.AdapterView import android.widget.AdapterView
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.net.toUri
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import chat.rocket.android.R import chat.rocket.android.R
import chat.rocket.android.about.ui.AboutFragment import chat.rocket.android.about.ui.AboutFragment
...@@ -72,25 +73,35 @@ class SettingsFragment : Fragment(), SettingsView, AdapterView.OnItemClickListen ...@@ -72,25 +73,35 @@ class SettingsFragment : Fragment(), SettingsView, AdapterView.OnItemClickListen
resources.getStringArray(R.array.settings_actions)[1] -> resources.getStringArray(R.array.settings_actions)[1] ->
activity?.startActivity(Intent(activity, PasswordActivity::class.java)) activity?.startActivity(Intent(activity, PasswordActivity::class.java))
resources.getStringArray(R.array.settings_actions)[2] -> { resources.getStringArray(R.array.settings_actions)[2] -> shareApp()
(activity as AppCompatActivity).addFragmentBackStack(
TAG_ABOUT_FRAGMENT,
R.id.fragment_container
) {
AboutFragment.newInstance()
}
}
resources.getStringArray(R.array.settings_actions)[3] -> shareApp() resources.getStringArray(R.array.settings_actions)[3] -> showAppOnStore()
resources.getStringArray(R.array.settings_actions)[4] -> activity?.startActivity( resources.getStringArray(R.array.settings_actions)[4] -> contactSupport()
resources.getStringArray(R.array.settings_actions)[5] -> activity?.startActivity(
context?.webViewIntent( context?.webViewIntent(
getString(R.string.license_url), getString(R.string.license_url),
getString(R.string.title_licence) getString(R.string.title_licence)
) )
) )
resources.getStringArray(R.array.settings_actions)[5] -> contactSupport() resources.getStringArray(R.array.settings_actions)[6] -> {
(activity as AppCompatActivity).addFragmentBackStack(
TAG_ABOUT_FRAGMENT,
R.id.fragment_container
) {
AboutFragment.newInstance()
}
}
}
}
private fun showAppOnStore() {
try {
startActivity(Intent(Intent.ACTION_VIEW, getString(R.string.market_link).toUri()))
} catch (error: ActivityNotFoundException) {
startActivity(Intent(Intent.ACTION_VIEW, getString(R.string.play_store_link).toUri()))
} }
} }
......
package chat.rocket.android.util package chat.rocket.android.util
import android.database.sqlite.SQLiteDatabaseLockedException
import chat.rocket.common.RocketChatNetworkErrorException import chat.rocket.common.RocketChatNetworkErrorException
import kotlinx.coroutines.experimental.TimeoutCancellationException import kotlinx.coroutines.experimental.TimeoutCancellationException
import kotlinx.coroutines.experimental.delay import kotlinx.coroutines.experimental.delay
...@@ -8,6 +9,7 @@ import timber.log.Timber ...@@ -8,6 +9,7 @@ import timber.log.Timber
import kotlin.coroutines.experimental.coroutineContext import kotlin.coroutines.experimental.coroutineContext
const val DEFAULT_RETRY = 3 const val DEFAULT_RETRY = 3
private const val DEFAULT_DB_RETRY = 5
suspend fun <T> retryIO( suspend fun <T> retryIO(
description: String = "<missing description>", description: String = "<missing description>",
...@@ -32,6 +34,33 @@ suspend fun <T> retryIO( ...@@ -32,6 +34,33 @@ suspend fun <T> retryIO(
currentDelay = (currentDelay * factor).toLong().coerceAtMost(maxDelay) currentDelay = (currentDelay * factor).toLong().coerceAtMost(maxDelay)
} }
if (!coroutineContext.isActive) throw TimeoutCancellationException("job canceled")
return block() // last attempt
}
suspend fun <T> retryDB(
description: String = "<missing description>",
times: Int = DEFAULT_DB_RETRY,
initialDelay: Long = 100, // 0.1 second
maxDelay: Long = 500, // 0.5 second
factor: Double = 1.2,
block: suspend () -> T): T
{
var currentDelay = initialDelay
repeat(times - 1) { currentTry ->
if (!coroutineContext.isActive) throw TimeoutCancellationException("job canceled")
try {
return block()
} catch (e: SQLiteDatabaseLockedException) {
Timber.d(e, "failed call($currentTry): $description")
e.printStackTrace()
}
if (!coroutineContext.isActive) throw TimeoutCancellationException("job canceled")
delay(currentDelay)
currentDelay = (currentDelay * factor).toLong().coerceAtMost(maxDelay)
}
if (!coroutineContext.isActive) throw TimeoutCancellationException("job canceled") if (!coroutineContext.isActive) throw TimeoutCancellationException("job canceled")
return block() // last attempt return block() // last attempt
} }
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_gravity="center">
<ImageView
android:id="@+id/emoji_image_view"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_gravity="center"
tools:src="@tools:sample/avatars" />
</FrameLayout>
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/emoji_view"
android:layout_width="48dp"
android:layout_height="48dp"
android:foreground="?selectableItemBackground"
android:layout_gravity="center"
android:gravity="center"
android:textColor="#000000"
android:textSize="24sp"
tools:text="😀" />
<?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:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/colorWhite"
android:padding="16dp">
<ViewFlipper
android:id="@+id/view_flipper"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:foregroundGravity="center"
app:layout_constraintTop_toTopOf="parent">
<include layout="@layout/emoji_row_item" />
<include layout="@layout/emoji_image_row_item" />
</ViewFlipper>
<TextView
android:id="@+id/text_view_usernames"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:fontFamily="sans-serif-light"
android:textColor="@color/darkGray"
android:textSize="14sp"
app:layout_constraintBottom_toBottomOf="@+id/view_flipper"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/view_flipper"
app:layout_constraintTop_toTopOf="@+id/view_flipper"
tools:text="Ann reacted with :grin:" />
</androidx.constraintlayout.widget.ConstraintLayout>
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
<string name="title_settings">Einstellungen</string> <string name="title_settings">Einstellungen</string>
<string name="title_preferences">Preferences</string> <!-- TODO Add translation --> <string name="title_preferences">Preferences</string> <!-- TODO Add translation -->
<string name="title_change_password">Ändere Passwort</string> <string name="title_change_password">Ändere Passwort</string>
<string name="title_rate_us">Bewerten Sie uns</string>
<string name="title_admin_panel">Admin panel</string> <!-- TODO Add translation --> <string name="title_admin_panel">Admin panel</string> <!-- TODO Add translation -->
<string name="title_password">Ändere Passwort</string> <string name="title_password">Ändere Passwort</string>
<string name="title_update_profile">Update Profil</string> <string name="title_update_profile">Update Profil</string>
...@@ -58,11 +59,12 @@ ...@@ -58,11 +59,12 @@
<!-- Settings List --> <!-- Settings List -->
<string-array name="settings_actions"> <string-array name="settings_actions">
<item name="item_preferences">Preferences</item> <!-- TODO Add translation --> <item name="item_preferences">Preferences</item> <!-- TODO Add translation -->
<item name="item_password">Ändere Passwort</item> <item name="item_password">Change password</item> <!-- TODO Add translation -->
<item name="item_password">Über</item> <item name="item_share_app">Share app</item> <!-- TODO Add translation -->
<item name="item_share_app">App teilen</item> <item name="item_rate_us">Rate us</item> <!-- TODO Add translation -->
<item name="item_share_app_licence">Licence</item> <!-- TODO Add translation -->
<item name="item_contact_us">Contact us</item> <!-- TODO Add translation --> <item name="item_contact_us">Contact us</item> <!-- TODO Add translation -->
<item name="item_licence">Licence</item> <!-- TODO Add translation -->
<item name="item_about">About</item> <!-- TODO Add translation -->
</string-array> </string-array>
<!-- Regular information messages --> <!-- Regular information messages -->
...@@ -151,6 +153,13 @@ ...@@ -151,6 +153,13 @@
<string name="msg_continue_with_wordpress">Continue with <b>WordPress</b></string> <!-- TODO Add translation --> <string name="msg_continue_with_wordpress">Continue with <b>WordPress</b></string> <!-- TODO Add translation -->
<string name="msg_two_factor_authentication">Two-factor Authentication</string> <!-- TODO Add translation --> <string name="msg_two_factor_authentication">Two-factor Authentication</string> <!-- TODO Add translation -->
<string name="msg__your_2fa_code">What’s your 2FA code?</string> <!-- TODO Add translation --> <string name="msg__your_2fa_code">What’s your 2FA code?</string> <!-- TODO Add translation -->
<!-- TODO - Add proper translation -->
<string name="msg_muted_on_this_channel">You are muted on this channel</string>
<!-- TODO - Add proper translation -->
<plurals name="msg_reacted_with_">
<item quantity="one">%1$s reacted with %2$s</item>
<item quantity="other">%1$s reacted with %2$s</item>
</plurals>
<!-- Create channel messages --> <!-- Create channel messages -->
<string name="msg_private_channel">Privat</string> <string name="msg_private_channel">Privat</string>
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
<string name="title_settings">Configuraciones</string> <string name="title_settings">Configuraciones</string>
<string name="title_preferences">Preferences</string> <!-- TODO Add translation --> <string name="title_preferences">Preferences</string> <!-- TODO Add translation -->
<string name="title_change_password">Cambia la contraseña</string> <string name="title_change_password">Cambia la contraseña</string>
<string name="title_rate_us">Nos califica</string>
<string name="title_admin_panel">Admin panel</string> <!-- TODO Add translation --> <string name="title_admin_panel">Admin panel</string> <!-- TODO Add translation -->
<string name="title_password">Cambia la contraseña</string> <string name="title_password">Cambia la contraseña</string>
<string name="title_update_profile">Actualización del perfil</string> <string name="title_update_profile">Actualización del perfil</string>
...@@ -57,11 +58,12 @@ ...@@ -57,11 +58,12 @@
<!-- Settings List --> <!-- Settings List -->
<string-array name="settings_actions"> <string-array name="settings_actions">
<item name="item_preferences">Preferences</item> <!-- TODO Add translation --> <item name="item_preferences">Preferences</item> <!-- TODO Add translation -->
<item name="item_password">Cambia la contraseña</item> <item name="item_password">Change password</item> <!-- TODO Add translation -->
<item name="item_password">Acerca de</item> <item name="item_share_app">Share app</item> <!-- TODO Add translation -->
<item name="item_share_app">Compartir aplicación</item> <item name="item_rate_us">Rate us</item> <!-- TODO Add translation -->
<item name="item_share_app_licence">Licence</item> <!-- TODO Add translation -->
<item name="item_contact_us">Contact us</item> <!-- TODO Add translation --> <item name="item_contact_us">Contact us</item> <!-- TODO Add translation -->
<item name="item_licence">Licence</item> <!-- TODO Add translation -->
<item name="item_about">About</item> <!-- TODO Add translation -->
</string-array> </string-array>
<!-- Regular information messages --> <!-- Regular information messages -->
...@@ -168,6 +170,11 @@ ...@@ -168,6 +170,11 @@
<string name="msg_permalink_copied">Permalink copied</string> <!-- TODO - Add proper translation --> <string name="msg_permalink_copied">Permalink copied</string> <!-- TODO - Add proper translation -->
<string name="msg_send_email">Send email</string> <!-- TODO - Add proper translation --> <string name="msg_send_email">Send email</string> <!-- TODO - Add proper translation -->
<string name="msg_android_app_support">Android app support</string> <!-- TODO - Add proper translation --> <string name="msg_android_app_support">Android app support</string> <!-- TODO - Add proper translation -->
<string name="msg_muted_on_this_channel">You are muted on this channel</string> <!-- TODO - Add proper translation -->
<plurals name="msg_reacted_with_">
<item quantity="one">%1$s reacted with %2$s</item> <!-- TODO - Add proper translation -->
<item quantity="other">%1$s reacted with %2$s</item> <!-- TODO - Add proper translation -->
</plurals>
<!-- Preferences messages --> <!-- Preferences messages -->
<string name="msg_analytics_tracking">Analytics tracking</string> <!-- TODO Add translation --> <string name="msg_analytics_tracking">Analytics tracking</string> <!-- TODO Add translation -->
......
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
<string name="title_settings">Paramètres</string> <string name="title_settings">Paramètres</string>
<string name="title_preferences">Préférences</string> <string name="title_preferences">Préférences</string>
<string name="title_change_password">Changer le mot de passe</string> <string name="title_change_password">Changer le mot de passe</string>
<string name="title_rate_us">évaluez nous</string>
<string name="title_admin_panel">Administration</string> <string name="title_admin_panel">Administration</string>
<string name="title_password">Changer le mot de passe</string> <string name="title_password">Changer le mot de passe</string>
<string name="title_update_profile">Mettre à jour le profil</string> <string name="title_update_profile">Mettre à jour le profil</string>
...@@ -57,12 +58,13 @@ ...@@ -57,12 +58,13 @@
<!-- Settings List --> <!-- Settings List -->
<string-array name="settings_actions"> <string-array name="settings_actions">
<item name="item_preferences">Préférences</item> <item name="item_preferences">Preferences</item> <!-- TODO Add translation -->
<item name="item_password">Changer le mot de passe</item> <item name="item_password">Change password</item> <!-- TODO Add translation -->
<item name="item_password">À propos</item> <item name="item_share_app">Share app</item> <!-- TODO Add translation -->
<item name="item_share_app">Partager l\'application</item> <item name="item_rate_us">Rate us</item> <!-- TODO Add translation -->
<item name="item_share_app_licence">Licence</item> <!-- TODO Add translation -->
<item name="item_contact_us">Contact us</item> <!-- TODO Add translation --> <item name="item_contact_us">Contact us</item> <!-- TODO Add translation -->
<item name="item_licence">Licence</item> <!-- TODO Add translation -->
<item name="item_about">About</item> <!-- TODO Add translation -->
</string-array> </string-array>
<!-- Regular information messages --> <!-- Regular information messages -->
...@@ -160,6 +162,11 @@ ...@@ -160,6 +162,11 @@
<string name="msg_permalink_copied">Permalink copied</string> <!-- TODO - Add proper translation --> <string name="msg_permalink_copied">Permalink copied</string> <!-- TODO - Add proper translation -->
<string name="msg_send_email">Send email</string> <!-- TODO - Add proper translation --> <string name="msg_send_email">Send email</string> <!-- TODO - Add proper translation -->
<string name="msg_android_app_support">Android app support</string> <!-- TODO - Add proper translation --> <string name="msg_android_app_support">Android app support</string> <!-- TODO - Add proper translation -->
<string name="msg_muted_on_this_channel">You are muted on this channel</string> <!-- TODO - Add proper translation -->
<plurals name="msg_reacted_with_">
<item quantity="one">%1$s reacted with %2$s</item> <!-- TODO - Add proper translation -->
<item quantity="other">%1$s reacted with %2$s</item> <!-- TODO - Add proper translation -->
</plurals>
<!-- Create channel messages --> <!-- Create channel messages -->
<string name="msg_private_channel">Privé</string> <string name="msg_private_channel">Privé</string>
......
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
<string name="title_settings">सेटिंग्स</string> <string name="title_settings">सेटिंग्स</string>
<string name="title_preferences">प्राथमिकताएँ</string> <string name="title_preferences">प्राथमिकताएँ</string>
<string name="title_change_password">पासवर्ड बदलें</string> <string name="title_change_password">पासवर्ड बदलें</string>
<string name="title_rate_us">हमें रेटिंग दें</string>
<string name="title_admin_panel">एडमिन पैनल</string> <string name="title_admin_panel">एडमिन पैनल</string>
<string name="title_password">पासवर्ड बदलें</string> <string name="title_password">पासवर्ड बदलें</string>
<string name="title_update_profile">प्रोफ़ाइल अपडेट करें</string> <string name="title_update_profile">प्रोफ़ाइल अपडेट करें</string>
...@@ -57,12 +58,13 @@ ...@@ -57,12 +58,13 @@
<!-- Settings List --> <!-- Settings List -->
<string-array name="settings_actions"> <string-array name="settings_actions">
<item name="item_preferences">प्राथमिकताएँ</item> <item name="item_preferences">Preferences</item> <!-- TODO Add translation -->
<item name="item_password">पासवर्ड बदलें</item> <item name="item_password">Change password</item> <!-- TODO Add translation -->
<item name="item_password">परिचय</item> <item name="item_share_app">Share app</item> <!-- TODO Add translation -->
<item name="item_share_app">ऐप शेयर करें</item> <item name="item_rate_us">Rate us</item> <!-- TODO Add translation -->
<item name="item_share_app_licence">Licence</item> <!-- TODO Add translation -->
<item name="item_contact_us">Contact us</item> <!-- TODO Add translation --> <item name="item_contact_us">Contact us</item> <!-- TODO Add translation -->
<item name="item_licence">Licence</item> <!-- TODO Add translation -->
<item name="item_about">About</item> <!-- TODO Add translation -->
</string-array> </string-array>
<!-- Regular information messages --> <!-- Regular information messages -->
...@@ -174,6 +176,12 @@ ...@@ -174,6 +176,12 @@
<string name="msg_permalink_copied">Permalink copied</string> <!-- TODO - Add proper translation --> <string name="msg_permalink_copied">Permalink copied</string> <!-- TODO - Add proper translation -->
<string name="msg_send_email">Send email</string> <!-- TODO - Add proper translation --> <string name="msg_send_email">Send email</string> <!-- TODO - Add proper translation -->
<string name="msg_android_app_support">Android app support</string> <!-- TODO - Add proper translation --> <string name="msg_android_app_support">Android app support</string> <!-- TODO - Add proper translation -->
<string name="msg_muted_on_this_channel">You are muted on this channel</string> <!-- TODO - Add proper translation -->
<plurals name="msg_reacted_with_">
<item quantity="one">%1$s reacted with %2$s</item> <!-- TODO - Add proper translation -->
<item quantity="few">%1$s reacted with %2$s</item> <!-- TODO - Add proper translation -->
<item quantity="many">%1$s reacted with %2$s</item> <!-- TODO - Add proper translation -->
</plurals>
<!-- Preferences messages --> <!-- Preferences messages -->
<string name="msg_analytics_tracking">एनालिटिक्स ट्रैकिंग</string> <string name="msg_analytics_tracking">एनालिटिक्स ट्रैकिंग</string>
......
<resources>
<!-- Titles -->
<string name="title_sign_in_your_server">Accedi al tuo server</string>
<string name="title_log_in">Accesso</string>
<string name="title_share_the_app">Condividi la App</string>
<string name="title_register_username">Registra nome utente</string>
<string name="title_reset_password">Annullare password</string>
<string name="title_sign_up">Iscrizione</string>
<string name="title_authentication">Autenticazione</string>
<string name="title_legal_terms">Termini Legali</string>
<string name="title_chats">Stanze</string>
<string name="title_profile">Profilo</string>
<string name="title_members">Membri</string>
<string name="title_counted_members">(%d) Membri</string>
<string name="title_settings">Parametri</string>
<string name="title_preferences">Preferenze</string>
<string name="title_change_password">Cambiare Password</string>
<string name="title_rate_us">Valutaci</string>
<string name="title_admin_panel">Pannello di Amministrazione</string>
<string name="title_password">Password</string>
<string name="title_update_profile">Aggiorna Profilo</string>
<string name="title_about">Informazioni</string>
<string name="title_create_channel">Crea Canale</string>
<string name="title_are_you_sure">Sei sicuro che vuoi uscire ?</string>
<!-- Actions -->
<string name="action_connect">Collega</string>
<string name="action_use_this_username">Usa questo nome utente</string>
<string name="action_terms_of_service">Termini di Servizio</string>
<string name="action_privacy_policy">Politica sulla Riservatezza</string>
<string name="action_search">Cerca</string>
<string name="action_update">Aggiorna</string>
<string name="action_settings">Parametri</string>
<string name="action_create_channel">Crea canale</string>
<string name="action_create">Crea</string>
<string name="action_logout">Disconnettersi</string>
<string name="action_attach_a_files">Attach a file</string> <!-- TODO ADD translation -->
<string name="action_confirm_password">Conferma Cambio Password</string>
<string name="action_join_chat">Iscriviti alla stanza</string>
<string name="action_add_account">Aggiungi utente</string>
<string name="action_online">In linea</string>
<string name="action_away">Lontano</string>
<string name="action_busy">Occupato</string>
<string name="action_invisible">Invisibile</string>
<string name="action_drawing">Disegno</string>
<string name="action_save_to_gallery">Salva in galleria</string>
<string name="action_select_photo_from_gallery">Seleziona foto dalla galleria</string>
<string name="action_take_a_photo">Take a photo</string> <!-- TODO ADD translation -->
<string name="action_reset_avatar">Reimposta avatar</string>
<string name="action_connect_server">Collegati a un server</string>
<string name="action_join_community">Entra nella community</string>
<string name="action_create_server">Crea un nuovo server</string>
<string name="action_register">Registra</string>
<string name="action_confirm">Conferma</string>
<string name="action_delete_account">Elimina utente</string>
<!-- Settings List -->
<string-array name="settings_actions">
<item name="item_preferences">Preferences</item> <!-- TODO Add translation -->
<item name="item_password">Change password</item> <!-- TODO Add translation -->
<item name="item_share_app">Share app</item> <!-- TODO Add translation -->
<item name="item_rate_us">Rate us</item> <!-- TODO Add translation -->
<item name="item_contact_us">Contact us</item> <!-- TODO Add translation -->
<item name="item_licence">Licence</item> <!-- TODO Add translation -->
<item name="item_about">About</item> <!-- TODO Add translation -->
</string-array>
<!-- Regular information messages -->
<string name="msg_generic_error">Mi dispiace, si è verificato un errore, per favore riprova</string>
<string name="msg_no_data_to_display">Nessun dato da visualizzare</string>
<string name="msg_check_this_out">Controllalo</string>
<string name="msg_share_using">Condividi utilizzando</string>
<string name="msg_profile_update_successfully">Aggiornamento del profilo con successo</string>
<string name="msg_username">Nome utente</string>
<string name="msg_username_or_email">Nome utente o e-mail</string>
<string name="msg_password">Password</string>
<string name="msg_name">Nome</string>
<string name="msg_email">E-mail</string>
<string name="msg_avatar_url">Avatar URL</string>
<string name="msg_or_continue_using_social_accounts">O continuare utilizzando un account social</string>
<string name="msg_new_user">Nuovo utente? %1$s</string>
<string name="msg_forgot__your_password">Hai dimenticato la tua password?</string>
<string name="msg_reset">Reimposta</string>
<string name="msg_check_your_email_to_reset_your_password">E-mail inviata! Controlla la tua casella di posta per reimpostare la password.</string>
<string name="msg_invalid_email">Per favore digita una e-mail valida</string>
<string name="msg_new_user_agreement">Procedendo accetti i nostri\n%1$s e %2$s</string>
<string name="msg_yesterday">Ieri</string>
<string name="msg_today">Oggi</string>
<string name="msg_message">Messaggio</string>
<string name="msg_this_room_is_read_only">Questa stanza è di sola lettura</string>
<string name="msg_invalid_2fa_code">Invalido Codice 2FA non valido</string>
<string name="msg_invalid_file">Documento non valido</string>
<string name="msg_invalid_server_url">URL del server non valido</string>
<string name="msg_content_description_log_in_using_facebook">Accedi usando Facebook</string>
<string name="msg_content_description_log_in_using_github">Accedi usando Github</string>
<string name="msg_content_description_log_in_using_google">Accedi usando Google</string>
<string name="msg_content_description_log_in_using_linkedin">Accedi usando Linkedin</string>
<string name="msg_content_description_log_in_using_meteor">Accedi usando Meteor</string>
<string name="msg_content_description_log_in_using_twitter">Accedi usando Twitter</string>
<string name="msg_content_description_log_in_using_gitlab">Accedi usando Gitlab</string>
<string name="msg_content_description_log_in_using_wordpress">Accedi usando WordPress</string>
<string name="msg_content_description_send_message">Invia messaggio</string>
<string name="msg_content_description_show_more_login_options">Mostra ulteriori opzioni di accesso</string>
<string name="msg_content_description_show_attachment_options">Mostra opzioni per allegati</string>
<string name="msg_you">Tu</string>
<string name="msg_unknown">Sconosciuto</string>
<string name="msg_email_address">Indirizzo e-mail</string>
<string name="msg_utc_offset">Fuso orario UTC</string>
<string name="msg_new_password">Inserire Nuova Password</string>
<string name="msg_confirm_password">Confermare Nuova Password</string>
<string name="msg_channel_name">Nome Canale</string>
<string name="msg_search">Ricerca</string>
<string name="msg_unread_messages">Messaggi non letti</string>
<string name="msg_preview_video">Video</string>
<string name="msg_preview_audio">Audio</string>
<string name="msg_preview_photo">Foto</string>
<string name="msg_preview_file">Documento</string>
<string name="msg_no_messages_yet">Nessun nuovo messaggio</string>
<string name="msg_ok">OK</string>
<string name="msg_update_app_version_in_order_to_continue">Versione server non aggiornata. Si prega di contattare l\'amministratore del server per aggiornare la versione del server per continuare.</string>
<string name="msg_ver_not_recommended">Sembra che la versione del tuo server sia inferiore alla versione consigliata %1$s.\nÈ ancora possibile accedere ma è possibile che si verifichino comportamenti imprevisti.</string>
<string name="msg_ver_not_minimum">Sembra che la versione del tuo server sia inferiore alla versione minima richiesta %1$s.\nSi prega di aggiornare il server per accedere!</string>
<string name="msg_no_chat_title">Nessun messaggio chat</string>
<string name="msg_no_chat_description">Inizia a conversare per vedere i tuoi\nmessagggi qui.</string>
<string name="msg_cancel">CANCELLARE</string>
<string name="msg_http_insecure">Quando si utilizza HTTP, ci si connette a un server non sicuro. Non ti consigliamo di farlo.</string>
<string name="msg_error_checking_server_version">Si è verificato un errore durante il controllo della versione del server, riprovare</string>
<string name="msg_invalid_server_protocol">Il protocollo selezionato non è accettato da questo server, prova a utilizzare HTTPS</string>
<string name="msg_image_saved_successfully">L\'immagine è stata salvata nella galleria</string>
<string name="msg_image_saved_failed">Impossibile salvare l\'immagine</string>
<string name="msg_edited">(modificato)</string>
<string name="msg_and">\u0020e\u0020</string>
<string name="msg_is_typing">\u0020sta scrivendo…</string>
<string name="msg_are_typing">\u0020stanno scrivendo…</string>
<string name="msg_several_users_are_typing">Alcuni utenti stanno scrivendo…</string>
<string name="msg_no_search_found">Nessun risultato trovato</string>
<string name="msg_log_out">Disconnessione…</string>
<string name="msg_upload_file">Caricamento documento</string>
<string name="msg_file_description">Descrizione documento</string>
<string name="msg_send">Inviare</string>
<string name="msg_sent_attachment">Inviato allegato</string>
<string name="msg_welcome_to_rocket_chat">Benvenuto in Rocket.Chat </string>
<string name="msg_team_communication">Team Communication</string>
<string name="msg_login_with_email">Accedi con <b>e-mail</b></string>
<string name="msg_create_account">Crea un utente</string>
<string name="msg_continue_with_facebook">Continua con <b>Facebook</b></string>
<string name="msg_continue_with_github">Continua con <b>Github</b></string>
<string name="msg_continue_with_google">Continua con <b>Google</b></string>
<string name="msg_continue_with_linkedin">Continua con <b>Linkedin</b></string>
<string name="msg_continue_with_gitlab">Continua con <b>GitLab</b></string>
<string name="msg_continue_with_wordpress">Continua con <b>WordPress</b></string>
<string name="msg_two_factor_authentication">Autenticazione a 2 fattori (2FA)</string>
<string name="msg__your_2fa_code">Qual è il tuo codice 2FA ?</string>
<string name="msg_permalink_copied">Permalink copiato</string>
<plurals name="msg_reacted_with_">
<item quantity="one">%1$s ha reagito con %2$s</item>
<item quantity="other">%1$s ha reagito con %2$s</item>
</plurals>
<!-- Create channel messages -->
<string name="msg_private_channel">Privato</string>
<string name="msg_public_channel">Pubblico</string>
<string name="msg_private_channel_description">Solo tu e i membri invitati possono accedere a questo canale</string>
<string name="msg_public_channel_description">Tutti possono accedere a questo canale</string>
<string name="msg_ready_only_channel">Canale a sola lettura</string>
<string name="msg_ready_only_channel_description">Solo l\'amministratore può scrivere nuovi messaggi</string>
<string name="msg_invite_members">Invita i partecipanti per il canale</string>
<string name="msg_member_already_added">Hai già selezionato questo utente</string>
<string name="msg_member_not_found">Partecipante non trovato</string>
<string name="msg_channel_created_successfully">Canale creato con successo</string>
<string name="msg_message_copied">Messaggio copiato</string>
<string name="msg_delete_message">Cancellare Messaggio</string>
<string name="msg_delete_description">Sei sicuro di voler eliminare questo messaggio ?</string>
<string name="msg_view_more">vedere di più</string>
<string name="msg_view_less">vedere di meno</string>
<string name="msg_muted_on_this_channel">Sei disattivato su questo canale</string>
<!-- Preferences messages -->
<string name="msg_analytics_tracking">Tracciamento Analitico</string>
<string name="msg_send_analytics_tracking">Invia statistiche anonime per migliorare questa app</string>
<string name="msg_do_not_send_analytics_tracking">Non inviare statiche anonime per migliorare questa app</string>
<string name="msg_not_applicable_since_it_is_a_foss_version">Non applicabile poiché è una versione FOSS</string>
<!-- System messages -->
<string name="message_room_name_changed">Nome della camera cambiato in: %1$s da %2$s</string>
<string name="message_user_added_by">Utente %1$s aggiunto da %2$s</string>
<string name="message_user_removed_by">Utente %1$s rimosso da %2$s</string>
<string name="message_user_left">Ha lasciato il canale.</string>
<string name="message_user_joined_channel">È entrato nel canale.</string>
<string name="message_welcome">Benvenuto %s</string>
<string name="message_removed">Messaggio rimosso</string>
<string name="message_pinned">Messaggio segnato:</string>
<string name="message_muted">Utente %1$s disattivato da %2$s</string>
<string name="message_unmuted">Utente %1$s riattivato da %2$s</string>
<string name="message_role_add">%1$s ha il ruolo %2$s aggiunto da %3$s</string>
<string name="message_role_removed">%1$s ha il ruolo %2$s rimosso da %3$s</string>
<string name="message_credentials_saved_successfully">Credenziali salvate con successo</string>
<!-- Message actions -->
<string name="action_msg_reply">Rispondi</string>
<string name="action_msg_info">Informazioni messaggio</string>
<string name="action_msg_edit">Modifica</string>
<string name="action_msg_copy">Copia</string>
<string name="action_msg_quote">Citazione</string>
<string name="action_msg_delete">Cancella</string>
<string name="action_msg_pin">Messaggio importante</string>
<string name="action_msg_unpin">Messaggio normale</string>
<string name="action_msg_star">Messaggio preferito</string>
<string name="action_msg_unstar">Messaggio normale</string>
<string name="action_msg_share">Condividi</string>
<string name="action_title_editing">Modifica messaggio</string>
<string name="action_msg_add_reaction">Aggiungi reazione</string>
<string name="action_msg_copy_permalink">Copia permalink</string>3
<!-- Permission messages -->
<string name="permission_editing_not_allowed">La modifica non è consentita</string>
<string name="permission_deleting_not_allowed">La cancellazione non è consentita</string>
<string name="permission_pinning_not_allowed">Segnare come importante non consentito</string>
<string name="permission_starring_not_allowed">Segnare come preferito non consentito</string>
<!-- Search message -->
<string name="title_search_message">Cerca messaggio</string>
<!-- Favorite/Unfavorite chat room -->
<string name="title_favorite_chat">Stanza preferita</string>
<string name="title_unfavorite_chat">Stanza normale</string>
<!-- Members List -->
<string name="title_members_list">Partecipanti</string>
<!-- Mentions -->
<string name="msg_mentions">Menziona</string>
<string name="msg_no_mention">Togli menzione</string>
<string name="msg_all_the_mentions_appear_here">Tutte le menzioni\nappaiono qui</string>
<!-- Pinned Messages -->
<string name="title_pinned_messages">Messaggi importanti</string>
<string name="no_pinned_messages">Nessun messaggio importante</string>
<string name="no_pinned_description">Tutti i messaggi importanti\nappaiono qui</string>
<!-- Favorite Messages -->
<string name="title_favorite_messages">Messaggi preferiti</string>
<string name="no_favorite_messages">Nessun messaggio preferito</string>
<string name="no_favorite_description">Tutti i messaggi preferiti\nappaiono qui</string>
<!-- Files -->
<string name="title_files">Documenti</string>
<string name="title_files_total">Totale Documenti (%d)</string>
<string name="msg_no_files">Nessun documento</string>
<string name="msg_all_files_appear_here">Tutti i documenti appaiono qui</string>
<!-- Upload Messages -->
<string name="max_file_size_exceeded">Il documento di dimensione %1$d bytes eccede la massima dimensione di caricamento di %2$d bytes</string>
<!-- Socket status -->
<string name="status_connected">Collegato</string>
<string name="status_disconnected">Non collegato</string>
<string name="status_connecting">Collegamento</string>
<string name="status_authenticating">Autenticazione</string>
<string name="status_disconnecting">Disconessione</string>
<string name="status_waiting">Collegamento fra %d secondi</string>
<!--Suggestions-->
<string name="suggest_all_description">Notifica tutti in questa stanza</string>
<string name="suggest_here_description">Notifica gli utenti attivi di questa stanza</string>
<!-- Slash Commands -->
<string name="Slash_Gimme_Description">Mostra ༼ つ ◕_◕ ༽つ prima del tuo messaggio</string>
<string name="Slash_LennyFace_Description">Mostra ( ͡° ͜ʖ ͡°) dopo il tuo messaggio</string>
<string name="Slash_Shrug_Description">Mostra ¯\_(ツ)_/¯ dopo il tuo messaggio</string>
<string name="Slash_Tableflip_Description">Mostra (╯°□°)╯︵ ┻━┻</string>
<string name="Slash_TableUnflip_Description">Mostra ┬─┬ ノ( ゜-゜ノ)</string>
<string name="Create_A_New_Channel">Crea un nuovo canale</string>
<string name="Show_the_keyboard_shortcut_list">Mostra l\'elenco delle scorciatoie da tastiera</string>
<string name="Invite_user_to_join_channel_all_from">Invita tutti gli utenti da [#channel] a unirsi a questo canale</string>
<string name="Invite_user_to_join_channel_all_to">Invita tutti gli utenti da questo canale a partecipare a [#channel]</string>
<string name="Archive">Archivia</string>
<string name="Remove_someone_from_room">Rimuovi qualcuno dalla stanza</string>
<string name="Leave_the_current_channel">Lascia il canale corrente</string>
<string name="Displays_action_text">Visualizza il testo dell\'azione</string>
<string name="Direct_message_someone">Invia un messaggio diretto a qualcuno</string>
<string name="Mute_someone_in_room">Disattiva qualcuno nella stanza</string>
<string name="Unmute_someone_in_room">Riattiva qualcuno nella stanza</string>
<string name="Invite_user_to_join_channel">Invita un utente a unirsi a questo canale</string>
<string name="Unarchive">Non archiviare</string>
<string name="Join_the_given_channel">Unisciti al canale indicato</string>
<string name="Guggy_Command_Description">Genera una gif in base al testo fornito</string>
<string name="Slash_Topic_Description">Imposta argomento</string>
<!-- Emoji message-->
<string name="msg_no_recent_emoji">No recent emojis</string>
<string name="alert_title_default_skin_tone">Default skin tone</string>
<!-- Sorting and grouping-->
<string name="menu_chatroom_sort">Ordinare</string>
<string name="dialog_sort_title">Ordinare per</string>
<string name="dialog_sort_by_alphabet">Alfabeto</string>
<string name="dialog_sort_by_activity">Attività</string>
<string name="dialog_group_by_type">Raggruppa per tipo</string>
<string name="dialog_group_favourites">Raggruppa preferiti</string>
<string name="dialog_button_done">Fatto</string>
<string name="chatroom_header">Intestazione</string>
<!--ChatRooms Headers-->
<string name="header_channel">Canali</string>
<string name="header_private_groups">Gruppi Privati</string>
<string name="header_direct_messages">Messaggi Diretti</string>
<string name="header_live_chats">Stanza in diretta</string>
<string name="header_unknown">Sconosciuto</string>
<!--Notifications-->
<string name="share_label">Modifica messaggio condiviso</string>
<string name="notif_action_reply_hint">Risposta</string>
<string name="notif_error_sending">La risposta è fallita. Per favore riprova.</string>
<string name="notif_success_sending">Messaggio inviato a %1$s!</string>
<string name="read_by">Letto da</string>
<string name="message_information_title">Informazioni Messaggio</string>
<string name="message_room_changed_privacy">Il tipo di stanza è cambiato in: %1$s da %2$s</string>
</resources>
...@@ -18,12 +18,14 @@ ...@@ -18,12 +18,14 @@
<string name="title_settings">設定</string> <string name="title_settings">設定</string>
<string name="title_preferences">環境設定</string> <string name="title_preferences">環境設定</string>
<string name="title_change_password">パスワードの変更</string> <string name="title_change_password">パスワードの変更</string>
<string name="title_rate_us">私たちを評価してください</string>
<string name="title_admin_panel">管理パネル</string> <string name="title_admin_panel">管理パネル</string>
<string name="title_password">パスワードの変更</string> <string name="title_password">パスワードの変更</string>
<string name="title_update_profile">プロフィールの更新</string> <string name="title_update_profile">プロフィールの更新</string>
<string name="title_about">About</string> <string name="title_about">About</string>
<string name="title_licence">Licence</string> <!-- TODO Add translation --> <string name="title_licence">Licence</string> <!-- TODO Add translation -->
<string name="title_create_channel">新しいチャネルを作成</string> <string name="title_create_channel">新しいチャネルを作成</string>
<string name="title_are_you_sure">本気ですか?</string>
<!-- Actions --> <!-- Actions -->
<string name="action_connect">接続</string> <string name="action_connect">接続</string>
...@@ -54,16 +56,17 @@ ...@@ -54,16 +56,17 @@
<string name="action_create_server">新規サーバーを作成</string> <string name="action_create_server">新規サーバーを作成</string>
<string name="action_register">登録</string> <string name="action_register">登録</string>
<string name="action_confirm">確認</string> <string name="action_confirm">確認</string>
<string name="action_delete_account">Delete account</string> <!-- TODO Add translation --> <string name="action_delete_account">アカウントを削除する</string>
<!-- Settings List --> <!-- Settings List -->
<string-array name="settings_actions"> <string-array name="settings_actions">
<item name="item_preferences">環境設定</item> <item name="item_preferences">Preferences</item> <!-- TODO Add translation -->
<item name="item_password">パスワードの変更</item> <item name="item_password">Change password</item> <!-- TODO Add translation -->
<item name="item_password">アプリ情報</item> <item name="item_share_app">Share app</item> <!-- TODO Add translation -->
<item name="item_share_app">アプリを共有する</item> <item name="item_rate_us">Rate us</item> <!-- TODO Add translation -->
<item name="item_share_app_licence">Licence</item> <!-- TODO Add translation -->
<item name="item_contact_us">Contact us</item> <!-- TODO Add translation --> <item name="item_contact_us">Contact us</item> <!-- TODO Add translation -->
<item name="item_licence">Licence</item> <!-- TODO Add translation -->
<item name="item_about">About</item> <!-- TODO Add translation -->
</string-array> </string-array>
<!-- Regular information messages --> <!-- Regular information messages -->
...@@ -155,10 +158,14 @@ ...@@ -155,10 +158,14 @@
<string name="msg_continue_with_wordpress"><b>WordPress</b>でログイン</string> <string name="msg_continue_with_wordpress"><b>WordPress</b>でログイン</string>
<string name="msg_two_factor_authentication">二要素認証</string> <string name="msg_two_factor_authentication">二要素認証</string>
<string name="msg__your_2fa_code">あなたの 2FA コードは何ですか?</string> <string name="msg__your_2fa_code">あなたの 2FA コードは何ですか?</string>
<string name="msg_view_more">更に表示</string> <string name="msg_view_more">更に表示</string>
<string name="msg_view_less">隠す</string> <string name="msg_view_less">隠す</string>
<string name="msg_muted_on_this_channel">あなたはこのチャンネルでミュートされています</string>
<!-- TODO - Add proper translation -->
<plurals name="msg_reacted_with_">
<item quantity="one">%1$s reacted with %2$s</item>
<item quantity="other">%1$s reacted with %2$s</item>
</plurals>
<!-- Create channel messages --> <!-- Create channel messages -->
<string name="msg_private_channel">プライベート</string> <string name="msg_private_channel">プライベート</string>
...@@ -213,7 +220,6 @@ ...@@ -213,7 +220,6 @@
<string name="action_msg_share">Share</string> <string name="action_msg_share">Share</string>
<string name="action_title_editing">メッセージの編集</string> <string name="action_title_editing">メッセージの編集</string>
<string name="action_msg_add_reaction">リアクションする</string> <string name="action_msg_add_reaction">リアクションする</string>
<!-- TODO - Add proper translation -->
<string name="action_msg_copy_permalink">パーマリンクのコピー</string> <string name="action_msg_copy_permalink">パーマリンクのコピー</string>
<!-- Permission messages --> <!-- Permission messages -->
......
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
<string name="title_settings">Configurações</string> <string name="title_settings">Configurações</string>
<string name="title_preferences">Preferencias</string> <string name="title_preferences">Preferencias</string>
<string name="title_change_password">Alterar senha</string> <string name="title_change_password">Alterar senha</string>
<string name="title_rate_us">nos avalie</string>
<string name="title_admin_panel">Painel administrativo</string> <string name="title_admin_panel">Painel administrativo</string>
<string name="title_password">Alterar senha</string> <string name="title_password">Alterar senha</string>
<string name="title_update_profile">Editar perfil</string> <string name="title_update_profile">Editar perfil</string>
...@@ -57,12 +58,13 @@ ...@@ -57,12 +58,13 @@
<!-- Settings List --> <!-- Settings List -->
<string-array name="settings_actions"> <string-array name="settings_actions">
<item name="item_preferences">Preferencias</item> <item name="item_preferences">Preferências</item>
<item name="item_password">Alterar senha</item> <item name="item_password">Alterar senha</item>
<item name="item_password">Sobre</item> <item name="item_share_app">Compartilhar app</item>
<item name="item_share_app">Compartilhe o aplicativo</item> <item name="item_rate_us">Classifique-nos</item>
<item name="item_share_app_licence">Licença</item>
<item name="item_contact_us">Contate-nos</item> <item name="item_contact_us">Contate-nos</item>
<item name="item_licence">Licença</item>
<item name="item_about">Sobre</item>
</string-array> </string-array>
<!-- Regular information messages --> <!-- Regular information messages -->
...@@ -161,6 +163,11 @@ ...@@ -161,6 +163,11 @@
<string name="msg_permalink_copied">Permalink copiado</string> <string name="msg_permalink_copied">Permalink copiado</string>
<string name="msg_send_email">Enviar e-mail</string> <string name="msg_send_email">Enviar e-mail</string>
<string name="msg_android_app_support">Suporte ao aplicativo Android</string> <string name="msg_android_app_support">Suporte ao aplicativo Android</string>
<string name="msg_muted_on_this_channel">Você está silenciado neste canal</string>
<plurals name="msg_reacted_with_">
<item quantity="one">%1$s reagiu com %2$s</item>
<item quantity="other">%1$s reagiram com %2$s</item>
</plurals>
<!-- Create channel messages --> <!-- Create channel messages -->
<string name="msg_private_channel">Privado</string> <string name="msg_private_channel">Privado</string>
...@@ -189,8 +196,8 @@ ...@@ -189,8 +196,8 @@
<string name="message_welcome">Bem-vindo, %s</string> <string name="message_welcome">Bem-vindo, %s</string>
<string name="message_removed">Mensagem removida</string> <string name="message_removed">Mensagem removida</string>
<string name="message_pinned">Pinou uma mensagem:</string> <string name="message_pinned">Pinou uma mensagem:</string>
<string name="message_muted">Usuário %1$s entrou no modo mudo por %2$s</string> <string name="message_muted">Usuário %1$s foi silenciado por %2$s</string>
<string name="message_unmuted">Usuário %1$s saiu do modo mudo por %2$s</string> <string name="message_unmuted">Usuário %1$s saiu do modo silenciado por %2$s</string>
<string name="message_role_add">%1$s foi definido %2$s por %3$s</string> <string name="message_role_add">%1$s foi definido %2$s por %3$s</string>
<string name="message_role_removed">%1$s não é mais %2$s por %3$s</string> <string name="message_role_removed">%1$s não é mais %2$s por %3$s</string>
// TODO:Add proper translation. // TODO:Add proper translation.
......
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
<string name="title_settings">Настройки</string> <string name="title_settings">Настройки</string>
<string name="title_preferences">Персональные</string> <string name="title_preferences">Персональные</string>
<string name="title_change_password">Изменить пароль</string> <string name="title_change_password">Изменить пароль</string>
<string name="title_rate_us">оцените нас</string>
<string name="title_admin_panel">Панель админа</string> <string name="title_admin_panel">Панель админа</string>
<string name="title_password">Изменить пароль</string> <string name="title_password">Изменить пароль</string>
<string name="title_update_profile">Обновить профиль</string> <string name="title_update_profile">Обновить профиль</string>
...@@ -57,12 +58,13 @@ ...@@ -57,12 +58,13 @@
<!-- Settings List --> <!-- Settings List -->
<string-array name="settings_actions"> <string-array name="settings_actions">
<item name="item_preferences">Персональные</item> <item name="item_preferences">Preferences</item> <!-- TODO Add translation -->
<item name="item_password">Изменить пароль</item> <item name="item_password">Change password</item> <!-- TODO Add translation -->
<item name="item_password">О программе</item> <item name="item_share_app">Share app</item> <!-- TODO Add translation -->
<item name="item_share_app">добавить приложение</item> <item name="item_rate_us">Rate us</item> <!-- TODO Add translation -->
<item name="item_share_app_licence">Licence</item> <!-- TODO Add translation -->
<item name="item_contact_us">Contact us</item> <!-- TODO Add translation --> <item name="item_contact_us">Contact us</item> <!-- TODO Add translation -->
<item name="item_licence">Licence</item> <!-- TODO Add translation -->
<item name="item_about">About</item> <!-- TODO Add translation -->
</string-array> </string-array>
<!-- Regular information messages --> <!-- Regular information messages -->
...@@ -159,6 +161,12 @@ ...@@ -159,6 +161,12 @@
<string name="msg_permalink_copied">Ссылка скопирована</string> <string name="msg_permalink_copied">Ссылка скопирована</string>
<string name="msg_send_email">Send email</string> <!-- TODO - Add proper translation --> <string name="msg_send_email">Send email</string> <!-- TODO - Add proper translation -->
<string name="msg_android_app_support">Android app support</string> <!-- TODO - Add proper translation --> <string name="msg_android_app_support">Android app support</string> <!-- TODO - Add proper translation -->
<string name="msg_muted_on_this_channel">You are muted on this channel</string> <!-- TODO - Add proper translation -->
<plurals name="msg_reacted_with_">
<item quantity="one">%1$s reacted with %2$s</item> <!-- TODO - Add proper translation -->
<item quantity="few">%1$s reacted with %2$s</item> <!-- TODO - Add proper translation -->
<item quantity="many">%1$s reacted with %2$s</item> <!-- TODO - Add proper translation -->
</plurals>
<!-- Create channel messages --> <!-- Create channel messages -->
<string name="msg_private_channel">Приватный</string> <string name="msg_private_channel">Приватный</string>
......
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
<string name="title_settings">Ayarlar</string> <string name="title_settings">Ayarlar</string>
<string name="title_preferences">Tercihler</string> <string name="title_preferences">Tercihler</string>
<string name="title_change_password">Şifre Değişikliği</string> <string name="title_change_password">Şifre Değişikliği</string>
<string name="title_rate_us">Bizi değerlendirin</string>
<string name="title_admin_panel">Yönetici Paneli</string> <string name="title_admin_panel">Yönetici Paneli</string>
<string name="title_password">Şifrenizi Değiştirin</string> <string name="title_password">Şifrenizi Değiştirin</string>
<string name="title_update_profile">Profilinizi Düzenleyin</string> <string name="title_update_profile">Profilinizi Düzenleyin</string>
...@@ -57,12 +58,13 @@ ...@@ -57,12 +58,13 @@
<!-- Settings List --> <!-- Settings List -->
<string-array name="settings_actions"> <string-array name="settings_actions">
<item name="item_preferences">Tercihler</item> <item name="item_preferences">Preferences</item> <!-- TODO Add translation -->
<item name="item_password">Şifre Değiştir</item> <item name="item_password">Change password</item> <!-- TODO Add translation -->
<item name="item_password">Hakkında</item> <item name="item_share_app">Share app</item> <!-- TODO Add translation -->
<item name="item_share_app">uygulamayı Paylaş</item> <item name="item_rate_us">Rate us</item> <!-- TODO Add translation -->
<item name="item_share_app_licence">Licence</item> <!-- TODO Add translation -->
<item name="item_contact_us">Contact us</item> <!-- TODO Add translation --> <item name="item_contact_us">Contact us</item> <!-- TODO Add translation -->
<item name="item_licence">Licence</item> <!-- TODO Add translation -->
<item name="item_about">About</item> <!-- TODO Add translation -->
</string-array> </string-array>
<!-- Regular information messages --> <!-- Regular information messages -->
...@@ -175,7 +177,11 @@ ...@@ -175,7 +177,11 @@
<string name="msg_permalink_copied">Permalink copied</string> <!-- TODO - Add proper translation --> <string name="msg_permalink_copied">Permalink copied</string> <!-- TODO - Add proper translation -->
<string name="msg_send_email">Send email</string> <!-- TODO - Add proper translation --> <string name="msg_send_email">Send email</string> <!-- TODO - Add proper translation -->
<string name="msg_android_app_support">Android app support</string> <!-- TODO - Add proper translation --> <string name="msg_android_app_support">Android app support</string> <!-- TODO - Add proper translation -->
<string name="msg_muted_on_this_channel">You are muted on this channel</string> <!-- TODO - Add proper translation -->
<plurals name="msg_reacted_with_">
<item quantity="one">%1$s reacted with %2$s</item> <!-- TODO - Add proper translation -->
<item quantity="other">%1$s reacted with %2$s</item> <!-- TODO - Add proper translation -->
</plurals>
<!-- Preferences messages --> <!-- Preferences messages -->
<string name="msg_analytics_tracking">İstatistik takibi</string> <string name="msg_analytics_tracking">İstatistik takibi</string>
<string name="msg_send_analytics_tracking">Uygulamanın gelişmesine katkıda bulunmak için anonim istatistik bilgisi gönder</string> <string name="msg_send_analytics_tracking">Uygulamanın gelişmesine katkıda bulunmak için anonim istatistik bilgisi gönder</string>
......
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
<string name="title_settings">Налаштування</string> <string name="title_settings">Налаштування</string>
<string name="title_preferences">Персональні</string> <string name="title_preferences">Персональні</string>
<string name="title_change_password">Змінити пароль</string> <string name="title_change_password">Змінити пароль</string>
<string name="title_rate_us">Оцініть нас</string>
<string name="title_admin_panel">Панель адміністратора</string> <string name="title_admin_panel">Панель адміністратора</string>
<string name="title_password">Змінити пароль</string> <string name="title_password">Змінити пароль</string>
<string name="title_update_profile">Оновити профіль</string> <string name="title_update_profile">Оновити профіль</string>
...@@ -58,11 +59,12 @@ ...@@ -58,11 +59,12 @@
<!-- Settings List --> <!-- Settings List -->
<string-array name="settings_actions"> <string-array name="settings_actions">
<item name="item_preferences">Preferences</item> <!-- TODO Add translation --> <item name="item_preferences">Preferences</item> <!-- TODO Add translation -->
<item name="item_password">Change Password</item> <!-- TODO Add translation --> <item name="item_password">Change password</item> <!-- TODO Add translation -->
<item name="item_password">About</item> <!-- TODO Add translation --> <item name="item_share_app">Share app</item> <!-- TODO Add translation -->
<item name="item_share_app">поділитися прикладом</item> <item name="item_rate_us">Rate us</item> <!-- TODO Add translation -->
<item name="item_share_app_licence">Licence</item> <!-- TODO Add translation -->
<item name="item_contact_us">Contact us</item> <!-- TODO Add translation --> <item name="item_contact_us">Contact us</item> <!-- TODO Add translation -->
<item name="item_licence">Licence</item> <!-- TODO Add translation -->
<item name="item_about">About</item> <!-- TODO Add translation -->
</string-array> </string-array>
<!-- Regular information messages --> <!-- Regular information messages -->
...@@ -158,6 +160,12 @@ ...@@ -158,6 +160,12 @@
<string name="msg_permalink_copied">Permalink copied</string> <!-- TODO - Add proper translation --> <string name="msg_permalink_copied">Permalink copied</string> <!-- TODO - Add proper translation -->
<string name="msg_send_email">Send email</string> <!-- TODO - Add proper translation --> <string name="msg_send_email">Send email</string> <!-- TODO - Add proper translation -->
<string name="msg_android_app_support">Android app support</string> <!-- TODO - Add proper translation --> <string name="msg_android_app_support">Android app support</string> <!-- TODO - Add proper translation -->
<string name="msg_muted_on_this_channel">You are muted on this channel</string> <!-- TODO - Add proper translation -->
<plurals name="msg_reacted_with_">
<item quantity="one">%1$s reacted with %2$s</item> <!-- TODO - Add proper translation -->
<item quantity="few">%1$s reacted with %2$s</item> <!-- TODO - Add proper translation -->
<item quantity="many">%1$s reacted with %2$s</item> <!-- TODO - Add proper translation -->
</plurals>
<!-- Create channel messages --> <!-- Create channel messages -->
<string name="msg_private_channel">Приватний</string> <string name="msg_private_channel">Приватний</string>
......
...@@ -5,5 +5,6 @@ ...@@ -5,5 +5,6 @@
<string name="community_server_url" translatable="false">open.rocket.chat</string> <string name="community_server_url" translatable="false">open.rocket.chat</string>
<string name="create_server_url" translatable="false">cloud.rocket.chat/trial</string> <string name="create_server_url" translatable="false">cloud.rocket.chat/trial</string>
<string name="play_store_link" translatable="false">https://play.google.com/store/apps/details?id=chat.rocket.android</string> <string name="play_store_link" translatable="false">https://play.google.com/store/apps/details?id=chat.rocket.android</string>
<string name="market_link" translatable="false">market://details?id=chat.rocket.android</string>
<string name="license_url" translatable="false">https://github.com/RocketChat/Rocket.Chat.Android/blob/develop/LICENSE</string> <string name="license_url" translatable="false">https://github.com/RocketChat/Rocket.Chat.Android/blob/develop/LICENSE</string>
</resources> </resources>
\ No newline at end of file
...@@ -28,6 +28,7 @@ https://github.com/RocketChat/java-code-styles/blob/master/CODING_STYLE.md#strin ...@@ -28,6 +28,7 @@ https://github.com/RocketChat/java-code-styles/blob/master/CODING_STYLE.md#strin
<string name="title_settings">Settings</string> <string name="title_settings">Settings</string>
<string name="title_preferences">Preferences</string> <string name="title_preferences">Preferences</string>
<string name="title_change_password">Change Password</string> <string name="title_change_password">Change Password</string>
<string name="title_rate_us">Rate Us</string>
<string name="title_admin_panel">Admin panel</string> <string name="title_admin_panel">Admin panel</string>
<string name="title_password">Change Password</string> <string name="title_password">Change Password</string>
<string name="title_update_profile">Update profile</string> <string name="title_update_profile">Update profile</string>
...@@ -70,11 +71,12 @@ https://github.com/RocketChat/java-code-styles/blob/master/CODING_STYLE.md#strin ...@@ -70,11 +71,12 @@ https://github.com/RocketChat/java-code-styles/blob/master/CODING_STYLE.md#strin
<!-- Settings List --> <!-- Settings List -->
<string-array name="settings_actions"> <string-array name="settings_actions">
<item name="item_preferences">Preferences</item> <item name="item_preferences">Preferences</item>
<item name="item_password">Change Password</item> <item name="item_password">Change password</item>
<item name="item_password">About</item> <item name="item_share_app">Share app</item>
<item name="item_share_app">Share App</item> <item name="item_rate_us">Rate us</item>
<item name="item_share_app_licence">Licence</item>
<item name="item_contact_us">Contact us</item> <item name="item_contact_us">Contact us</item>
<item name="item_licence">Licence</item>
<item name="item_about">About</item>
</string-array> </string-array>
<!-- Regular information messages --> <!-- Regular information messages -->
...@@ -171,6 +173,10 @@ https://github.com/RocketChat/java-code-styles/blob/master/CODING_STYLE.md#strin ...@@ -171,6 +173,10 @@ https://github.com/RocketChat/java-code-styles/blob/master/CODING_STYLE.md#strin
<string name="msg_permalink_copied">Permalink copied</string> <string name="msg_permalink_copied">Permalink copied</string>
<string name="msg_send_email">Send email</string> <string name="msg_send_email">Send email</string>
<string name="msg_android_app_support">Android app support</string> <string name="msg_android_app_support">Android app support</string>
<plurals name="msg_reacted_with_">
<item quantity="one">%1$s reacted with %2$s</item>
<item quantity="other">%1$s reacted with %2$s</item>
</plurals>
<!-- Create channel messages --> <!-- Create channel messages -->
<string name="msg_private_channel">Private</string> <string name="msg_private_channel">Private</string>
...@@ -188,6 +194,7 @@ https://github.com/RocketChat/java-code-styles/blob/master/CODING_STYLE.md#strin ...@@ -188,6 +194,7 @@ https://github.com/RocketChat/java-code-styles/blob/master/CODING_STYLE.md#strin
<string name="msg_delete_description">Are you sure you want to delete this message</string> <string name="msg_delete_description">Are you sure you want to delete this message</string>
<string name="msg_view_more">view more</string> <string name="msg_view_more">view more</string>
<string name="msg_view_less">view less</string> <string name="msg_view_less">view less</string>
<string name="msg_muted_on_this_channel">You are muted on this channel</string>
<!-- Preferences messages --> <!-- Preferences messages -->
<string name="msg_analytics_tracking">Analytics tracking</string> <string name="msg_analytics_tracking">Analytics tracking</string>
......
...@@ -67,4 +67,11 @@ class AnswersAnalytics : Analytics { ...@@ -67,4 +67,11 @@ class AnswersAnalytics : Analytics {
) )
override fun logOpenAdmin() = Answers.getInstance().logCustom(CustomEvent("open_admin")) override fun logOpenAdmin() = Answers.getInstance().logCustom(CustomEvent("open_admin"))
override fun logResetPassword(resetPasswordSucceeded: Boolean) =
Answers.getInstance()
.logCustom(
CustomEvent("reset_password")
.putCustomAttribute("resetPasswordSucceeded", resetPasswordSucceeded.toString())
)
} }
...@@ -60,4 +60,9 @@ class GoogleAnalyticsForFirebase @Inject constructor(val context: Context) : ...@@ -60,4 +60,9 @@ class GoogleAnalyticsForFirebase @Inject constructor(val context: Context) :
} }
override fun logOpenAdmin() = firebaseAnalytics.logEvent("open_admin", null) override fun logOpenAdmin() = firebaseAnalytics.logEvent("open_admin", null)
override fun logResetPassword(resetPasswordSucceeded: Boolean) =
firebaseAnalytics.logEvent("reset_password", Bundle(1).apply {
putBoolean("resetPasswordSucceeded", resetPasswordSucceeded)
})
} }
package chat.rocket.android.push package chat.rocket.android.push
import android.os.Bundle
import androidx.core.os.bundleOf import androidx.core.os.bundleOf
import androidx.work.Constraints import androidx.work.Constraints
import androidx.work.NetworkType import androidx.work.NetworkType
...@@ -23,9 +24,12 @@ class FirebaseMessagingService : FirebaseMessagingService() { ...@@ -23,9 +24,12 @@ class FirebaseMessagingService : FirebaseMessagingService() {
} }
override fun onMessageReceived(message: RemoteMessage) { override fun onMessageReceived(message: RemoteMessage) {
// XXX - for now this is ok, if we start to do network calls, use a Worker instead message.data?.let { data ->
message.data?.let { val bundle = Bundle()
pushManager.handle(bundleOf(*(it.map { Pair(it.key, it.value) }).toTypedArray())) data.entries.forEach { entry ->
bundle.putString(entry.key, entry.value)
}
pushManager.handle(bundle)
} }
} }
......
...@@ -10,7 +10,6 @@ import android.view.View ...@@ -10,7 +10,6 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.EditText import android.widget.EditText
import android.widget.ImageView import android.widget.ImageView
import android.widget.TextView
import androidx.annotation.ColorInt import androidx.annotation.ColorInt
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
...@@ -22,6 +21,7 @@ import chat.rocket.android.emoji.internal.EmojiCategory ...@@ -22,6 +21,7 @@ import chat.rocket.android.emoji.internal.EmojiCategory
import chat.rocket.android.emoji.internal.EmojiPagerAdapter import chat.rocket.android.emoji.internal.EmojiPagerAdapter
import chat.rocket.android.emoji.internal.PREF_EMOJI_SKIN_TONE import chat.rocket.android.emoji.internal.PREF_EMOJI_SKIN_TONE
import com.google.android.material.tabs.TabLayout import com.google.android.material.tabs.TabLayout
import kotlinx.android.synthetic.main.dialog_skin_tone_chooser.view.*
import kotlinx.coroutines.experimental.android.UI import kotlinx.coroutines.experimental.android.UI
import kotlinx.coroutines.experimental.launch import kotlinx.coroutines.experimental.launch
...@@ -77,59 +77,50 @@ class EmojiKeyboardPopup(context: Context, view: View) : OverKeyboardPopupWindow ...@@ -77,59 +77,50 @@ class EmojiKeyboardPopup(context: Context, view: View) : OverKeyboardPopupWindow
} }
private fun showSkinToneChooser() { private fun showSkinToneChooser() {
val view = LayoutInflater.from(context).inflate(R.layout.color_select_popup, null) val view = LayoutInflater.from(context).inflate(R.layout.dialog_skin_tone_chooser, null)
val dialog = AlertDialog.Builder(context, R.style.Dialog) val dialog = AlertDialog.Builder(context, R.style.Dialog)
.setView(view) .setView(view)
.setTitle(context.getString(R.string.alert_title_default_skin_tone)) .setTitle(context.getString(R.string.alert_title_default_skin_tone))
.setCancelable(true) .setCancelable(true)
.create() .create()
view.findViewById<TextView>(R.id.default_tone_text).also { with (view) {
it.text = EmojiParser.parse(context, it.text) image_view_default_tone.setOnClickListener {
}.setOnClickListener { dialog.dismiss()
dialog.dismiss() changeSkinTone(Fitzpatrick.Default)
changeSkinTone(Fitzpatrick.Default) }
}
view.findViewById<TextView>(R.id.light_tone_text).also { image_view_light_tone.setOnClickListener {
it.text = EmojiParser.parse(context, it.text) dialog.dismiss()
}.setOnClickListener { changeSkinTone(Fitzpatrick.LightTone)
dialog.dismiss() }
changeSkinTone(Fitzpatrick.LightTone)
}
view.findViewById<TextView>(R.id.medium_light_text).also { image_view_medium_light.setOnClickListener {
it.text = EmojiParser.parse(context, it.text) dialog.dismiss()
}.setOnClickListener { changeSkinTone(Fitzpatrick.MediumLightTone)
dialog.dismiss() }
changeSkinTone(Fitzpatrick.MediumLightTone)
}
view.findViewById<TextView>(R.id.medium_tone_text).also { image_view_medium_tone.setOnClickListener {
it.text = EmojiParser.parse(context, it.text) dialog.dismiss()
}.setOnClickListener { changeSkinTone(Fitzpatrick.MediumTone)
dialog.dismiss() }
changeSkinTone(Fitzpatrick.MediumTone)
}
view.findViewById<TextView>(R.id.medium_dark_tone_text).also { image_view_medium_dark_tone.setOnClickListener {
it.text = EmojiParser.parse(context, it.text) dialog.dismiss()
}.setOnClickListener { changeSkinTone(Fitzpatrick.MediumDarkTone)
dialog.dismiss() }
changeSkinTone(Fitzpatrick.MediumDarkTone)
}
view.findViewById<TextView>(R.id.dark_tone_text).also { image_view_dark_tone.setOnClickListener {
it.text = EmojiParser.parse(context, it.text) dialog.dismiss()
}.setOnClickListener { changeSkinTone(Fitzpatrick.DarkTone)
dialog.dismiss() }
changeSkinTone(Fitzpatrick.DarkTone)
} }
dialog.show() dialog.show()
} }
private fun changeSkinTone(tone: Fitzpatrick) { private fun changeSkinTone(tone: Fitzpatrick) {
val drawable = ContextCompat.getDrawable(context, R.drawable.color_change_circle)!! val drawable = ContextCompat.getDrawable(context, R.drawable.bg_skin_tone)!!
val wrappedDrawable = DrawableCompat.wrap(drawable) val wrappedDrawable = DrawableCompat.wrap(drawable)
DrawableCompat.setTint(wrappedDrawable, getFitzpatrickColor(tone)) DrawableCompat.setTint(wrappedDrawable, getFitzpatrickColor(tone))
(changeColorView as ImageView).setImageDrawable(wrappedDrawable) (changeColorView as ImageView).setImageDrawable(wrappedDrawable)
......
...@@ -16,4 +16,14 @@ interface EmojiReactionListener { ...@@ -16,4 +16,14 @@ interface EmojiReactionListener {
* @param emojiShortname The shortname of the emoji (:grin:, :smiley:, etc). * @param emojiShortname The shortname of the emoji (:grin:, :smiley:, etc).
*/ */
fun onReactionTouched(messageId: String, emojiShortname: String) fun onReactionTouched(messageId: String, emojiShortname: String)
}
\ No newline at end of file /**
* Callback when an added reaction is long-clicked.
*
* @param shortname The shortname of the emoji (:grin:, :smiley:, etc).
* @param isCustom Whether the reaction is custom or one of the defaults.
* @param url In case of a custom emoji, this is the url to find it. Can be null if not a custom.
* @param usernames The list of usernames of users who added the reaction.
*/
fun onReactionLongClicked(shortname: String, isCustom: Boolean, url: String?, usernames: List<String>)
}
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" <shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval"> android:shape="oval">
<solid android:color="@color/tone_default" /> <solid android:color="@color/tone_default" />
<size <size
android:width="24dp" android:width="24dp"
android:height="24dp" /> android:height="24dp" />
</shape> </shape>
\ 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:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"> android:layout_height="wrap_content">
<TextView <ImageView
android:id="@+id/default_tone_text" android:id="@+id/image_view_default_tone"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="16dp" android:layout_marginTop="16dp"
android:text="👌" android:layout_marginBottom="16dp"
android:textSize="32sp"
android:tint="@color/tone_default" android:tint="@color/tone_default"
app:layout_constraintEnd_toStartOf="@+id/light_tone_text" app:layout_constraintBottom_toTopOf="@+id/image_view_medium_tone"
app:layout_constraintEnd_toStartOf="@+id/image_view_light_tone"
app:layout_constraintHorizontal_bias="0.5" app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintHorizontal_chainStyle="packed" app:layout_constraintHorizontal_chainStyle="spread_inside"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/color_change_circle" /> app:srcCompat="@drawable/bg_skin_tone" />
<TextView <ImageView
android:id="@+id/light_tone_text" android:id="@+id/image_view_light_tone"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="16dp" android:layout_marginTop="16dp"
android:layout_marginEnd="24dp"
android:text="👌🏻"
android:textSize="32sp"
android:tint="@color/tone_light" android:tint="@color/tone_light"
app:layout_constraintEnd_toStartOf="@+id/medium_light_text" app:layout_constraintEnd_toStartOf="@+id/image_view_medium_light"
app:layout_constraintHorizontal_bias="0.5" app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/default_tone_text" app:layout_constraintStart_toEndOf="@+id/image_view_default_tone"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/color_change_circle" app:srcCompat="@drawable/bg_skin_tone" />
tools:text="👌" />
<TextView <ImageView
android:id="@+id/medium_light_text" android:id="@+id/image_view_medium_light"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="16dp" android:layout_marginTop="16dp"
android:text="👌🏼" android:layout_marginEnd="24dp"
android:textSize="32sp"
android:tint="@color/tone_medium_light" android:tint="@color/tone_medium_light"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5" app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/light_tone_text" app:layout_constraintStart_toEndOf="@+id/image_view_light_tone"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/color_change_circle" app:srcCompat="@drawable/bg_skin_tone" />
tools:text="👌" />
<TextView <ImageView
android:id="@+id/medium_tone_text" android:id="@+id/image_view_medium_tone"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
android:layout_marginBottom="16dp" android:layout_marginBottom="16dp"
android:text="👌🏽"
android:textSize="32sp"
android:tint="@color/tone_medium" android:tint="@color/tone_medium"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/medium_dark_tone_text" app:layout_constraintEnd_toStartOf="@+id/image_view_medium_dark_tone"
app:layout_constraintHorizontal_chainStyle="packed" app:layout_constraintHorizontal_chainStyle="spread_inside"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="@+id/image_view_default_tone"
app:layout_constraintTop_toBottomOf="@+id/default_tone_text" app:layout_constraintTop_toBottomOf="@+id/image_view_default_tone"
app:srcCompat="@drawable/color_change_circle" app:srcCompat="@drawable/bg_skin_tone" />
tools:text="👌" />
<TextView <ImageView
android:id="@+id/medium_dark_tone_text" android:id="@+id/image_view_medium_dark_tone"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="24dp"
android:layout_marginBottom="16dp"
android:text="👌🏾"
android:textSize="32sp"
android:tint="@color/tone_medium_dark" android:tint="@color/tone_medium_dark"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="@+id/image_view_medium_tone"
app:layout_constraintEnd_toStartOf="@+id/dark_tone_text" app:layout_constraintEnd_toStartOf="@+id/image_view_dark_tone"
app:layout_constraintHorizontal_bias="0.5" app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/medium_tone_text" app:layout_constraintStart_toEndOf="@+id/image_view_medium_tone"
app:layout_constraintTop_toBottomOf="@+id/light_tone_text" app:layout_constraintTop_toTopOf="@+id/image_view_medium_tone"
app:srcCompat="@drawable/color_change_circle" app:srcCompat="@drawable/bg_skin_tone" />
tools:text="👌" />
<TextView <ImageView
android:id="@+id/dark_tone_text" android:id="@+id/image_view_dark_tone"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginBottom="16dp"
android:text="👌🏿"
android:textSize="32sp"
android:tint="@color/tone_dark" android:tint="@color/tone_dark"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="@+id/image_view_medium_dark_tone"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="@+id/image_view_medium_light"
app:layout_constraintStart_toEndOf="@+id/medium_dark_tone_text" app:layout_constraintStart_toEndOf="@+id/image_view_medium_dark_tone"
app:layout_constraintTop_toBottomOf="@+id/medium_light_text" app:layout_constraintTop_toTopOf="@+id/image_view_medium_dark_tone"
app:srcCompat="@drawable/color_change_circle" app:srcCompat="@drawable/bg_skin_tone" />
tools:text="👌" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
...@@ -43,7 +43,7 @@ ...@@ -43,7 +43,7 @@
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/color_change_circle" /> app:srcCompat="@drawable/bg_skin_tone" />
<ImageView <ImageView
android:id="@+id/emoji_search" android:id="@+id/emoji_search"
......
<resources> <resources>
<string name="msg_no_recent_emoji">No recent emoji</string> <string name="msg_no_recent_emoji">No recent emoji</string>
<string name="alert_title_default_skin_tone">Default skin tone</string> <string name="alert_title_default_skin_tone">Default skin tone</string>
<string name="msg_reactions" translatable="false">Reactions</string>
</resources> </resources>
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